diff --git a/.changeset/flat-bikes-boil.md b/.changeset/flat-bikes-boil.md new file mode 100644 index 0000000000..7b87020ae8 --- /dev/null +++ b/.changeset/flat-bikes-boil.md @@ -0,0 +1,6 @@ +--- +"@lynx-js/lynx-bundle-rslib-config": patch +"@lynx-js/react-umd": patch +--- + +Support compile main-thread script to bytecode in external bundle diff --git a/packages/rspeedy/lynx-bundle-rslib-config/etc/lynx-bundle-rslib-config.api.md b/packages/rspeedy/lynx-bundle-rslib-config/etc/lynx-bundle-rslib-config.api.md index 2162aca6d8..2e6edca698 100644 --- a/packages/rspeedy/lynx-bundle-rslib-config/etc/lynx-bundle-rslib-config.api.md +++ b/packages/rspeedy/lynx-bundle-rslib-config/etc/lynx-bundle-rslib-config.api.md @@ -45,6 +45,7 @@ export interface ExternalBundleWebpackPluginOptions { buffer: Buffer; }>; engineVersion?: string | undefined; + mainThreadChunks?: string[] | undefined; } // @public diff --git a/packages/rspeedy/lynx-bundle-rslib-config/package.json b/packages/rspeedy/lynx-bundle-rslib-config/package.json index 534811af50..c3d76be7bf 100644 --- a/packages/rspeedy/lynx-bundle-rslib-config/package.json +++ b/packages/rspeedy/lynx-bundle-rslib-config/package.json @@ -38,7 +38,7 @@ "dependencies": { "@lynx-js/css-serializer": "workspace:*", "@lynx-js/runtime-wrapper-webpack-plugin": "workspace:*", - "@lynx-js/tasm": "0.0.33" + "@lynx-js/tasm": "0.0.35" }, "devDependencies": { "@lynx-js/react": "workspace:*", diff --git a/packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts b/packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts index fcfd47b724..84091d8698 100644 --- a/packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts +++ b/packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts @@ -453,8 +453,9 @@ export function defineExternalBundleRslibConfig( ), ], plugins: [ - externalBundleEntryRsbuildPlugin(), - externalBundleRsbuildPlugin(encodeOptions.engineVersion), + externalBundleRsbuildPlugin({ + engineVersion: encodeOptions.engineVersion, + }), ], } } @@ -472,8 +473,12 @@ interface ExposedLayers { * - `` for background * - `__main-thread` for main thread */ -const externalBundleEntryRsbuildPlugin = (): rsbuild.RsbuildPlugin => ({ - name: 'lynx:external-bundle-entry', +const externalBundleRsbuildPlugin = ({ + engineVersion, +}: { + engineVersion: string | undefined +}): rsbuild.RsbuildPlugin => ({ + name: 'lynx:external-bundle', // ensure dsl plugin has exposed LAYERS enforce: 'post', setup(api) { @@ -488,75 +493,85 @@ const externalBundleEntryRsbuildPlugin = (): rsbuild.RsbuildPlugin => ({ ) } - api.modifyBundlerChain((chain) => { - // copy entries - const entries = chain.entryPoints.entries() ?? {} + api.modifyBundlerChain( + async (chain, { environment: { name: libName } }) => { + // copy entries + const entries = chain.entryPoints.entries() ?? {} - chain.entryPoints.clear() + chain.entryPoints.clear() - const backgroundEntryName: string[] = [] - const mainThreadEntryName: string[] = [] + const backgroundEntryName: string[] = [] + const mainThreadEntryName: string[] = [] + const mainThreadChunks: string[] = [] - const addLayeredEntry = ( - entryName: string, - entryValue: Rspack.EntryDescription, - ) => { - chain - .entry(entryName) - .add(entryValue) - .end() - } + const addLayeredEntry = ( + entryName: string, + entryValue: Rspack.EntryDescription, + ) => { + const isMainThread = entryValue.layer === LAYERS.MAIN_THREAD + if (isMainThread) { + mainThreadChunks.push(entryName + '.js') + } - Object.entries(entries).forEach(([entryName, entryPoint]) => { - const entryPointValue = entryPoint.values() - - for (const value of entryPointValue) { - if (typeof value === 'string' || Array.isArray(value)) { - const mainThreadEntry = `${entryName}__main-thread` - const backgroundEntry = entryName - mainThreadEntryName.push(mainThreadEntry) - backgroundEntryName.push(backgroundEntry) - addLayeredEntry(mainThreadEntry, { - import: value, - layer: LAYERS.MAIN_THREAD, - }) - addLayeredEntry(backgroundEntry, { - import: value, - layer: LAYERS.BACKGROUND, - }) - } else { - // object - const { layer } = value - if (layer === LAYERS.MAIN_THREAD) { - mainThreadEntryName.push(entryName) - addLayeredEntry(entryName, { - ...value, - layer: LAYERS.MAIN_THREAD, - }) - } else if (layer === LAYERS.BACKGROUND) { - backgroundEntryName.push(entryName) - addLayeredEntry(entryName, { ...value, layer: LAYERS.BACKGROUND }) - } else { - // not specify layer + chain + .entry(entryName) + .add(entryValue) + .end() + } + + Object.entries(entries).forEach(([entryName, entryPoint]) => { + const entryPointValue = entryPoint.values() + + for (const value of entryPointValue) { + if (typeof value === 'string' || Array.isArray(value)) { const mainThreadEntry = `${entryName}__main-thread` const backgroundEntry = entryName mainThreadEntryName.push(mainThreadEntry) backgroundEntryName.push(backgroundEntry) addLayeredEntry(mainThreadEntry, { - ...value, + import: value, layer: LAYERS.MAIN_THREAD, }) addLayeredEntry(backgroundEntry, { - ...value, + import: value, layer: LAYERS.BACKGROUND, }) + } else { + // object + const { layer } = value + if (layer === LAYERS.MAIN_THREAD) { + mainThreadEntryName.push(entryName) + addLayeredEntry(entryName, { + ...value, + layer: LAYERS.MAIN_THREAD, + }) + } else if (layer === LAYERS.BACKGROUND) { + backgroundEntryName.push(entryName) + addLayeredEntry(entryName, { + ...value, + layer: LAYERS.BACKGROUND, + }) + } else { + // not specify layer + const mainThreadEntry = `${entryName}__main-thread` + const backgroundEntry = entryName + mainThreadEntryName.push(mainThreadEntry) + backgroundEntryName.push(backgroundEntry) + addLayeredEntry(mainThreadEntry, { + ...value, + layer: LAYERS.MAIN_THREAD, + }) + addLayeredEntry(backgroundEntry, { + ...value, + layer: LAYERS.BACKGROUND, + }) + } } } - } - }) - // add external bundle wrapper - // dprint-ignore - chain + }) + // add external bundle wrapper + // dprint-ignore + chain .plugin(MainThreadRuntimeWrapperWebpackPlugin.name) .use(MainThreadRuntimeWrapperWebpackPlugin, [{ test: mainThreadEntryName.map((name) => new RegExp(`${escapeRegex(name)}\\.js$`)), @@ -567,20 +582,11 @@ const externalBundleEntryRsbuildPlugin = (): rsbuild.RsbuildPlugin => ({ test: backgroundEntryName.map((name) => new RegExp(`${escapeRegex(name)}\\.js$`)), }]) .end() - }) - }, -}) -const externalBundleRsbuildPlugin = ( - engineVersion: string | undefined, -): rsbuild.RsbuildPlugin => ({ - name: 'lynx:gen-external-bundle', - async setup(api) { - const { getEncodeMode } = await import('@lynx-js/tasm') + const { getEncodeMode } = await import('@lynx-js/tasm') - api.modifyBundlerChain((chain, { environment: { name: libName } }) => { - // dprint-ignore - chain + // dprint-ignore + chain .plugin(ExternalBundleWebpackPlugin.name) .use( ExternalBundleWebpackPlugin, @@ -589,11 +595,13 @@ const externalBundleRsbuildPlugin = ( bundleFileName: `${libName}.lynx.bundle`, encode: getEncodeMode(), engineVersion, + mainThreadChunks, }, ], ) .end() - }) + }, + ) }, }) diff --git a/packages/rspeedy/lynx-bundle-rslib-config/src/webpack/ExternalBundleWebpackPlugin.ts b/packages/rspeedy/lynx-bundle-rslib-config/src/webpack/ExternalBundleWebpackPlugin.ts index 088274bb23..e8786237f3 100644 --- a/packages/rspeedy/lynx-bundle-rslib-config/src/webpack/ExternalBundleWebpackPlugin.ts +++ b/packages/rspeedy/lynx-bundle-rslib-config/src/webpack/ExternalBundleWebpackPlugin.ts @@ -42,6 +42,13 @@ export interface ExternalBundleWebpackPluginOptions { * @defaultValue '3.5' */ engineVersion?: string | undefined + + /** + * The main thread chunks of the external bundle. + * + * @defaultValue [] + */ + mainThreadChunks?: string[] | undefined } const isDebug = (): boolean => { @@ -128,6 +135,11 @@ export class ExternalBundleWebpackPlugin { return ({ ...prev, [cur.name.replace(/\.js$/, '')]: { + ...(this.options.mainThreadChunks?.includes(cur.name) + ? { + 'encoding': 'JsBytecode', + } + : {}), content: cur.source.source().toString(), }, }) @@ -154,6 +166,7 @@ export class ExternalBundleWebpackPlugin { const compilerOptions: Record = { enableFiberArch: true, + useLepusNG: true, // `lynx.fetchBundle` and `lynx.loadScript` require engineVersion >= 3.5 targetSdkVersion: this.options.engineVersion ?? '3.5', enableCSSInvalidation: true, diff --git a/packages/rspeedy/lynx-bundle-rslib-config/test/external-bundle.test.ts b/packages/rspeedy/lynx-bundle-rslib-config/test/external-bundle.test.ts index 3ea7a6e654..2e51c06752 100644 --- a/packages/rspeedy/lynx-bundle-rslib-config/test/external-bundle.test.ts +++ b/packages/rspeedy/lynx-bundle-rslib-config/test/external-bundle.test.ts @@ -243,37 +243,22 @@ describe('should build external bundle', () => { 'index:CSS', 'index__main-thread', ]) - }) - it('should include LoadingConsumerModulesRuntimeModule in the main-thread bundle', async () => { - vi.stubEnv('NODE_ENV', 'development') - const rslibConfig = defineExternalBundleRslibConfig({ - source: { - entry: { - utils: path.join(__dirname, './fixtures/utils-lib/index.ts'), - }, - }, - id: 'utils-runtime-module', - output: { - distPath: { - root: path.join(fixtureDir, 'dist'), - }, - minify: false, - }, - plugins: [pluginReactLynx()], - }) - - await build(rslibConfig) - - const decodedResult = await decodeTemplate( - path.join(fixtureDir, 'dist/utils-runtime-module.lynx.bundle'), + expect(Array.isArray(decodedResult['custom-sections']['index:CSS'])).toBe( + true, ) - - // Check if the runtime module code injected by LoadingConsumerModulesRuntimeModule is present - expect(decodedResult['custom-sections']['utils__main-thread']).toContain( - 'var globalModules = globalThis[Symbol.for(\'__LYNX_WEBPACK_MODULES__\')];', + expect(decodedResult['custom-sections']['index:CSS']![0]).toBeTypeOf( + 'number', ) - vi.unstubAllEnvs() + + expect(decodedResult['custom-sections']['index']).toBeTypeOf('string') + + // MTS should be bytecode + expect( + Array.isArray(decodedResult['custom-sections']['index__main-thread']), + ).toBe(true) + expect(decodedResult['custom-sections']['index__main-thread']![0]) + .toBeTypeOf('number') }) }) @@ -318,9 +303,13 @@ describe('NODE_ENV configuration', () => { // The produced artifacts should be different expect(devMainThread).not.toBe(prodMainThread) + const devBackground = devResult['custom-sections']['utils']! + const prodBackground = prodResult['custom-sections']['utils']! + + expect(devBackground).not.toBe(prodBackground) // __DEV__ macro should be replaced differently - expect(devMainThread).toMatch(/isDev:\s*(!0|true)/) - expect(prodMainThread).toMatch(/isDev:\s*(!1|false)/) + expect(devBackground).toMatch(/isDev:\s*(!0|true)/) + expect(prodBackground).toMatch(/isDev:\s*(!1|false)/) }) }) @@ -408,9 +397,12 @@ describe('mount externals library', () => { expect(decodedResult['custom-sections']['utils']).toContain( 'lynx[Symbol.for("__LYNX_EXTERNAL_GLOBAL__")].ReactLynx.React', ) - expect(decodedResult['custom-sections']['utils__main-thread']).toContain( - 'lynx[Symbol.for("__LYNX_EXTERNAL_GLOBAL__")].ReactLynx.React', - ) + // MTS should be bytecode + expect( + Array.isArray(decodedResult['custom-sections']['utils__main-thread']), + ).toBe(true) + expect(decodedResult['custom-sections']['utils__main-thread']![0]) + .toBeTypeOf('number') }) it('should apply reactlynx externals preset to the final bundle', async () => { @@ -462,9 +454,13 @@ describe('mount externals library', () => { expect(decodedResult['custom-sections']['utils']).toContain( 'globalThis[Symbol.for("__LYNX_EXTERNAL_GLOBAL__")].ReactLynx.React', ) - expect(decodedResult['custom-sections']['utils__main-thread']).toContain( - 'globalThis[Symbol.for("__LYNX_EXTERNAL_GLOBAL__")].ReactLynx.React', - ) + + // MTS should be bytecode + expect( + Array.isArray(decodedResult['custom-sections']['utils__main-thread']), + ).toBe(true) + expect(decodedResult['custom-sections']['utils__main-thread']![0]) + .toBeTypeOf('number') }) it('should let explicit externals override the reactlynx preset', async () => { @@ -502,16 +498,15 @@ describe('mount externals library', () => { expect(decodedResult['custom-sections']['utils']).toContain( 'globalThis[Symbol.for("__LYNX_EXTERNAL_GLOBAL__")].CustomRuntime.React', ) - expect(decodedResult['custom-sections']['utils__main-thread']).toContain( - 'globalThis[Symbol.for("__LYNX_EXTERNAL_GLOBAL__")].CustomRuntime.React', - ) expect(decodedResult['custom-sections']['utils']).not.toContain( 'globalThis[Symbol.for("__LYNX_EXTERNAL_GLOBAL__")].ReactLynx.React', ) - expect(decodedResult['custom-sections']['utils__main-thread']).not - .toContain( - 'globalThis[Symbol.for("__LYNX_EXTERNAL_GLOBAL__")].ReactLynx.React', - ) + // MTS should be bytecode + expect( + Array.isArray(decodedResult['custom-sections']['utils__main-thread']), + ).toBe(true) + expect(decodedResult['custom-sections']['utils__main-thread']![0]) + .toBeTypeOf('number') }) it('should allow extending the built-in reactlynx preset', async () => { @@ -634,9 +629,13 @@ describe('mount externals library', () => { expect(decodedResult['custom-sections']['utils']).toContain( 'globalThis[Symbol.for("__LYNX_EXTERNAL_GLOBAL__")].ReactLynx.React', ) - expect(decodedResult['custom-sections']['utils__main-thread']).toContain( - 'globalThis[Symbol.for("__LYNX_EXTERNAL_GLOBAL__")].ReactLynx.React', - ) + + // MTS should be bytecode + expect( + Array.isArray(decodedResult['custom-sections']['utils__main-thread']), + ).toBe(true) + expect(decodedResult['custom-sections']['utils__main-thread']![0]) + .toBeTypeOf('number') }) }) @@ -743,22 +742,20 @@ describe('pluginReactLynx', () => { expect(decodedResult['custom-sections']['utils']).toContain( 'log("defineDCE",{isMainThread:!1,isLepus:!1,isBackground:!0}', ) - expect(decodedResult['custom-sections']['utils__main-thread']).toContain( - 'log("defineDCE",{isMainThread:!0,isLepus:!0,isBackground:!1}', - ) expect(decodedResult['custom-sections']['utils']).toContain( 'log("define",{isDev:!1,isProfile:!0}', ) - expect(decodedResult['custom-sections']['utils__main-thread']).toContain( - 'log("define",{isDev:!1,isProfile:!0}', - ) expect(decodedResult['custom-sections']['utils']).toContain( 'log("process.env.NODE_ENV",{NODE_ENV:"test"}', ) - expect(decodedResult['custom-sections']['utils__main-thread']).toContain( - 'log("process.env.NODE_ENV",{NODE_ENV:"test"}', - ) + + // MTS should be bytecode + expect( + Array.isArray(decodedResult['custom-sections']['utils__main-thread']), + ).toBe(true) + expect(decodedResult['custom-sections']['utils__main-thread']![0]) + .toBeTypeOf('number') }) }) diff --git a/packages/webpack/template-webpack-plugin/package.json b/packages/webpack/template-webpack-plugin/package.json index f24db341a3..7cf1939b2e 100644 --- a/packages/webpack/template-webpack-plugin/package.json +++ b/packages/webpack/template-webpack-plugin/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@lynx-js/css-serializer": "workspace:*", - "@lynx-js/tasm": "0.0.33", + "@lynx-js/tasm": "0.0.35", "@lynx-js/web-core": "workspace:*", "@lynx-js/webpack-runtime-globals": "workspace:^", "@rspack/lite-tapable": "1.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d833070107..3c47566440 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -967,8 +967,8 @@ importers: specifier: workspace:* version: link:../../webpack/runtime-wrapper-webpack-plugin '@lynx-js/tasm': - specifier: 0.0.33 - version: 0.0.33 + specifier: 0.0.35 + version: 0.0.35 devDependencies: '@lynx-js/react': specifier: workspace:* @@ -1870,8 +1870,8 @@ importers: specifier: workspace:* version: link:../../tools/css-serializer '@lynx-js/tasm': - specifier: 0.0.33 - version: 0.0.33 + specifier: 0.0.35 + version: 0.0.35 '@lynx-js/web-core': specifier: workspace:* version: link:../../web-platform/web-core @@ -3451,8 +3451,8 @@ packages: peerDependencies: '@lynx-js/react': '>=0.114.4' - '@lynx-js/tasm@0.0.33': - resolution: {integrity: sha512-pquTgvCwrcP1VV8Fw/mRrvQDFyeA2Wu2KK3yI24wZYSua14g8AhRAeFpSs9NT6lDaktCnzc1mg+a2GzWoGrVWA==} + '@lynx-js/tasm@0.0.35': + resolution: {integrity: sha512-8c2Nh6JbAdOzIvAwOgbUZGV7mNJ917y40xzGKHOjyv8Rxhjw0FTv+f+FJGpW3VXl9DUzUGM9GNoDOrL678Kt8A==} hasBin: true '@lynx-js/trace-processor@0.0.1': @@ -12188,7 +12188,7 @@ snapshots: - react - react-dom - '@lynx-js/tasm@0.0.33': {} + '@lynx-js/tasm@0.0.35': {} '@lynx-js/trace-processor@0.0.1': dependencies: