From e59715741c5e2ed2f884212189547c9aef1f40ec Mon Sep 17 00:00:00 2001 From: BitterGourd <91231822+gaoachao@users.noreply.github.com> Date: Wed, 21 May 2025 16:27:31 +0800 Subject: [PATCH 1/6] feat: support output.inlineScripts --- .changeset/afraid-meals-attend.md | 19 ++++++++ packages/rspeedy/core/etc/rspeedy.api.md | 1 + packages/rspeedy/core/src/config/defaults.ts | 3 ++ .../rspeedy/core/src/config/output/index.ts | 24 ++++++++++ .../rspeedy/core/test/config/output.test-d.ts | 9 ++++ .../rspeedy/core/test/config/validate.test.ts | 12 +++++ packages/rspeedy/plugin-react/src/entry.ts | 6 ++- .../etc/template-webpack-plugin.api.md | 4 ++ .../src/LynxEncodePlugin.ts | 40 +++++++++++----- .../test/cases/inline-scripts/external/foo.js | 3 ++ .../cases/inline-scripts/external/index.js | 46 +++++++++++++++++++ .../inline-scripts/external/rspack.config.js | 33 +++++++++++++ .../test/cases/inline-scripts/inline/foo.js | 3 ++ .../test/cases/inline-scripts/inline/index.js | 37 +++++++++++++++ .../inline-scripts/inline/rspack.config.js | 33 +++++++++++++ 15 files changed, 261 insertions(+), 12 deletions(-) create mode 100644 .changeset/afraid-meals-attend.md create mode 100644 packages/webpack/template-webpack-plugin/test/cases/inline-scripts/external/foo.js create mode 100644 packages/webpack/template-webpack-plugin/test/cases/inline-scripts/external/index.js create mode 100644 packages/webpack/template-webpack-plugin/test/cases/inline-scripts/external/rspack.config.js create mode 100644 packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline/foo.js create mode 100644 packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline/index.js create mode 100644 packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline/rspack.config.js diff --git a/.changeset/afraid-meals-attend.md b/.changeset/afraid-meals-attend.md new file mode 100644 index 0000000000..496526edad --- /dev/null +++ b/.changeset/afraid-meals-attend.md @@ -0,0 +1,19 @@ +--- +"@lynx-js/template-webpack-plugin": patch +"@lynx-js/react-rsbuild-plugin": patch +"@lynx-js/rspeedy": patch +--- + +Support `output.inlineScripts`, which controls whether to inline scripts files when LynxEncodePlugin generates the manifest file. + +example: + +```js +import { defineConfig } from '@lynx-js/rspeedy'; + +export default defineConfig({ + output: { + inlineScripts: false, + }, +}); +``` diff --git a/packages/rspeedy/core/etc/rspeedy.api.md b/packages/rspeedy/core/etc/rspeedy.api.md index 4a93450508..03bcbaf0bd 100644 --- a/packages/rspeedy/core/etc/rspeedy.api.md +++ b/packages/rspeedy/core/etc/rspeedy.api.md @@ -232,6 +232,7 @@ export interface Output { distPath?: DistPath | undefined; filename?: string | Filename | undefined; filenameHash?: boolean | string | undefined; + inlineScripts?: boolean | undefined; legalComments?: 'none' | 'inline' | 'linked' | undefined; minify?: Minify | boolean | undefined; sourceMap?: boolean | SourceMap | undefined; diff --git a/packages/rspeedy/core/src/config/defaults.ts b/packages/rspeedy/core/src/config/defaults.ts index e387826ea1..1c1ef870b6 100644 --- a/packages/rspeedy/core/src/config/defaults.ts +++ b/packages/rspeedy/core/src/config/defaults.ts @@ -14,6 +14,9 @@ export function applyDefaultRspeedyConfig(config: Config): Config { // since some plugin(e.g.: `@lynx-js/qrcode-rsbuild-plugin`) will read // from the `output.filename.bundle` field. filename: getFilename(config.output?.filename), + + // inlineScripts should be enabled by default + inlineScripts: true, }, tools: { diff --git a/packages/rspeedy/core/src/config/output/index.ts b/packages/rspeedy/core/src/config/output/index.ts index 6b9b6e794e..1005473737 100644 --- a/packages/rspeedy/core/src/config/output/index.ts +++ b/packages/rspeedy/core/src/config/output/index.ts @@ -299,6 +299,30 @@ export interface Output { */ filenameHash?: boolean | string | undefined + /** + * The {@link Output.inlineScripts} option controls whether to inline scripts files when LynxEncodePlugin generates the manifest file. + * + * @remarks + * + * If no value is provided, the default value would be `true`. + * + * This is different with {@link https://rsbuild.dev/config/output/inline-scripts | output.inlineScripts } since we normally want to inline scripts in Lynx manifest file. + * + * @example + * + * Disable inlining scripts. + * ```js + * import { defineConfig } from '@lynx-js/rspeedy' + * + * export default defineConfig({ + * output: { + * inlineScripts: false, + * }, + * }) + * ``` + */ + inlineScripts?: boolean | undefined + /** * The {@link Output.legalComments} controls how to handle the legal comment. * diff --git a/packages/rspeedy/core/test/config/output.test-d.ts b/packages/rspeedy/core/test/config/output.test-d.ts index 32c5839bd6..3db9b06a53 100644 --- a/packages/rspeedy/core/test/config/output.test-d.ts +++ b/packages/rspeedy/core/test/config/output.test-d.ts @@ -175,6 +175,15 @@ describe('Config - Output', () => { }) }) + test('output.inlineScripts', () => { + assertType({ + inlineScripts: true, + }) + assertType({ + inlineScripts: false, + }) + }) + test('output.legalComments', () => { assertType({ legalComments: 'inline', diff --git a/packages/rspeedy/core/test/config/validate.test.ts b/packages/rspeedy/core/test/config/validate.test.ts index c53122907c..160ab75a1d 100644 --- a/packages/rspeedy/core/test/config/validate.test.ts +++ b/packages/rspeedy/core/test/config/validate.test.ts @@ -833,6 +833,8 @@ describe('Config Validation', () => { { distPath: { image: 'image' } }, { distPath: { font: 'font' } }, { distPath: { svg: 'svg' } }, + { inlineScripts: true }, + { inlineScripts: false }, { legalComments: 'inline' }, { legalComments: 'none' }, { legalComments: 'linked' }, @@ -1097,6 +1099,16 @@ describe('Config Validation', () => { ] `) + expect(() => validate({ output: { inlineScripts: null } })) + .toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid configuration. + + Invalid config on \`$input.output.inlineScripts\`. + - Expect to be (boolean | undefined) + - Got: null + ] + `) + expect(() => validate({ output: { legalComments: [null] } })) .toThrowErrorMatchingInlineSnapshot(` [Error: Invalid configuration. diff --git a/packages/rspeedy/plugin-react/src/entry.ts b/packages/rspeedy/plugin-react/src/entry.ts index 12ea3a2ab7..4f1e42f100 100644 --- a/packages/rspeedy/plugin-react/src/entry.ts +++ b/packages/rspeedy/plugin-react/src/entry.ts @@ -198,6 +198,10 @@ export function applyEntry( }) if (isLynx) { + const inlineScripts = typeof config.output?.inlineScripts === 'boolean' + ? config.output.inlineScripts + : true + chain .plugin(PLUGIN_NAME_RUNTIME_WRAPPER) .use(RuntimeWrapperWebpackPlugin, [{ @@ -222,7 +226,7 @@ export function applyEntry( }]) .end() .plugin(`${LynxEncodePlugin.name}`) - .use(LynxEncodePlugin, [{}]) + .use(LynxEncodePlugin, [{ inlineScripts }]) .end() } diff --git a/packages/webpack/template-webpack-plugin/etc/template-webpack-plugin.api.md b/packages/webpack/template-webpack-plugin/etc/template-webpack-plugin.api.md index 00a885e3b9..67edd3ffed 100644 --- a/packages/webpack/template-webpack-plugin/etc/template-webpack-plugin.api.md +++ b/packages/webpack/template-webpack-plugin/etc/template-webpack-plugin.api.md @@ -78,6 +78,10 @@ export class LynxEncodePlugin { // @public export interface LynxEncodePluginOptions { + // (undocumented) + encodeBinary?: string; + // (undocumented) + inlineScripts?: boolean; } // @public diff --git a/packages/webpack/template-webpack-plugin/src/LynxEncodePlugin.ts b/packages/webpack/template-webpack-plugin/src/LynxEncodePlugin.ts index 952894a2f0..5d5fb0fb4b 100644 --- a/packages/webpack/template-webpack-plugin/src/LynxEncodePlugin.ts +++ b/packages/webpack/template-webpack-plugin/src/LynxEncodePlugin.ts @@ -14,8 +14,10 @@ import type { CSS } from './index.js'; * * @public */ -// biome-ignore lint/suspicious/noEmptyInterface: As expected. -export interface LynxEncodePluginOptions {} +export interface LynxEncodePluginOptions { + encodeBinary?: string; + inlineScripts?: boolean; +} /** * LynxEncodePlugin @@ -92,6 +94,7 @@ export class LynxEncodePlugin { static defaultOptions: Readonly> = Object .freeze>({ encodeBinary: 'napi', + inlineScripts: true, }); /** * The entry point of a webpack plugin. @@ -146,6 +149,19 @@ export class LynxEncodePluginImpl { const { encodeData } = args; const { manifest } = encodeData; + let publicPath = '/'; + if (!this.options.inlineScripts) { + if (typeof compilation?.outputOptions.publicPath === 'function') { + compilation.errors.push( + new compiler.webpack.WebpackError( + '`publicPath` as a function is not supported yet.', + ), + ); + } else { + publicPath = compilation?.outputOptions.publicPath ?? '/'; + } + } + if (!isDebug() && !isDev && !isRsdoctor()) { [ encodeData.lepusCode.root, @@ -178,18 +194,20 @@ export class LynxEncodePluginImpl { Object.keys(manifest) .map((name) => `module.exports=lynx.requireModule('${ - this.#formatJSName(name) + this.#formatJSName(name, publicPath) }',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')` ) .join(','), this.#appServiceFooter(), ].join(''), - ...(Object.fromEntries( - Object.entries(manifest).map(([name, source]) => [ - this.#formatJSName(name), - source, - ]), - )), + ...(this.options.inlineScripts + ? Object.fromEntries( + Object.entries(manifest).map(([name, source]) => [ + this.#formatJSName(name, publicPath), + source, + ]), + ) + : {}), }; return args; @@ -230,8 +248,8 @@ export class LynxEncodePluginImpl { return amdFooter + loadScriptFooter; } - #formatJSName(name: string): string { - return `/${name}`; + #formatJSName(name: string, publicPath: string): string { + return publicPath + name; } protected options: Required; diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/external/foo.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/external/foo.js new file mode 100644 index 0000000000..5775bf0191 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/external/foo.js @@ -0,0 +1,3 @@ +export function foo() { + return 42; +} diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/external/index.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/external/index.js new file mode 100644 index 0000000000..f077b8f24d --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/external/index.js @@ -0,0 +1,46 @@ +/* +// Copyright 2024 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 { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; + +it('should have correct chunk content', async () => { + const { foo } = await import( + /* webpackChunkName: 'foo:main-thread' */ + './foo.js' + ); + expect(foo()).toBe(42); + + const fooBackground = await import( + /* webpackChunkName: 'foo:background' */ + './foo.js' + ); + expect(fooBackground.foo()).toBe(42); +}); + +it('manifest only contains /app-service.js', async () => { + const tasmJSONPath = resolve(__dirname, '.rspeedy/async/foo/tasm.json'); + expect(existsSync(tasmJSONPath)).toBeTruthy(); + + const content = await readFile(tasmJSONPath, 'utf-8'); + const { sourceContent, manifest } = JSON.parse(content); + const output = resolve(__dirname, 'foo:background.rspack.bundle.js'); + expect(existsSync(output)); + + const outputContent = await readFile(output, 'utf-8'); + expect(outputContent).toContain(['function', 'foo()'].join(' ')); + + expect(sourceContent).toHaveProperty('appType', 'DynamicComponent'); + + expect(manifest).not.toHaveProperty('/foo:background.rspack.bundle.js'); + expect(manifest).toHaveProperty('/app-service.js'); + + expect(manifest['/app-service.js']).toContain( + `lynx.requireModule('/foo:background.rspack.bundle.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`, + ); +}); diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/external/rspack.config.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/external/rspack.config.js new file mode 100644 index 0000000000..f889279c21 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/external/rspack.config.js @@ -0,0 +1,33 @@ +import { LynxEncodePlugin, LynxTemplatePlugin } from '../../../../src'; + +/** @type {import('@rspack/core').Configuration} */ +export default { + devtool: false, + mode: 'development', + plugins: [ + new LynxEncodePlugin({ + inlineScripts: false, + }), + new LynxTemplatePlugin({ + ...LynxTemplatePlugin.defaultOptions, + intermediate: '.rspeedy/main', + }), + /** + * @param {import('@rspack/core').Compiler} compiler - Rspack Compiler + */ + (compiler) => { + compiler.hooks.thisCompilation.tap('test', (compilation) => { + const hooks = LynxTemplatePlugin.getLynxTemplatePluginHooks( + compilation, + ); + hooks.asyncChunkName.tap( + 'test', + chunkName => + chunkName + .replace(':main-thread', '') + .replace(':background', ''), + ); + }); + }, + ], +}; diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline/foo.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline/foo.js new file mode 100644 index 0000000000..5775bf0191 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline/foo.js @@ -0,0 +1,3 @@ +export function foo() { + return 42; +} diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline/index.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline/index.js new file mode 100644 index 0000000000..168236f1d2 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline/index.js @@ -0,0 +1,37 @@ +/* +// Copyright 2024 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 { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; + +it('should have correct chunk content', async () => { + const { foo } = await import( + /* webpackChunkName: 'foo:main-thread' */ + './foo.js' + ); + expect(foo()).toBe(42); + + const fooBackground = await import( + /* webpackChunkName: 'foo:background' */ + './foo.js' + ); + expect(fooBackground.foo()).toBe(42); +}); + +it('should generate correct foo template', async () => { + const tasmJSONPath = resolve(__dirname, '.rspeedy/async/foo/tasm.json'); + expect(existsSync(tasmJSONPath)).toBeTruthy(); + + const content = await readFile(tasmJSONPath, 'utf-8'); + const { sourceContent, manifest } = JSON.parse(content); + expect(sourceContent).toHaveProperty('appType', 'DynamicComponent'); + expect(manifest).toHaveProperty( + '/foo:background.rspack.bundle.js', + expect.stringContaining('function foo()'), + ); +}); diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline/rspack.config.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline/rspack.config.js new file mode 100644 index 0000000000..93b6e8b86e --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline/rspack.config.js @@ -0,0 +1,33 @@ +import { LynxEncodePlugin, LynxTemplatePlugin } from '../../../../src'; + +/** @type {import('@rspack/core').Configuration} */ +export default { + devtool: false, + mode: 'development', + plugins: [ + new LynxEncodePlugin({ + inlineScripts: true, + }), + new LynxTemplatePlugin({ + ...LynxTemplatePlugin.defaultOptions, + intermediate: '.rspeedy/main', + }), + /** + * @param {import('@rspack/core').Compiler} compiler - Rspack Compiler + */ + (compiler) => { + compiler.hooks.thisCompilation.tap('test', (compilation) => { + const hooks = LynxTemplatePlugin.getLynxTemplatePluginHooks( + compilation, + ); + hooks.asyncChunkName.tap( + 'test', + chunkName => + chunkName + .replace(':main-thread', '') + .replace(':background', ''), + ); + }); + }, + ], +}; From 57b7b2263fe4b1646079dbbf3aeb498584d5bd0b Mon Sep 17 00:00:00 2001 From: BitterGourd <91231822+gaoachao@users.noreply.github.com> Date: Wed, 21 May 2025 19:46:59 +0800 Subject: [PATCH 2/6] Update .changeset/afraid-meals-attend.md Co-authored-by: Qingyu Wang <40660121+colinaaa@users.noreply.github.com> Signed-off-by: BitterGourd <91231822+gaoachao@users.noreply.github.com> --- .changeset/afraid-meals-attend.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.changeset/afraid-meals-attend.md b/.changeset/afraid-meals-attend.md index 496526edad..44ffb80400 100644 --- a/.changeset/afraid-meals-attend.md +++ b/.changeset/afraid-meals-attend.md @@ -4,7 +4,9 @@ "@lynx-js/rspeedy": patch --- -Support `output.inlineScripts`, which controls whether to inline scripts files when LynxEncodePlugin generates the manifest file. +Support `output.inlineScripts`, which controls whether to inline scripts into Lynx bundle (`.lynx.bundle`). + +Only background thread scripts can remain non-inlined, whereas the main thread script is always inlined. example: From 1b3a058fc6bb97884eeb5f5649aa0839b0959eb1 Mon Sep 17 00:00:00 2001 From: BitterGourd <91231822+gaoachao@users.noreply.github.com> Date: Wed, 21 May 2025 19:47:15 +0800 Subject: [PATCH 3/6] Update packages/rspeedy/core/src/config/output/index.ts Co-authored-by: Qingyu Wang <40660121+colinaaa@users.noreply.github.com> Signed-off-by: BitterGourd <91231822+gaoachao@users.noreply.github.com> --- packages/rspeedy/core/src/config/output/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rspeedy/core/src/config/output/index.ts b/packages/rspeedy/core/src/config/output/index.ts index 1005473737..2b2a49308b 100644 --- a/packages/rspeedy/core/src/config/output/index.ts +++ b/packages/rspeedy/core/src/config/output/index.ts @@ -300,7 +300,7 @@ export interface Output { filenameHash?: boolean | string | undefined /** - * The {@link Output.inlineScripts} option controls whether to inline scripts files when LynxEncodePlugin generates the manifest file. + * The {@link Output.inlineScripts} option controls whether to inline scripts into Lynx bundle (`.lynx.bundle`). * * @remarks * From c88998c25bce66676c63c2296b7aaf839e0247e1 Mon Sep 17 00:00:00 2001 From: BitterGourd <91231822+gaoachao@users.noreply.github.com> Date: Wed, 21 May 2025 19:49:23 +0800 Subject: [PATCH 4/6] Update docs --- packages/rspeedy/core/src/config/output/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/rspeedy/core/src/config/output/index.ts b/packages/rspeedy/core/src/config/output/index.ts index 2b2a49308b..507ea8e4d2 100644 --- a/packages/rspeedy/core/src/config/output/index.ts +++ b/packages/rspeedy/core/src/config/output/index.ts @@ -306,7 +306,9 @@ export interface Output { * * If no value is provided, the default value would be `true`. * - * This is different with {@link https://rsbuild.dev/config/output/inline-scripts | output.inlineScripts } since we normally want to inline scripts in Lynx manifest file. + * This is different with {@link https://rsbuild.dev/config/output/inline-scripts | output.inlineScripts } since we normally want to inline scripts in Lynx bundle (`.lynx.bundle`). + * + * Only background thread scripts can remain non-inlined, whereas the main thread script is always inlined. * * @example * From 260ba2891272802f4cca857b066189e17ce59054 Mon Sep 17 00:00:00 2001 From: BitterGourd <91231822+gaoachao@users.noreply.github.com> Date: Wed, 21 May 2025 19:50:06 +0800 Subject: [PATCH 5/6] Update docs --- packages/rspeedy/core/src/config/output/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rspeedy/core/src/config/output/index.ts b/packages/rspeedy/core/src/config/output/index.ts index 507ea8e4d2..82d8f66d2c 100644 --- a/packages/rspeedy/core/src/config/output/index.ts +++ b/packages/rspeedy/core/src/config/output/index.ts @@ -312,7 +312,7 @@ export interface Output { * * @example * - * Disable inlining scripts. + * Disable inlining background thread scripts. * ```js * import { defineConfig } from '@lynx-js/rspeedy' * From a56169a8954cfd3618cced590fb17d7fa317ffb6 Mon Sep 17 00:00:00 2001 From: BitterGourd <91231822+gaoachao@users.noreply.github.com> Date: Wed, 21 May 2025 20:33:10 +0800 Subject: [PATCH 6/6] Adopt environment.config --- packages/rspeedy/plugin-react/src/entry.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/rspeedy/plugin-react/src/entry.ts b/packages/rspeedy/plugin-react/src/entry.ts index 4f1e42f100..bdda96ed15 100644 --- a/packages/rspeedy/plugin-react/src/entry.ts +++ b/packages/rspeedy/plugin-react/src/entry.ts @@ -198,9 +198,10 @@ export function applyEntry( }) if (isLynx) { - const inlineScripts = typeof config.output?.inlineScripts === 'boolean' - ? config.output.inlineScripts - : true + const inlineScripts = + typeof environment.config.output?.inlineScripts === 'boolean' + ? environment.config.output.inlineScripts + : true chain .plugin(PLUGIN_NAME_RUNTIME_WRAPPER)