diff --git a/.changeset/neat-bees-know.md b/.changeset/neat-bees-know.md new file mode 100644 index 0000000..e3da8b4 --- /dev/null +++ b/.changeset/neat-bees-know.md @@ -0,0 +1,5 @@ +--- +'@unisonjs/core': patch +--- + +Handle fast refresh for react hooks diff --git a/packages/core/package.json b/packages/core/package.json index 5443bd6..c99c942 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -4,9 +4,9 @@ "packageManager": "pnpm@8.15.3", "type": "module", "scripts": { - "build": "pnpm build-vue && pnpm build-js && pnpm build-types", - "build-js": "rimraf dist && rollup -c rollup.config.js", - "build-types": "tsc --noCheck && pnpm dlx tsc-alias", + "build": "rimraf dist && pnpm build-vue && pnpm build-js && pnpm build-types", + "build-js": "rimraf dist/*.js && rollup -c rollup.config.js", + "build-types": "rimraf dist/types && tsc --noCheck && pnpm dlx tsc-alias", "build-vue": "pnpm build-vue-js && pnpm build-rm-vue-aliases", "build-vue-js": "(rimraf vue/temp && cd vue && pnpm tsc --noCheck) || exit 0;", "build-rm-vue-aliases": "(pnpm dlx tsc-alias -p vue/tsconfig.json) || exit 0;", diff --git a/packages/core/src/context.ts b/packages/core/src/context.ts index bc83581..5d4035d 100644 --- a/packages/core/src/context.ts +++ b/packages/core/src/context.ts @@ -59,7 +59,7 @@ export type Event = { type OnFlushCallback = (event: Partial) => void; -class OnBeforeMount extends React.Component { +class OnBeforeMount extends React.Component<{ hooks: Function[] }> { componentWillUnmount(): void { for (const hook of this.props.hooks) { hook(); @@ -74,8 +74,8 @@ class Context { #parent: ComponentInternalInstance | null; #renderTrigger: () => void = __DEV__ ? () => { - warn("Can't trigger a new rendering, the state is not setup properly"); - } + warn("Can't trigger a new rendering, the state is not setup properly"); + } : NOOP; #isRunning = false; #propsKeys: string[] = []; @@ -150,7 +150,7 @@ class Context { }); this.#renderEffect.scheduler = () => { this.triggerRendering(); - this.#renderJob = queueJob(() => {}); + this.#renderJob = queueJob(() => { }); this.#shouldGenerateTemplate = true; }; } @@ -180,6 +180,10 @@ class Context { return this.#plugins?.get(key) as InstanceType; } + get plugins() { + return this.#plugins; + } + set children(children: () => React.ReactNode) { this.#children = children; } diff --git a/packages/core/src/management.ts b/packages/core/src/management.ts index 3512577..083a88e 100644 --- a/packages/core/src/management.ts +++ b/packages/core/src/management.ts @@ -49,8 +49,12 @@ export function $unison>(fn: SetupComponent, na instance.setupState(); const trackedProps = instance.trackProps({ ...props, ref }); + if (instance.isFastRefresh() && instance.plugins) { + for (const plugin of instance.plugins.values()) plugin.onInstanceFastRefresh?.(instance); + } + if (!instance.isExecuted() || instance.isFastRefresh()) { - instance.children = fn(trackedProps); + instance.children = fn(trackedProps as any); instance.invalidateChildren(); } diff --git a/packages/core/src/plugins/hook-manager/index.ts b/packages/core/src/plugins/hook-manager/index.ts index e16ac30..c862734 100644 --- a/packages/core/src/plugins/hook-manager/index.ts +++ b/packages/core/src/plugins/hook-manager/index.ts @@ -62,7 +62,7 @@ export class HookManager implements UnisonPlugin { this.#hookEffect.scheduler = () => instance.triggerRendering(); instance.addEventListener(Events.BEFORE_FLUSHING_PRE_EFFECT, ({ job }) => { - if (!instance.isExecuted()) return; + if (!instance.isExecuted() || instance.isFastRefresh()) return; if (job) { const position = job.position || 0; while (this.#i < position) { @@ -73,7 +73,7 @@ export class HookManager implements UnisonPlugin { }); instance.addEventListener(Events.AFTER_FLUSHING_ALL_PRE_EFFECT, () => { - if (!instance.isExecuted()) return; + if (!instance.isExecuted() || instance.isFastRefresh()) return; while (this.#i < this.#hooks.length && !instance.hasPendingPreEffects()) { this.processHook(this.#hooks[this.#i]); this.#i++; @@ -86,6 +86,16 @@ export class HookManager implements UnisonPlugin { this.#i = 0; }); } + reset() { + this.#hookKeys.length = 0; + this.#hookSignals.length = 0; + this.#hookValues.length = 0; + this.#hooks.length = 0; + this.#hookEffect = new ReactiveEffect(NOOP); + } + onInstanceFastRefresh(instance: ComponentInternalInstance): void { + this.reset(); + } onInstanceDisposed(): void {} getValueAt(index: number) { diff --git a/packages/core/src/plugins/index.ts b/packages/core/src/plugins/index.ts index 724836a..3e8c611 100644 --- a/packages/core/src/plugins/index.ts +++ b/packages/core/src/plugins/index.ts @@ -6,5 +6,6 @@ export type UnisonPluginClass = ClassType; export interface UnisonPlugin { onInstanceCreated(instance: ComponentInternalInstance): void + onInstanceFastRefresh(instance: ComponentInternalInstance): void onInstanceDisposed(instance: ComponentInternalInstance): void } diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index edd4079..a25e71a 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -10,7 +10,6 @@ "emitDeclarationOnly": true, "moduleResolution": "bundler", "allowJs": true, - "noCheck": true, "strict": true, "noUnusedLocals": true, "experimentalDecorators": true,