From 8291a27b4d3c8878f854c827c7701227c78bf065 Mon Sep 17 00:00:00 2001 From: Yiming Li Date: Wed, 24 Dec 2025 20:20:55 +0800 Subject: [PATCH 1/3] feat: expose `LynxTemplatePlugin` to rsbuild API --- .changeset/thick-ideas-drum.md | 29 ++++++ .../etc/react-rsbuild-plugin.api.md | 12 +++ packages/rspeedy/plugin-react/src/index.ts | 10 ++ .../plugin-react/src/pluginReactLynx.ts | 2 + .../rspeedy/plugin-react/test/expose.test.ts | 93 +++++++++++++++++++ 5 files changed, 146 insertions(+) create mode 100644 .changeset/thick-ideas-drum.md create mode 100644 packages/rspeedy/plugin-react/test/expose.test.ts diff --git a/.changeset/thick-ideas-drum.md b/.changeset/thick-ideas-drum.md new file mode 100644 index 0000000000..7afe30efe4 --- /dev/null +++ b/.changeset/thick-ideas-drum.md @@ -0,0 +1,29 @@ +--- +"@lynx-js/react-rsbuild-plugin": patch +--- + +Expose `LynxTemplatePlugin` to rsbuild API of key `Symbol.for('LynxTemplatePlugin')`. Usage: + +```ts +const pluginThatUsesTemplateHooks = { + name: 'pluginThatUsesTemplateHooks', + setup(api) { + const expose = api.useExposed(Symbol.for('LynxTemplatePlugin')); + api.modifyBundlerChain(chain => { + const PLUGIN_NAME = 'pluginThatUsesTemplateHooks'; + chain.plugin(PLUGIN_NAME).use({ + apply(compiler) { + compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => { + const templateHooks = expose.LynxTemplatePlugin + .getLynxTemplatePluginHooks(compilation); + templateHooks.beforeEncode.tapPromise(PLUGIN_NAME, async args => { + // ... plugin logic here + return args; + }); + }); + }, + }); + }); + }, +}; +``` diff --git a/packages/rspeedy/plugin-react/etc/react-rsbuild-plugin.api.md b/packages/rspeedy/plugin-react/etc/react-rsbuild-plugin.api.md index 4c2daec9c1..17fed27603 100644 --- a/packages/rspeedy/plugin-react/etc/react-rsbuild-plugin.api.md +++ b/packages/rspeedy/plugin-react/etc/react-rsbuild-plugin.api.md @@ -4,8 +4,12 @@ ```ts +import { EncodeOptions } from '@lynx-js/template-webpack-plugin'; import { LAYERS } from '@lynx-js/react-webpack-plugin'; +import { LynxTemplatePlugin } from '@lynx-js/template-webpack-plugin'; +import { LynxTemplatePluginOptions } from '@lynx-js/template-webpack-plugin'; import type { RsbuildPlugin } from '@rsbuild/core'; +import { TemplateHooks } from '@lynx-js/template-webpack-plugin'; // @public export interface AddComponentElementConfig { @@ -37,6 +41,8 @@ export interface DefineDceVisitorConfig { define: Record } +export { EncodeOptions } + // @public export interface ExtractStrConfig { // @internal (undocumented) @@ -46,6 +52,10 @@ export interface ExtractStrConfig { export { LAYERS } +export { LynxTemplatePlugin } + +export { LynxTemplatePluginOptions } + // @public export function pluginReactLynx(userOptions?: PluginReactLynxOptions): RsbuildPlugin[]; @@ -83,4 +93,6 @@ export interface ShakeVisitorConfig { retainProp: Array } +export { TemplateHooks } + ``` diff --git a/packages/rspeedy/plugin-react/src/index.ts b/packages/rspeedy/plugin-react/src/index.ts index 90896718e2..9f6d10ecb5 100644 --- a/packages/rspeedy/plugin-react/src/index.ts +++ b/packages/rspeedy/plugin-react/src/index.ts @@ -19,4 +19,14 @@ export type { ShakeVisitorConfig, } from '@lynx-js/react-transform' +// We only export types here +// It is encouraged to use `api.useExposed(Symbol.for('LynxTemplatePlugin'))` +// to access the actual API +export type { + LynxTemplatePlugin, + LynxTemplatePluginOptions, + TemplateHooks, + EncodeOptions, +} from '@lynx-js/template-webpack-plugin' + export { LAYERS } from '@lynx-js/react-webpack-plugin' diff --git a/packages/rspeedy/plugin-react/src/pluginReactLynx.ts b/packages/rspeedy/plugin-react/src/pluginReactLynx.ts index b4ae22a386..ad482a3e60 100644 --- a/packages/rspeedy/plugin-react/src/pluginReactLynx.ts +++ b/packages/rspeedy/plugin-react/src/pluginReactLynx.ts @@ -21,6 +21,7 @@ import type { } from '@lynx-js/react-transform' import { LAYERS } from '@lynx-js/react-webpack-plugin' import type { ExposedAPI } from '@lynx-js/rspeedy' +import { LynxTemplatePlugin } from '@lynx-js/template-webpack-plugin' import { applyBackgroundOnly } from './backgroundOnly.js' import { applyCSS } from './css.js' @@ -369,6 +370,7 @@ export function pluginReactLynx( } api.expose(Symbol.for('LAYERS'), LAYERS) + api.expose(Symbol.for('LynxTemplatePlugin'), { LynxTemplatePlugin }) const rspeedyAPIs = api.useExposed( Symbol.for('rspeedy.api'), diff --git a/packages/rspeedy/plugin-react/test/expose.test.ts b/packages/rspeedy/plugin-react/test/expose.test.ts new file mode 100644 index 0000000000..ab1c3827c4 --- /dev/null +++ b/packages/rspeedy/plugin-react/test/expose.test.ts @@ -0,0 +1,93 @@ +// 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 { RsbuildPlugin, Rspack } from '@rsbuild/core' +import { describe, expect, test } from 'vitest' + +import { createRspeedy } from '@lynx-js/rspeedy' + +import { pluginStubRspeedyAPI } from './stub-rspeedy-api.plugin.js' +import type { LynxTemplatePlugin, TemplateHooks } from '../src/index.js' + +describe('Expose', () => { + test('LynxTemplatePlugin', async () => { + const { pluginReactLynx } = await import('../src/pluginReactLynx.js') + + let expose: { LynxTemplatePlugin: typeof LynxTemplatePlugin } | undefined + let beforeEncodeArgs: + | Parameters[1]>[0] + | undefined + + const rsbuild = await createRspeedy({ + rspeedyConfig: { + source: { + entry: { + main: new URL('./fixtures/basic.tsx', import.meta.url).pathname, + }, + }, + plugins: [ + pluginReactLynx(), + pluginStubRspeedyAPI(), + { + name: 'pluginThatUsesTemplateHooks', + setup(api) { + expose = api.useExposed< + { LynxTemplatePlugin: typeof LynxTemplatePlugin } + >(Symbol.for('LynxTemplatePlugin')) + api.modifyBundlerChain(chain => { + const PLUGIN_NAME = 'pluginThatUsesTemplateHooks' + chain.plugin(PLUGIN_NAME).use({ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + compilation => { + const templateHooks = expose!.LynxTemplatePlugin + .getLynxTemplatePluginHooks( + compilation as unknown as Parameters< + typeof LynxTemplatePlugin.getLynxTemplatePluginHooks + >[0], + ) + templateHooks.beforeEncode.tap(PLUGIN_NAME, args => { + beforeEncodeArgs = args + return args + }) + }, + ) + }, + } as Rspack.RspackPluginInstance) + }) + }, + } as RsbuildPlugin, + ], + }, + }) + + expect(expose).toBeUndefined() + expect(beforeEncodeArgs).toBeUndefined() + + await rsbuild.initConfigs() + expect(expose).toMatchInlineSnapshot(` + { + "LynxTemplatePlugin": [Function], + } + `) + + await rsbuild.build() + + expect(Object.keys(beforeEncodeArgs!.encodeData.lepusCode)) + .toMatchInlineSnapshot(` + [ + "root", + "chunks", + "filename", + ] + `) + expect(Object.keys(beforeEncodeArgs!.encodeData.manifest)) + .toMatchInlineSnapshot(` + [ + "/app-service.js", + "/.rspeedy/main/background.js", + ] + `) + }) +}) From c289f836f6dedd812311c5189e1783dc75a34ffc Mon Sep 17 00:00:00 2001 From: Yiming Li Date: Thu, 25 Dec 2025 11:11:34 +0800 Subject: [PATCH 2/3] test: fix createRspeedy path issue --- packages/rspeedy/plugin-react/test/expose.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/rspeedy/plugin-react/test/expose.test.ts b/packages/rspeedy/plugin-react/test/expose.test.ts index ab1c3827c4..9cc3ceb743 100644 --- a/packages/rspeedy/plugin-react/test/expose.test.ts +++ b/packages/rspeedy/plugin-react/test/expose.test.ts @@ -4,14 +4,13 @@ import type { RsbuildPlugin, Rspack } from '@rsbuild/core' import { describe, expect, test } from 'vitest' -import { createRspeedy } from '@lynx-js/rspeedy' - +import { createStubRspeedy as createRspeedy } from './createRspeedy.js' import { pluginStubRspeedyAPI } from './stub-rspeedy-api.plugin.js' import type { LynxTemplatePlugin, TemplateHooks } from '../src/index.js' describe('Expose', () => { test('LynxTemplatePlugin', async () => { - const { pluginReactLynx } = await import('../src/pluginReactLynx.js') + const { pluginReactLynx } = await import('../src/index.js') let expose: { LynxTemplatePlugin: typeof LynxTemplatePlugin } | undefined let beforeEncodeArgs: From b486a4f400db4d21942632e6cc330f0c71565933 Mon Sep 17 00:00:00 2001 From: Yiming Li Date: Fri, 26 Dec 2025 11:50:22 +0800 Subject: [PATCH 3/3] feat: only expose getLynxTemplatePluginHooks to user --- .changeset/thick-ideas-drum.md | 26 ------------------- .../etc/react-rsbuild-plugin.api.md | 18 ++++++------- packages/rspeedy/plugin-react/src/index.ts | 17 +++++++----- .../plugin-react/src/pluginReactLynx.ts | 10 ++++++- .../rspeedy/plugin-react/test/expose.test.ts | 10 ++++--- .../src/LynxTemplatePlugin.ts | 2 +- 6 files changed, 36 insertions(+), 47 deletions(-) diff --git a/.changeset/thick-ideas-drum.md b/.changeset/thick-ideas-drum.md index 7afe30efe4..d40bc08da1 100644 --- a/.changeset/thick-ideas-drum.md +++ b/.changeset/thick-ideas-drum.md @@ -1,29 +1,3 @@ --- "@lynx-js/react-rsbuild-plugin": patch --- - -Expose `LynxTemplatePlugin` to rsbuild API of key `Symbol.for('LynxTemplatePlugin')`. Usage: - -```ts -const pluginThatUsesTemplateHooks = { - name: 'pluginThatUsesTemplateHooks', - setup(api) { - const expose = api.useExposed(Symbol.for('LynxTemplatePlugin')); - api.modifyBundlerChain(chain => { - const PLUGIN_NAME = 'pluginThatUsesTemplateHooks'; - chain.plugin(PLUGIN_NAME).use({ - apply(compiler) { - compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => { - const templateHooks = expose.LynxTemplatePlugin - .getLynxTemplatePluginHooks(compilation); - templateHooks.beforeEncode.tapPromise(PLUGIN_NAME, async args => { - // ... plugin logic here - return args; - }); - }); - }, - }); - }); - }, -}; -``` diff --git a/packages/rspeedy/plugin-react/etc/react-rsbuild-plugin.api.md b/packages/rspeedy/plugin-react/etc/react-rsbuild-plugin.api.md index 17fed27603..8ff1f93e37 100644 --- a/packages/rspeedy/plugin-react/etc/react-rsbuild-plugin.api.md +++ b/packages/rspeedy/plugin-react/etc/react-rsbuild-plugin.api.md @@ -4,12 +4,10 @@ ```ts -import { EncodeOptions } from '@lynx-js/template-webpack-plugin'; import { LAYERS } from '@lynx-js/react-webpack-plugin'; -import { LynxTemplatePlugin } from '@lynx-js/template-webpack-plugin'; -import { LynxTemplatePluginOptions } from '@lynx-js/template-webpack-plugin'; +import type { LynxTemplatePlugin as LynxTemplatePlugin_2 } from '@lynx-js/template-webpack-plugin'; import type { RsbuildPlugin } from '@rsbuild/core'; -import { TemplateHooks } from '@lynx-js/template-webpack-plugin'; +import type { TemplateHooks } from '@lynx-js/template-webpack-plugin'; // @public export interface AddComponentElementConfig { @@ -41,8 +39,6 @@ export interface DefineDceVisitorConfig { define: Record } -export { EncodeOptions } - // @public export interface ExtractStrConfig { // @internal (undocumented) @@ -52,9 +48,13 @@ export interface ExtractStrConfig { export { LAYERS } -export { LynxTemplatePlugin } - -export { LynxTemplatePluginOptions } +// Warning: (ae-missing-release-tag) "LynxTemplatePlugin" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface LynxTemplatePlugin { + // (undocumented) + getLynxTemplatePluginHooks: typeof LynxTemplatePlugin_2.getLynxTemplatePluginHooks; +} // @public export function pluginReactLynx(userOptions?: PluginReactLynxOptions): RsbuildPlugin[]; diff --git a/packages/rspeedy/plugin-react/src/index.ts b/packages/rspeedy/plugin-react/src/index.ts index 9f6d10ecb5..ad6b3e3360 100644 --- a/packages/rspeedy/plugin-react/src/index.ts +++ b/packages/rspeedy/plugin-react/src/index.ts @@ -8,6 +8,11 @@ * A rsbuild plugin that integrates with ReactLynx. */ +import type { + LynxTemplatePlugin as InnerLynxTemplatePlugin, + TemplateHooks, +} from '@lynx-js/template-webpack-plugin' + export { pluginReactLynx } from './pluginReactLynx.js' export type { PluginReactLynxOptions } from './pluginReactLynx.js' @@ -19,14 +24,14 @@ export type { ShakeVisitorConfig, } from '@lynx-js/react-transform' +interface LynxTemplatePlugin { + getLynxTemplatePluginHooks: + typeof InnerLynxTemplatePlugin.getLynxTemplatePluginHooks +} + // We only export types here // It is encouraged to use `api.useExposed(Symbol.for('LynxTemplatePlugin'))` // to access the actual API -export type { - LynxTemplatePlugin, - LynxTemplatePluginOptions, - TemplateHooks, - EncodeOptions, -} from '@lynx-js/template-webpack-plugin' +export type { LynxTemplatePlugin, TemplateHooks } export { LAYERS } from '@lynx-js/react-webpack-plugin' diff --git a/packages/rspeedy/plugin-react/src/pluginReactLynx.ts b/packages/rspeedy/plugin-react/src/pluginReactLynx.ts index ad482a3e60..0b2f102584 100644 --- a/packages/rspeedy/plugin-react/src/pluginReactLynx.ts +++ b/packages/rspeedy/plugin-react/src/pluginReactLynx.ts @@ -370,7 +370,15 @@ export function pluginReactLynx( } api.expose(Symbol.for('LAYERS'), LAYERS) - api.expose(Symbol.for('LynxTemplatePlugin'), { LynxTemplatePlugin }) + // Only expose `LynxTemplatePlugin.getLynxTemplatePluginHooks` to avoid + // other breaking changes in `LynxTemplatePlugin` + // breaks `pluginReactLynx` + api.expose(Symbol.for('LynxTemplatePlugin'), { + LynxTemplatePlugin: { + getLynxTemplatePluginHooks: LynxTemplatePlugin + .getLynxTemplatePluginHooks.bind(LynxTemplatePlugin), + }, + }) const rspeedyAPIs = api.useExposed( Symbol.for('rspeedy.api'), diff --git a/packages/rspeedy/plugin-react/test/expose.test.ts b/packages/rspeedy/plugin-react/test/expose.test.ts index 9cc3ceb743..894604fe9f 100644 --- a/packages/rspeedy/plugin-react/test/expose.test.ts +++ b/packages/rspeedy/plugin-react/test/expose.test.ts @@ -12,7 +12,7 @@ describe('Expose', () => { test('LynxTemplatePlugin', async () => { const { pluginReactLynx } = await import('../src/index.js') - let expose: { LynxTemplatePlugin: typeof LynxTemplatePlugin } | undefined + let expose: { LynxTemplatePlugin: LynxTemplatePlugin } | undefined let beforeEncodeArgs: | Parameters[1]>[0] | undefined @@ -31,7 +31,7 @@ describe('Expose', () => { name: 'pluginThatUsesTemplateHooks', setup(api) { expose = api.useExposed< - { LynxTemplatePlugin: typeof LynxTemplatePlugin } + { LynxTemplatePlugin: LynxTemplatePlugin } >(Symbol.for('LynxTemplatePlugin')) api.modifyBundlerChain(chain => { const PLUGIN_NAME = 'pluginThatUsesTemplateHooks' @@ -43,7 +43,7 @@ describe('Expose', () => { const templateHooks = expose!.LynxTemplatePlugin .getLynxTemplatePluginHooks( compilation as unknown as Parameters< - typeof LynxTemplatePlugin.getLynxTemplatePluginHooks + LynxTemplatePlugin['getLynxTemplatePluginHooks'] >[0], ) templateHooks.beforeEncode.tap(PLUGIN_NAME, args => { @@ -67,7 +67,9 @@ describe('Expose', () => { await rsbuild.initConfigs() expect(expose).toMatchInlineSnapshot(` { - "LynxTemplatePlugin": [Function], + "LynxTemplatePlugin": { + "getLynxTemplatePluginHooks": [Function], + }, } `) diff --git a/packages/webpack/template-webpack-plugin/src/LynxTemplatePlugin.ts b/packages/webpack/template-webpack-plugin/src/LynxTemplatePlugin.ts index 3e22c473b6..570cb80712 100644 --- a/packages/webpack/template-webpack-plugin/src/LynxTemplatePlugin.ts +++ b/packages/webpack/template-webpack-plugin/src/LynxTemplatePlugin.ts @@ -64,7 +64,7 @@ const LynxTemplatePluginHooksMap = new WeakMap(); * compiler.hooks.compilation.tap("MyPlugin", (compilation) => { * console.log("The compiler is starting a new compilation..."); * - * LynxTemplatePlugin.getCompilationHooks(compilation).beforeEmit.tapAsync( + * LynxTemplatePlugin.getLynxTemplatePluginHooks(compilation).beforeEmit.tapAsync( * "MyPlugin", // <-- Set a meaningful name here for stacktraces * (data, cb) => { * // Manipulate the content