diff --git a/.changeset/thick-ideas-drum.md b/.changeset/thick-ideas-drum.md new file mode 100644 index 0000000000..d40bc08da1 --- /dev/null +++ b/.changeset/thick-ideas-drum.md @@ -0,0 +1,3 @@ +--- +"@lynx-js/react-rsbuild-plugin": patch +--- 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..8ff1f93e37 100644 --- a/packages/rspeedy/plugin-react/etc/react-rsbuild-plugin.api.md +++ b/packages/rspeedy/plugin-react/etc/react-rsbuild-plugin.api.md @@ -5,7 +5,9 @@ ```ts import { LAYERS } from '@lynx-js/react-webpack-plugin'; +import type { LynxTemplatePlugin as LynxTemplatePlugin_2 } from '@lynx-js/template-webpack-plugin'; import type { RsbuildPlugin } from '@rsbuild/core'; +import type { TemplateHooks } from '@lynx-js/template-webpack-plugin'; // @public export interface AddComponentElementConfig { @@ -46,6 +48,14 @@ export interface ExtractStrConfig { export { LAYERS } +// 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[]; @@ -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..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,4 +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, 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 b4ae22a386..0b2f102584 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,15 @@ export function pluginReactLynx( } api.expose(Symbol.for('LAYERS'), LAYERS) + // 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 new file mode 100644 index 0000000000..894604fe9f --- /dev/null +++ b/packages/rspeedy/plugin-react/test/expose.test.ts @@ -0,0 +1,94 @@ +// 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 { 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/index.js') + + let expose: { LynxTemplatePlugin: 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: 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< + 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": { + "getLynxTemplatePluginHooks": [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", + ] + `) + }) +}) 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