diff --git a/.changeset/tangy-ends-act.md b/.changeset/tangy-ends-act.md new file mode 100644 index 0000000000..129d1b207e --- /dev/null +++ b/.changeset/tangy-ends-act.md @@ -0,0 +1,34 @@ +--- +"@lynx-js/rspeedy": patch +"@lynx-js/react-rsbuild-plugin": patch +"@lynx-js/react-webpack-plugin": patch +"@lynx-js/template-webpack-plugin": patch +--- + +Enable fine-grained control for `output.inlineScripts` + +```ts +type InlineChunkTestFunction = (params: { + size: number; + name: string; +}) => boolean; + +type InlineChunkTest = RegExp | InlineChunkTestFunction; + +type InlineChunkConfig = + | boolean + | InlineChunkTest + | { enable?: boolean | 'auto'; test: InlineChunkTest }; +``` + +```ts +import { defineConfig } from '@lynx-js/rspeedy'; + +export default defineConfig({ + output: { + inlineScripts: ({ name, size }) => { + return name.includes('foo') && size < 1000; + }, + }, +}); +``` diff --git a/packages/rspeedy/core/etc/rspeedy.api.md b/packages/rspeedy/core/etc/rspeedy.api.md index 03bcbaf0bd..84fa67ea71 100644 --- a/packages/rspeedy/core/etc/rspeedy.api.md +++ b/packages/rspeedy/core/etc/rspeedy.api.md @@ -6,6 +6,7 @@ import type { CreateRsbuildOptions } from '@rsbuild/core'; import type { DistPathConfig } from '@rsbuild/core'; +import type { InlineChunkConfig } from '@rsbuild/core'; import { logger } from '@rsbuild/core'; import type { PerformanceConfig } from '@rsbuild/core'; import type { RsbuildConfig } from '@rsbuild/core'; @@ -232,7 +233,7 @@ export interface Output { distPath?: DistPath | undefined; filename?: string | Filename | undefined; filenameHash?: boolean | string | undefined; - inlineScripts?: boolean | undefined; + inlineScripts?: InlineChunkConfig | undefined; legalComments?: 'none' | 'inline' | 'linked' | undefined; minify?: Minify | boolean | undefined; sourceMap?: boolean | SourceMap | undefined; diff --git a/packages/rspeedy/core/src/config/output/index.ts b/packages/rspeedy/core/src/config/output/index.ts index 82d8f66d2c..1180be1186 100644 --- a/packages/rspeedy/core/src/config/output/index.ts +++ b/packages/rspeedy/core/src/config/output/index.ts @@ -1,7 +1,7 @@ // 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 type { Rspack } from '@rsbuild/core' +import type { InlineChunkConfig, Rspack } from '@rsbuild/core' import type { CssModules } from './css-modules.js' import type { DistPath } from './dist-path.js' @@ -304,11 +304,15 @@ export interface Output { * * @remarks * - * If no value is provided, the default value would be `true`. + * If no value is provided, the default value would be `true`, which means all background thread scripts will be inlined. * * 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. + * There are two points that need to be especially noted: + * + * 1. Only background thread scripts can remain non-inlined, whereas the main thread script is always inlined. + * + * 2. Currently, when `experimental_isLazyBundle` is enabled, `inlineScripts` will always be `true`. * * @example * @@ -323,7 +327,7 @@ export interface Output { * }) * ``` */ - inlineScripts?: boolean | undefined + inlineScripts?: InlineChunkConfig | 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 3db9b06a53..a67502a713 100644 --- a/packages/rspeedy/core/test/config/output.test-d.ts +++ b/packages/rspeedy/core/test/config/output.test-d.ts @@ -182,6 +182,26 @@ describe('Config - Output', () => { assertType({ inlineScripts: false, }) + assertType({ + inlineScripts: /[\\/]background\.\w+\.js$/, + }) + assertType({ + inlineScripts: ({ size }) => { + return size < 10 * 1000 + }, + }) + assertType({ + inlineScripts: { + enable: 'auto', + test: /[\\/]background\.\w+\.js$/, + }, + }) + assertType({ + inlineScripts: { + enable: true, + test: /[\\/]background\.\w+\.js$/, + }, + }) }) test('output.legalComments', () => { diff --git a/packages/rspeedy/core/test/config/validate.test.ts b/packages/rspeedy/core/test/config/validate.test.ts index dbb0d76fb1..9945f71f7b 100644 --- a/packages/rspeedy/core/test/config/validate.test.ts +++ b/packages/rspeedy/core/test/config/validate.test.ts @@ -835,6 +835,24 @@ describe('Config Validation', () => { { distPath: { svg: 'svg' } }, { inlineScripts: true }, { inlineScripts: false }, + { inlineScripts: /[\\/]background\.\w+\.js$/ }, + { + inlineScripts: ({ size }) => { + return size < 10 * 1000 + }, + }, + { + inlineScripts: { + enable: 'auto', + test: /[\\/]background\.\w+\.js$/, + }, + }, + { + inlineScripts: { + enable: true, + test: /[\\/]background\.\w+\.js$/, + }, + }, { legalComments: 'inline' }, { legalComments: 'none' }, { legalComments: 'linked' }, @@ -1099,15 +1117,67 @@ describe('Config Validation', () => { ] `) - expect(() => validate({ output: { inlineScripts: null } })) - .toThrowErrorMatchingInlineSnapshot(` - [Error: Invalid configuration. + expect(() => + validate({ + output: { + inlineScripts: { + enable: 123, + }, + }, + }) + ).toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid configuration. - Invalid config on \`$input.output.inlineScripts\`. - - Expect to be (boolean | undefined) - - Got: null - ] - `) + Invalid config on \`$input.output.inlineScripts.enable\`. + - Expect to be ("auto" | boolean | undefined) + - Got: number + + Invalid config on \`$input.output.inlineScripts.test\`. + - Expect to be RegExp + - Got: undefined + ] + `) + + expect(() => + validate({ + output: { + inlineScripts: { + enable: true, + }, + }, + }) + ).toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid configuration. + + Invalid config on \`$input.output.inlineScripts.test\`. + - Expect to be RegExp + - Got: undefined + ] + `) + + expect(() => + validate({ + output: { + inlineScripts: { + enable: true, + test: 123, + }, + }, + }) + ).toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid configuration. + + Invalid config on \`$input.output.inlineScripts.test\`. + - Expect to be RegExp + - Got: number + ] + `) + + // FIXME: + // ubuntu will matchingInlineSnapshot _type.o111 + // macos will matchingInlineSnapshot _type.o110 + expect(() => validate({ output: { inlineScripts: null } })) + .toThrowError() expect(() => validate({ output: { legalComments: [null] } })) .toThrowErrorMatchingInlineSnapshot(` diff --git a/packages/rspeedy/plugin-react/src/entry.ts b/packages/rspeedy/plugin-react/src/entry.ts index e74a5705c2..18e001f7f0 100644 --- a/packages/rspeedy/plugin-react/src/entry.ts +++ b/packages/rspeedy/plugin-react/src/entry.ts @@ -197,11 +197,20 @@ export function applyEntry( .end() }) + let finalFirstScreenSyncTiming = firstScreenSyncTiming + if (isLynx) { - const inlineScripts = - typeof environment.config.output?.inlineScripts === 'boolean' - ? environment.config.output.inlineScripts - : true + let inlineScripts + if (experimental_isLazyBundle) { + // TODO: support inlineScripts in lazyBundle + inlineScripts = true + } else { + inlineScripts = environment.config.output?.inlineScripts ?? true + } + + if (inlineScripts !== true) { + finalFirstScreenSyncTiming = 'jsReady' + } chain .plugin(PLUGIN_NAME_RUNTIME_WRAPPER) @@ -246,7 +255,7 @@ export function applyEntry( .use(ReactWebpackPlugin, [{ disableCreateSelectorQueryIncompatibleWarning: compat ?.disableCreateSelectorQueryIncompatibleWarning ?? false, - firstScreenSyncTiming, + firstScreenSyncTiming: finalFirstScreenSyncTiming, enableSSR, mainThreadChunks, extractStr, 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 ffcaea2188..66e3cd2faf 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 @@ -87,8 +87,10 @@ export class LynxEncodePlugin { // @public export interface LynxEncodePluginOptions { + // Warning: (ae-forgotten-export) The symbol "InlineChunkConfig" needs to be exported by the entry point index.d.ts + // // (undocumented) - inlineScripts?: boolean | undefined; + inlineScripts?: InlineChunkConfig | undefined; } // @public @@ -184,6 +186,6 @@ export class WebEncodePlugin { // Warnings were encountered during analysis: // -// lib/LynxTemplatePlugin.d.ts:63:9 - (ae-forgotten-export) The symbol "EncodeRawData" needs to be exported by the entry point index.d.ts +// lib/LynxTemplatePlugin.d.ts:67:9 - (ae-forgotten-export) The symbol "EncodeRawData" needs to be exported by the entry point index.d.ts ``` diff --git a/packages/webpack/template-webpack-plugin/src/LynxEncodePlugin.ts b/packages/webpack/template-webpack-plugin/src/LynxEncodePlugin.ts index 7f79b5bfeb..151175bf8e 100644 --- a/packages/webpack/template-webpack-plugin/src/LynxEncodePlugin.ts +++ b/packages/webpack/template-webpack-plugin/src/LynxEncodePlugin.ts @@ -6,13 +6,24 @@ import type { Compiler } from 'webpack'; import { LynxTemplatePlugin } from './LynxTemplatePlugin.js'; +// https://github.com/web-infra-dev/rsbuild/blob/main/packages/core/src/types/config.ts#L1029 +type InlineChunkTestFunction = (params: { + size: number; + name: string; +}) => boolean; +type InlineChunkTest = RegExp | InlineChunkTestFunction; +type InlineChunkConfig = boolean | InlineChunkTest | { + enable?: boolean | 'auto'; + test: InlineChunkTest; +}; + /** * The options for LynxEncodePluginOptions * * @public */ export interface LynxEncodePluginOptions { - inlineScripts?: boolean | undefined; + inlineScripts?: InlineChunkConfig | undefined; } /** @@ -113,24 +124,43 @@ export class LynxEncodePluginImpl { const { encodeData } = args; const { manifest } = encodeData; + const [inlinedManifest, externalManifest] = Object.entries( + manifest, + ) + .reduce( + ([inlined, external], [name, content]) => { + const assert = compilation.getAsset(name); + const shouldInline = this.#shouldInlineScript( + name, + assert!.source.size(), + ); + + if (shouldInline) { + inlined[name] = content; + } else { + external[name] = content; + } + return [inlined, external]; + }, + [{}, {}] as [Record, Record], + ); + 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 (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, ...encodeData.lepusCode.chunks, - ...Object.keys(manifest).map(name => ({ name })), + ...Object.keys(inlinedManifest).map(name => ({ name })), ...encodeData.css.chunks, ] .filter(asset => asset !== undefined) @@ -146,32 +176,38 @@ export class LynxEncodePluginImpl { // '/app-service.js': ` // lynx.requireModule('async-chunk1') // lynx.requireModule('async-chunk2') - // lynx.requireModule('initial-chunk1') - // lynx.requireModule('initial-chunk2') + // lynx.requireModule('inlined-initial-chunk1') + // lynx.requireModule('inlined-initial-chunk2') + // lynx.requireModuleAsync('external-initial-chunk1') + // lynx.requireModuleAsync('external-initial-chunk2') // `, - // 'initial-chunk1': ``, - // 'initial-chunk2': ``, + // 'inlined-initial-chunk1': ``, + // 'inlined-initial-chunk2': ``, // }, // ``` '/app-service.js': [ this.#appServiceBanner(), - Object.keys(manifest) - .map((name) => - `module.exports=lynx.requireModule('${ - this.#formatJSName(name, publicPath) - }',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')` - ) - .join(','), + ...[externalManifest, inlinedManifest].flatMap(manifest => + Object.keys(manifest).map(name => { + if (manifest === externalManifest) { + return `lynx.requireModuleAsync('${ + this.#formatJSName(name, publicPath) + }')`; + } else { + return `module.exports=lynx.requireModule('${ + this.#formatJSName(name, '/') + }',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`; + } + }).join(',') + ), this.#appServiceFooter(), ].join(''), - ...(this.options.inlineScripts - ? Object.fromEntries( - Object.entries(manifest).map(([name, source]) => [ - this.#formatJSName(name, publicPath), - source, - ]), - ) - : {}), + ...Object.fromEntries( + Object.entries(inlinedManifest).map(([name, content]) => [ + this.#formatJSName(name, '/'), + content, + ]), + ), }; return args; @@ -216,6 +252,28 @@ export class LynxEncodePluginImpl { return publicPath + name; } + #shouldInlineScript(name: string, size: number): boolean { + const inlineConfig = this.options.inlineScripts; + + if (inlineConfig instanceof RegExp) { + return inlineConfig.test(name); + } + + if (typeof inlineConfig === 'function') { + return inlineConfig({ size, name }); + } + + if (typeof inlineConfig === 'object') { + if (inlineConfig.enable === false) return false; + if (inlineConfig.test instanceof RegExp) { + return inlineConfig.test.test(name); + } + return inlineConfig.test({ size, name }); + } + + return inlineConfig !== false; + } + protected options: Required; } diff --git a/packages/webpack/template-webpack-plugin/src/LynxTemplatePlugin.ts b/packages/webpack/template-webpack-plugin/src/LynxTemplatePlugin.ts index b009f7e3bb..9a47bc485a 100644 --- a/packages/webpack/template-webpack-plugin/src/LynxTemplatePlugin.ts +++ b/packages/webpack/template-webpack-plugin/src/LynxTemplatePlugin.ts @@ -25,6 +25,11 @@ import { RuntimeGlobals } from '@lynx-js/webpack-runtime-globals'; import { cssChunksToMap } from './css/cssChunksToMap.js'; import { createLynxAsyncChunksRuntimeModule } from './LynxAsyncChunksRuntimeModule.js'; +export type OriginManifest = Record; + /** * The options for encoding a Lynx bundle. * 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 index f077b8f24d..efd5ac3d90 100644 --- 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 @@ -41,6 +41,6 @@ it('manifest only contains /app-service.js', async () => { expect(manifest).toHaveProperty('/app-service.js'); expect(manifest['/app-service.js']).toContain( - `lynx.requireModule('/foo:background.rspack.bundle.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`, + `lynx.requireModuleAsync('/foo:background.rspack.bundle.js')`, ); }); diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-fn/bar.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-fn/bar.js new file mode 100644 index 0000000000..f0eec38759 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-fn/bar.js @@ -0,0 +1,3 @@ +export function bar() { + return 52; +} diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-fn/foo.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-fn/foo.js new file mode 100644 index 0000000000..5775bf0191 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-fn/foo.js @@ -0,0 +1,3 @@ +export function foo() { + return 42; +} diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-fn/index.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-fn/index.js new file mode 100644 index 0000000000..138c99ff83 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-fn/index.js @@ -0,0 +1,68 @@ +/* +// 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 foo 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 have correct bar chunk content', async () => { + const { bar } = await import( + /* webpackChunkName: 'bar:main-thread' */ + './bar.js' + ); + expect(bar()).toBe(52); + + const fooBackground = await import( + /* webpackChunkName: 'bar:background' */ + './bar.js' + ); + expect(fooBackground.bar()).toBe(52); +}); + +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('/app-service.js'); + + expect(manifest).toHaveProperty( + '/foo:background.rspack.bundle.js', + expect.stringContaining('function foo()'), + ); +}); + +it('should generate correct bar template', async () => { + const tasmJSONPath = resolve(__dirname, '.rspeedy/async/bar/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('/app-service.js'); + + expect(manifest).not.toHaveProperty('/bar:background.rspack.bundle.js'); + expect(manifest['/app-service.js']).toContain( + `lynx.requireModuleAsync('/bar:background.rspack.bundle.js')`, + ); +}); diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-fn/rspack.config.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-fn/rspack.config.js new file mode 100644 index 0000000000..43f14daf93 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-fn/rspack.config.js @@ -0,0 +1,35 @@ +import { LynxEncodePlugin, LynxTemplatePlugin } from '../../../../src'; + +/** @type {import('@rspack/core').Configuration} */ +export default { + devtool: false, + mode: 'development', + plugins: [ + new LynxEncodePlugin({ + inlineScripts: ({ name, size }) => { + return name.includes('foo') && size < 1000; + }, + }), + 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-object/bar.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-object/bar.js new file mode 100644 index 0000000000..f0eec38759 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-object/bar.js @@ -0,0 +1,3 @@ +export function bar() { + return 52; +} diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-object/foo.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-object/foo.js new file mode 100644 index 0000000000..5775bf0191 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-object/foo.js @@ -0,0 +1,3 @@ +export function foo() { + return 42; +} diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-object/index.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-object/index.js new file mode 100644 index 0000000000..138c99ff83 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-object/index.js @@ -0,0 +1,68 @@ +/* +// 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 foo 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 have correct bar chunk content', async () => { + const { bar } = await import( + /* webpackChunkName: 'bar:main-thread' */ + './bar.js' + ); + expect(bar()).toBe(52); + + const fooBackground = await import( + /* webpackChunkName: 'bar:background' */ + './bar.js' + ); + expect(fooBackground.bar()).toBe(52); +}); + +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('/app-service.js'); + + expect(manifest).toHaveProperty( + '/foo:background.rspack.bundle.js', + expect.stringContaining('function foo()'), + ); +}); + +it('should generate correct bar template', async () => { + const tasmJSONPath = resolve(__dirname, '.rspeedy/async/bar/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('/app-service.js'); + + expect(manifest).not.toHaveProperty('/bar:background.rspack.bundle.js'); + expect(manifest['/app-service.js']).toContain( + `lynx.requireModuleAsync('/bar:background.rspack.bundle.js')`, + ); +}); diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-object/rspack.config.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-object/rspack.config.js new file mode 100644 index 0000000000..a24b5b29c2 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-object/rspack.config.js @@ -0,0 +1,36 @@ +import { LynxEncodePlugin, LynxTemplatePlugin } from '../../../../src'; + +/** @type {import('@rspack/core').Configuration} */ +export default { + devtool: false, + mode: 'development', + plugins: [ + new LynxEncodePlugin({ + inlineScripts: { + enable: true, + test: /foo.*\.js$/, + }, + }), + 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-regex/bar.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-regex/bar.js new file mode 100644 index 0000000000..f0eec38759 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-regex/bar.js @@ -0,0 +1,3 @@ +export function bar() { + return 52; +} diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-regex/foo.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-regex/foo.js new file mode 100644 index 0000000000..5775bf0191 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-regex/foo.js @@ -0,0 +1,3 @@ +export function foo() { + return 42; +} diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-regex/index.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-regex/index.js new file mode 100644 index 0000000000..138c99ff83 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-regex/index.js @@ -0,0 +1,68 @@ +/* +// 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 foo 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 have correct bar chunk content', async () => { + const { bar } = await import( + /* webpackChunkName: 'bar:main-thread' */ + './bar.js' + ); + expect(bar()).toBe(52); + + const fooBackground = await import( + /* webpackChunkName: 'bar:background' */ + './bar.js' + ); + expect(fooBackground.bar()).toBe(52); +}); + +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('/app-service.js'); + + expect(manifest).toHaveProperty( + '/foo:background.rspack.bundle.js', + expect.stringContaining('function foo()'), + ); +}); + +it('should generate correct bar template', async () => { + const tasmJSONPath = resolve(__dirname, '.rspeedy/async/bar/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('/app-service.js'); + + expect(manifest).not.toHaveProperty('/bar:background.rspack.bundle.js'); + expect(manifest['/app-service.js']).toContain( + `lynx.requireModuleAsync('/bar:background.rspack.bundle.js')`, + ); +}); diff --git a/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-regex/rspack.config.js b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-regex/rspack.config.js new file mode 100644 index 0000000000..4ce309ef4c --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/cases/inline-scripts/inline-regex/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: /foo.*\.js$/, + }), + 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', ''), + ); + }); + }, + ], +};