From 107530cd4147d39c1a434b2d56b54edb00cbbb15 Mon Sep 17 00:00:00 2001 From: Qingyu Wang <40660121+colinaaa@users.noreply.github.com> Date: Tue, 3 Jun 2025 15:33:18 +0800 Subject: [PATCH 1/3] feat(rspeedy/react): alias `use-sync-external-store` --- packages/rspeedy/plugin-react/package.json | 1 + .../plugin-react/src/backgroundOnly.ts | 29 +++++----------- .../plugin-react/src/pluginReactLynx.ts | 2 ++ packages/rspeedy/plugin-react/src/resolve.ts | 21 ++++++++++++ .../plugin-react/src/useSyncExternalStore.ts | 28 ++++++++++++++++ .../rspeedy/plugin-react/test/config.test.ts | 33 +++++++++++++++++++ pnpm-lock.yaml | 3 ++ 7 files changed, 97 insertions(+), 20 deletions(-) create mode 100644 packages/rspeedy/plugin-react/src/resolve.ts create mode 100644 packages/rspeedy/plugin-react/src/useSyncExternalStore.ts diff --git a/packages/rspeedy/plugin-react/package.json b/packages/rspeedy/plugin-react/package.json index f05398d895..5be06e1bce 100644 --- a/packages/rspeedy/plugin-react/package.json +++ b/packages/rspeedy/plugin-react/package.json @@ -43,6 +43,7 @@ "@lynx-js/react-webpack-plugin": "workspace:*", "@lynx-js/runtime-wrapper-webpack-plugin": "workspace:*", "@lynx-js/template-webpack-plugin": "workspace:*", + "@lynx-js/use-sync-external-store": "workspace:*", "background-only": "workspace:^" }, "devDependencies": { diff --git a/packages/rspeedy/plugin-react/src/backgroundOnly.ts b/packages/rspeedy/plugin-react/src/backgroundOnly.ts index f8a385774e..768c34a669 100644 --- a/packages/rspeedy/plugin-react/src/backgroundOnly.ts +++ b/packages/rspeedy/plugin-react/src/backgroundOnly.ts @@ -2,11 +2,9 @@ // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import path from 'node:path' -import { fileURLToPath } from 'node:url' import type { RsbuildPluginAPI } from '@rsbuild/core' -import { createLazyResolver } from '@lynx-js/react-alias-rsbuild-plugin' import { LAYERS } from '@lynx-js/react-webpack-plugin' const DETECT_IMPORT_ERROR = 'react:detect-import-error' @@ -17,22 +15,13 @@ const ALIAS_BACKGROUND_ONLY_BACKGROUND = export function applyBackgroundOnly( api: RsbuildPluginAPI, ): void { - const __dirname = path.dirname(fileURLToPath(import.meta.url)) - - const backgroundResolve = createLazyResolver( - __dirname, - ['import'], - ) - const mainThreadResolve = createLazyResolver( - __dirname, - ['lepus'], - ) - api.modifyBundlerChain(async chain => { - const backgroundOnly = { - background: await backgroundResolve('background-only'), - mainThread: await mainThreadResolve('background-only'), - } + const { resolve, resolveMainThread } = await import('./resolve.js') + + const [backgroundOnly, backgroundOnlyMainThread] = await Promise.all([ + resolve('background-only'), + resolveMainThread('background-only'), + ]) chain .module @@ -42,7 +31,7 @@ export function applyBackgroundOnly( .alias .set( 'background-only$', - backgroundOnly.mainThread, + backgroundOnlyMainThread, ) chain @@ -53,13 +42,13 @@ export function applyBackgroundOnly( .alias .set( 'background-only$', - backgroundOnly.background, + backgroundOnly, ) chain .module .rule(DETECT_IMPORT_ERROR) - .test(backgroundOnly.mainThread) + .test(backgroundOnlyMainThread) .issuerLayer(LAYERS.MAIN_THREAD) .use(DETECT_IMPORT_ERROR) .loader(path.resolve(__dirname, 'loaders/invalid-import-error-loader')) diff --git a/packages/rspeedy/plugin-react/src/pluginReactLynx.ts b/packages/rspeedy/plugin-react/src/pluginReactLynx.ts index af90fd158b..857074f6d5 100644 --- a/packages/rspeedy/plugin-react/src/pluginReactLynx.ts +++ b/packages/rspeedy/plugin-react/src/pluginReactLynx.ts @@ -30,6 +30,7 @@ import { applyLoaders } from './loaders.js' import { applyRefresh } from './refresh.js' import { applySplitChunksRule } from './splitChunks.js' import { applySWC } from './swc.js' +import { applyUseSyncExternalStore } from './useSyncExternalStore.js' import { validateConfig } from './validate.js' /** @@ -374,6 +375,7 @@ export function pluginReactLynx( applyRefresh(api) applySplitChunksRule(api) applySWC(api) + applyUseSyncExternalStore(api) api.modifyRsbuildConfig((config, { mergeRsbuildConfig }) => { const userConfig = api.getRsbuildConfig('original') diff --git a/packages/rspeedy/plugin-react/src/resolve.ts b/packages/rspeedy/plugin-react/src/resolve.ts new file mode 100644 index 0000000000..aa23a57217 --- /dev/null +++ b/packages/rspeedy/plugin-react/src/resolve.ts @@ -0,0 +1,21 @@ +// Copyright 2025 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import { createLazyResolver } from '@lynx-js/react-alias-rsbuild-plugin' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +export const resolve: (request: string) => Promise = createLazyResolver( + __dirname, + ['import'], +) + +export const resolveMainThread: (request: string) => Promise = + createLazyResolver( + __dirname, + ['lepus'], + ) diff --git a/packages/rspeedy/plugin-react/src/useSyncExternalStore.ts b/packages/rspeedy/plugin-react/src/useSyncExternalStore.ts new file mode 100644 index 0000000000..407b31fedd --- /dev/null +++ b/packages/rspeedy/plugin-react/src/useSyncExternalStore.ts @@ -0,0 +1,28 @@ +// Copyright 2025 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +import type { RsbuildPluginAPI } from '@rsbuild/core' + +export function applyUseSyncExternalStore(api: RsbuildPluginAPI): void { + api.modifyBundlerChain(async chain => { + const { resolve } = await import('./resolve.js') + const useSyncExternalStoreEntries = [ + 'use-sync-external-store', + 'use-sync-external-store/with-selector', + 'use-sync-external-store/shim', + 'use-sync-external-store/shim/with-selector', + ] + + await Promise.all( + useSyncExternalStoreEntries.map(entry => + resolve(`@lynx-js/${entry}`).then(value => { + chain + .resolve + .alias + .set(`${entry}$`, value) + }) + ), + ) + }) +} diff --git a/packages/rspeedy/plugin-react/test/config.test.ts b/packages/rspeedy/plugin-react/test/config.test.ts index 91865d0d35..4a2e635080 100644 --- a/packages/rspeedy/plugin-react/test/config.test.ts +++ b/packages/rspeedy/plugin-react/test/config.test.ts @@ -122,6 +122,22 @@ describe('Config', () => { 'preact/compat/scheduler$', expect.stringContaining('/preact/compat/scheduler.mjs'), ) + expect(config.resolve.alias).toHaveProperty( + 'use-sync-external-store$', + expect.stringContaining('/use-sync-external-store/index.js'), + ) + expect(config.resolve.alias).toHaveProperty( + 'use-sync-external-store/with-selector$', + expect.stringContaining('/use-sync-external-store/with-selector.js'), + ) + expect(config.resolve.alias).toHaveProperty( + 'use-sync-external-store/shim$', + expect.stringContaining('/use-sync-external-store/index.js'), + ) + expect(config.resolve.alias).toHaveProperty( + 'use-sync-external-store/shim/with-selector$', + expect.stringContaining('/use-sync-external-store/with-selector.js'), + ) }) test('alias with production', async () => { @@ -176,6 +192,23 @@ describe('Config', () => { expect(config.resolve.alias).not.toHaveProperty( '@lynx-js/react/refresh$', ) + + expect(config.resolve.alias).toHaveProperty( + 'use-sync-external-store$', + expect.stringContaining('/use-sync-external-store/index.js'), + ) + expect(config.resolve.alias).toHaveProperty( + 'use-sync-external-store/with-selector$', + expect.stringContaining('/use-sync-external-store/with-selector.js'), + ) + expect(config.resolve.alias).toHaveProperty( + 'use-sync-external-store/shim$', + expect.stringContaining('/use-sync-external-store/index.js'), + ) + expect(config.resolve.alias).toHaveProperty( + 'use-sync-external-store/shim/with-selector$', + expect.stringContaining('/use-sync-external-store/with-selector.js'), + ) }) test('extensionAlias with tsConfig', async () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93e9d0174e..b136688305 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -418,6 +418,9 @@ importers: '@lynx-js/template-webpack-plugin': specifier: workspace:* version: link:../../webpack/template-webpack-plugin + '@lynx-js/use-sync-external-store': + specifier: workspace:* + version: link:../../use-sync-external-store background-only: specifier: workspace:^ version: link:../../background-only From 7e39752f35c5f8d3db8ad6c93fef6069ee30264c Mon Sep 17 00:00:00 2001 From: Qingyu Wang <40660121+colinaaa@users.noreply.github.com> Date: Tue, 3 Jun 2025 15:38:51 +0800 Subject: [PATCH 2/3] chore: changeset --- .changeset/small-onions-hear.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/small-onions-hear.md diff --git a/.changeset/small-onions-hear.md b/.changeset/small-onions-hear.md new file mode 100644 index 0000000000..97bc6bc0c8 --- /dev/null +++ b/.changeset/small-onions-hear.md @@ -0,0 +1,7 @@ +--- +"@lynx-js/react-rsbuild-plugin": patch +--- + +Better [zustand](https://github.com/pmndrs/zustand) support by creating an alias for `use-sync-external-store`. + +See [lynx-family/lynx-stack#893](https://github.com/lynx-family/lynx-stack/issues/893) for more details. From e7c79b78c30d8e1d2f1507819eeb91646ae79298 Mon Sep 17 00:00:00 2001 From: Qingyu Wang <40660121+colinaaa@users.noreply.github.com> Date: Tue, 3 Jun 2025 16:15:24 +0800 Subject: [PATCH 3/3] fix: missing `__dirname` --- packages/rspeedy/plugin-react/src/backgroundOnly.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/rspeedy/plugin-react/src/backgroundOnly.ts b/packages/rspeedy/plugin-react/src/backgroundOnly.ts index 768c34a669..adeb21c345 100644 --- a/packages/rspeedy/plugin-react/src/backgroundOnly.ts +++ b/packages/rspeedy/plugin-react/src/backgroundOnly.ts @@ -2,6 +2,7 @@ // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import path from 'node:path' +import { fileURLToPath } from 'node:url' import type { RsbuildPluginAPI } from '@rsbuild/core' @@ -16,6 +17,8 @@ export function applyBackgroundOnly( api: RsbuildPluginAPI, ): void { api.modifyBundlerChain(async chain => { + const __dirname = path.dirname(fileURLToPath(import.meta.url)) + const { resolve, resolveMainThread } = await import('./resolve.js') const [backgroundOnly, backgroundOnlyMainThread] = await Promise.all([