diff --git a/.changeset/green-tips-bake.md b/.changeset/green-tips-bake.md new file mode 100644 index 0000000000..700ef63f57 --- /dev/null +++ b/.changeset/green-tips-bake.md @@ -0,0 +1,5 @@ +--- +"@lynx-js/react-rsbuild-plugin": patch +--- + +Update runtime-wrapper exclusion to use configured lepus chunk names instead of hardcoded single-chunk matching, so executable lepus chunks keep plain output formatting. diff --git a/.changeset/silent-cars-trade.md b/.changeset/silent-cars-trade.md new file mode 100644 index 0000000000..881b305ae2 --- /dev/null +++ b/.changeset/silent-cars-trade.md @@ -0,0 +1,7 @@ +--- +"@lynx-js/react-webpack-plugin": patch +--- + +Route lepus runtime chunks, including `worklet-runtime`, through the standard chunk compilation pipeline so sourcemaps can be discovered by upload plugins while keeping template injection names and runtime loading behavior unchanged. + +`@lynx-js/react-webpack-plugin` now compiles lepus chunks as normal entries, injects the compiled output into template lepus chunks, and removes generated lepus chunk assets from final output after report-stage processing. diff --git a/packages/rspeedy/plugin-react/src/entry.ts b/packages/rspeedy/plugin-react/src/entry.ts index 52151fa067..a7691d1da6 100644 --- a/packages/rspeedy/plugin-react/src/entry.ts +++ b/packages/rspeedy/plugin-react/src/entry.ts @@ -57,6 +57,15 @@ export function applyEntry( api.modifyBundlerChain(async (chain, { environment, isDev, isProd }) => { const mainThreadChunks: string[] = [] + const { resolve } = api.useExposed< + { resolve: (request: string) => Promise } + >(Symbol.for('@lynx-js/react/internal:resolve'))! + const workletRuntimePath = await resolve( + `@lynx-js/react/${isDev ? 'worklet-dev-runtime' : 'worklet-runtime'}`, + ) + const lepusChunkNames = getLepusChunkNames({ + workletRuntimePath, + }) const rsbuildConfig = api.getRsbuildConfig() const userConfig = api.getRsbuildConfig('original') @@ -235,8 +244,11 @@ export function applyEntry( }) }, targetSdkVersion, - // Inject runtime wrapper for all `.js` but not `main-thread.js` and `main-thread.[hash].js`. - test: /^(?!.*main-thread(?:\.[A-Fa-f0-9]*)?\.js$).*\.js$/, + // Inject runtime wrapper for all `.js` except chunks that should keep + // plain executable output (main-thread + all lepus chunks). + test: createRuntimeWrapperTestPattern( + lepusChunkNames, + ), experimental_isLazyBundle, }]) .end() @@ -261,10 +273,6 @@ export function applyEntry( extractStr = false } - const { resolve } = api.useExposed< - { resolve: (request: string) => Promise } - >(Symbol.for('@lynx-js/react/internal:resolve'))! - chain .plugin(PLUGIN_NAME_REACT) .after(PLUGIN_NAME_TEMPLATE) @@ -277,9 +285,7 @@ export function applyEntry( extractStr, experimental_isLazyBundle, profile: getDefaultProfile(), - workletRuntimePath: await resolve( - `@lynx-js/react/${isDev ? 'worklet-dev-runtime' : 'worklet-runtime'}`, - ), + workletRuntimePath, }]) function getDefaultProfile(): boolean | undefined { @@ -387,3 +393,31 @@ function getHash( return EMPTY_HASH } } + +function createRuntimeWrapperTestPattern(lepusChunkNames: string[]): RegExp { + const excluded = [ + 'main-thread', + ...lepusChunkNames, + ] + const escaped = excluded.map((name) => escapeRegExp(name)) + .join('|') + return new RegExp( + `^(?!.*(?:${escaped})(?:\\.[^/.]+)?\\.js$).*\\.js$`, + ) +} + +function escapeRegExp(input: string): string { + return input.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&') +} + +function getLepusChunkNames(options: { + workletRuntimePath: string +}): string[] { + const chunkNames: string[] = [] + + if (options.workletRuntimePath) { + chunkNames.push('worklet-runtime') + } + + return chunkNames +} diff --git a/packages/rspeedy/plugin-react/test/config.test.ts b/packages/rspeedy/plugin-react/test/config.test.ts index 3c0e9559ff..922b52fb53 100644 --- a/packages/rspeedy/plugin-react/test/config.test.ts +++ b/packages/rspeedy/plugin-react/test/config.test.ts @@ -2417,6 +2417,40 @@ describe('Config', () => { ) }) + test('runtime wrapper excludes lepus chunks and main-thread assets', async () => { + const { pluginReactLynx } = await import('../src/pluginReactLynx.js') + const rspeedy = await createRspeedy({ + rspeedyConfig: { + mode: 'production', + plugins: [ + pluginReactLynx(), + pluginStubRspeedyAPI(), + ], + }, + }) + + const [config] = await rspeedy.initConfigs() + const runtimeWrapperPlugin = config?.plugins?.find( + p => p?.constructor.name === 'RuntimeWrapperWebpackPlugin', + ) + + if (!runtimeWrapperPlugin) { + expect.fail('Should have RuntimeWrapperWebpackPlugin instance') + } + + const runtimeWrapperPluginWithOptions = runtimeWrapperPlugin as { + options: { test: RegExp } + } + const testRule = runtimeWrapperPluginWithOptions.options.test + expect(testRule).toBeInstanceOf(RegExp) + + expect(testRule.test('.rspeedy/main/main-thread.js')).toBe(false) + // `worklet-runtime` is currently the only lepus chunk. + expect(testRule.test('static/js/worklet-runtime.js')).toBe(false) + expect(testRule.test('static/js/worklet-runtime.abcd1234.js')).toBe(false) + expect(testRule.test('.rspeedy/main/background.js')).toBe(true) + }) + describe('environment', () => { test('lynx environment', async () => { const { pluginReactLynx } = await import('../src/pluginReactLynx.js') diff --git a/packages/webpack/react-webpack-plugin/src/ReactWebpackPlugin.ts b/packages/webpack/react-webpack-plugin/src/ReactWebpackPlugin.ts index b1a37f6d9c..e229f2fd31 100644 --- a/packages/webpack/react-webpack-plugin/src/ReactWebpackPlugin.ts +++ b/packages/webpack/react-webpack-plugin/src/ReactWebpackPlugin.ts @@ -2,7 +2,6 @@ // 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 * as fs from 'node:fs'; import { createRequire } from 'node:module'; import type { Chunk, Compilation, Compiler } from '@rspack/core'; @@ -13,6 +12,14 @@ import { LynxTemplatePlugin } from '@lynx-js/template-webpack-plugin'; import { RuntimeGlobals } from '@lynx-js/webpack-runtime-globals'; import { LAYERS } from './layer.js'; +import { + createIsolatedLepusChunkSource, + createLepusChunkPipeline, + excludeLepusChunksFromTemplate, + getLepusChunkGeneratedAssetNames, + getLepusChunkPrimaryJsAsset, + injectLepusChunkEntries, +} from './lepusChunkPipeline.js'; import { createLynxProcessEvalResultRuntimeModule } from './LynxProcessEvalResultRuntimeModule.js'; const require = createRequire(import.meta.url); @@ -156,6 +163,10 @@ class ReactWebpackPlugin { this.options, ); const { BannerPlugin, DefinePlugin, EnvironmentPlugin } = compiler.webpack; + const lepusChunkPipeline = createLepusChunkPipeline(options); + + injectLepusChunkEntries(compiler, lepusChunkPipeline); + excludeLepusChunksFromTemplate(compiler, lepusChunkPipeline); if (!options.experimental_isLazyBundle) { new BannerPlugin({ @@ -264,17 +275,25 @@ class ReactWebpackPlugin { this.constructor.name, (args) => { const lepusCode = args.encodeData.lepusCode; - if ( - lepusCode.root?.source.source().toString()?.includes( - 'registerWorkletInternal', - ) - ) { + const lepusRootSource = lepusCode.root?.source.source().toString(); + for (const chunk of lepusChunkPipeline) { + if (!chunk.shouldInject(lepusRootSource)) { + continue; + } + + const chunkAsset = getLepusChunkPrimaryJsAsset( + compilation, + chunk.chunkName, + ); + + const compiledChunkSource = chunkAsset.source.source().toString(); + const injectedChunkSource = createIsolatedLepusChunkSource( + compiledChunkSource, + ); + lepusCode.chunks.push({ - name: 'worklet-runtime', - source: new RawSource(fs.readFileSync( - options.workletRuntimePath, - 'utf8', - )), + name: chunk.chunkName, + source: new RawSource(injectedChunkSource), info: { ['lynx:main-thread']: true, }, @@ -325,6 +344,30 @@ class ReactWebpackPlugin { ?.replaceAll(`-react__background`, '') ?.replaceAll(`-react__main-thread`, ''), ); + + compilation.hooks.processAssets.tap( + { + name: `${this.constructor.name}:lepus-chunk-cleanup`, + // Run after normal report-stage consumers (e.g. sourcemap upload) + // so lepus chunk files can participate in toolchain processing + // but still not leak into final output assets. + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT + + 1000, + }, + () => { + for (const chunk of lepusChunkPipeline) { + const assetNames = getLepusChunkGeneratedAssetNames( + compilation, + chunk.chunkName, + ); + for (const assetName of assetNames) { + if (compilation.getAsset(assetName)) { + compilation.deleteAsset(assetName); + } + } + } + }, + ); }); } diff --git a/packages/webpack/react-webpack-plugin/src/lepusChunkPipeline.ts b/packages/webpack/react-webpack-plugin/src/lepusChunkPipeline.ts new file mode 100644 index 0000000000..d54a14320d --- /dev/null +++ b/packages/webpack/react-webpack-plugin/src/lepusChunkPipeline.ts @@ -0,0 +1,244 @@ +// 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 { Compilation, Compiler } from '@rspack/core'; +import invariant from 'tiny-invariant'; + +import { LynxTemplatePlugin } from '@lynx-js/template-webpack-plugin'; + +import { moduleHasWorkletUsage } from './workletMetadata.js'; + +const WORKLET_RUNTIME_CHUNK_NAME: string = 'worklet-runtime'; + +interface LepusChunkPipelineItem { + chunkName: string; + sourcePath: string; + shouldCompile: (module: unknown) => boolean; + shouldInject: (lepusRootSource: string | undefined) => boolean; +} + +interface LepusChunkPipelineOptions { + workletRuntimePath: string; +} + +interface LepusChunkCompiledAsset { + source: { + source: () => { toString: () => string }; + }; +} + +function createLepusChunkPipeline( + options: LepusChunkPipelineOptions, +): LepusChunkPipelineItem[] { + const chunks: LepusChunkPipelineItem[] = []; + + if (options.workletRuntimePath) { + chunks.push({ + chunkName: WORKLET_RUNTIME_CHUNK_NAME, + sourcePath: options.workletRuntimePath, + shouldCompile: moduleHasWorkletUsage, + shouldInject: (lepusRootSource) => + lepusRootSource?.includes('registerWorkletInternal') ?? false, + }); + } + + return chunks; +} + +function injectLepusChunkEntries( + compiler: Compiler, + lepusChunkPipeline: LepusChunkPipelineItem[], +): void { + if (lepusChunkPipeline.length === 0) { + return; + } + + compiler.hooks.make.tapAsync( + 'ReactWebpackPlugin:LepusChunkEntry', + (compilation, callback) => { + compilation.hooks.finishModules.tapAsync( + 'ReactWebpackPlugin:LepusChunkEntry', + (modules, finishCallback) => { + const allModules = [...modules]; + const queuedChunks = lepusChunkPipeline.filter((chunk) => + !hasEntry(compiler, chunk.chunkName) + && allModules.some(module => chunk.shouldCompile(module)) + ); + + if (queuedChunks.length === 0) { + finishCallback(); + return; + } + + let pending = queuedChunks.length; + let settled = false; + const done = (error?: Error) => { + if (settled) { + return; + } + if (error) { + settled = true; + finishCallback(error); + return; + } + pending -= 1; + if (pending === 0) { + settled = true; + finishCallback(); + } + }; + + for (const chunk of queuedChunks) { + const dependency = compiler.webpack.EntryPlugin.createDependency( + chunk.sourcePath, + ); + // Build lepus runtime chunks through the standard entry pipeline so + // source maps can be discovered from compilation assets/chunks. + compilation.addEntry( + compiler.context, + dependency, + { name: chunk.chunkName }, + (error) => { + if (error) { + done(error); + return; + } + done(); + }, + ); + } + }, + ); + + callback(); + }, + ); +} + +function excludeLepusChunksFromTemplate( + compiler: Compiler, + lepusChunkPipeline: LepusChunkPipelineItem[], +): void { + const chunkNames = new Set(lepusChunkPipeline.map(chunk => chunk.chunkName)); + if (chunkNames.size === 0) { + return; + } + + for (const plugin of compiler.options.plugins ?? []) { + if (!(plugin instanceof LynxTemplatePlugin)) { + continue; + } + + const templatePlugin = plugin as unknown as { + options?: { + excludeChunks?: string[]; + }; + }; + templatePlugin.options ??= {}; + templatePlugin.options.excludeChunks ??= []; + + for (const chunkName of chunkNames) { + if (templatePlugin.options.excludeChunks.includes(chunkName)) { + continue; + } + templatePlugin.options.excludeChunks.push(chunkName); + } + } +} + +function getLepusChunkPrimaryJsAsset( + compilation: Compilation, + chunkName: string, +): LepusChunkCompiledAsset { + const jsAssetNames = getLepusChunkJsAssetNames(compilation, chunkName); + + invariant( + jsAssetNames.length === 1, + `[ReactWebpackPlugin] Lepus chunk "${chunkName}" must emit exactly one JS asset, but got ${jsAssetNames.length}: ${ + jsAssetNames.length > 0 ? jsAssetNames.join(', ') : '(none)' + }. Please disable split/runtime extraction for this chunk.`, + ); + + const jsAssetName = jsAssetNames[0]!; + const asset = compilation.getAsset(jsAssetName); + + invariant( + asset, + `[ReactWebpackPlugin] Missing compiled asset "${jsAssetName}" for chunk "${chunkName}".`, + ); + + invariant( + hasSourceAccessor(asset), + `[ReactWebpackPlugin] Asset "${jsAssetName}" of chunk "${chunkName}" has no valid source accessor.`, + ); + + return asset; +} + +function getLepusChunkGeneratedAssetNames( + compilation: Compilation, + chunkName: string, +): string[] { + const jsAssetNames = getLepusChunkJsAssetNames(compilation, chunkName); + const assetNames = new Set(jsAssetNames); + for (const jsAssetName of jsAssetNames) { + const sourceMapName = `${jsAssetName}.map`; + if (compilation.getAsset(sourceMapName)) { + assetNames.add(sourceMapName); + } + } + return [...assetNames]; +} + +function createIsolatedLepusChunkSource(source: string): string { + // Lepus chunks run in the main-thread JS realm. + // Keep compiled entry bootstrap out of global scope to avoid clobbering + // root chunk runtime symbols (`__webpack_require__`, `__webpack_modules__`). + return `(function(){${source}\n})();`; +} + +function getLepusChunkJsAssetNames( + compilation: Compilation, + chunkName: string, +): string[] { + const chunk = compilation.namedChunks.get(chunkName); + if (!chunk) { + return []; + } + + const files = [...chunk.files]; + return files.filter((name: string) => + name.endsWith('.js') && !name.endsWith('.hot-update.js') + ); +} + +function hasEntry(compiler: Compiler, name: string): boolean { + const { entry } = compiler.options; + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { + return false; + } + return name in entry; +} + +function hasSourceAccessor(value: unknown): value is LepusChunkCompiledAsset { + if (!value || typeof value !== 'object') { + return false; + } + const source = (value as { source?: unknown }).source; + if (!source || typeof source !== 'object') { + return false; + } + const sourceGetter = (source as { source?: unknown }).source; + return typeof sourceGetter === 'function'; +} + +export { + createIsolatedLepusChunkSource, + createLepusChunkPipeline, + excludeLepusChunksFromTemplate, + getLepusChunkGeneratedAssetNames, + getLepusChunkPrimaryJsAsset, + injectLepusChunkEntries, +}; +export type { LepusChunkPipelineItem }; diff --git a/packages/webpack/react-webpack-plugin/src/loaders/background.ts b/packages/webpack/react-webpack-plugin/src/loaders/background.ts index f70ac61dad..5eb437edd3 100644 --- a/packages/webpack/react-webpack-plugin/src/loaders/background.ts +++ b/packages/webpack/react-webpack-plugin/src/loaders/background.ts @@ -7,6 +7,10 @@ import type { LoaderDefinitionFunction } from '@rspack/core'; import { getBackgroundTransformOptions } from './options.js'; import type { ReactLoaderOptions } from './options.js'; +import { + detectWorkletUsage, + setModuleWorkletUsage, +} from '../workletMetadata.js'; const backgroundLoader: LoaderDefinitionFunction = function( content, @@ -30,6 +34,8 @@ const backgroundLoader: LoaderDefinitionFunction = function( content, getBackgroundTransformOptions.call(this, swcInputSourceMap), ); + const hasWorklet = detectWorkletUsage(result.code); + setModuleWorkletUsage(this, hasWorklet); if (result.errors.length > 0) { for (const error of result.errors) { diff --git a/packages/webpack/react-webpack-plugin/src/loaders/main-thread.ts b/packages/webpack/react-webpack-plugin/src/loaders/main-thread.ts index aa8404f6d1..5f2f8587b7 100644 --- a/packages/webpack/react-webpack-plugin/src/loaders/main-thread.ts +++ b/packages/webpack/react-webpack-plugin/src/loaders/main-thread.ts @@ -7,6 +7,10 @@ import type { LoaderDefinitionFunction } from '@rspack/core'; import { getMainThreadTransformOptions } from './options.js'; import type { ReactLoaderOptions } from './options.js'; +import { + detectWorkletUsage, + setModuleWorkletUsage, +} from '../workletMetadata.js'; const mainThreadLoader: LoaderDefinitionFunction = function( content, @@ -30,6 +34,8 @@ const mainThreadLoader: LoaderDefinitionFunction = function( content, getMainThreadTransformOptions.call(this, swcInputSourceMap), ); + const hasWorklet = detectWorkletUsage(result.code); + setModuleWorkletUsage(this, hasWorklet); if (result.errors.length > 0) { for (const error of result.errors) { diff --git a/packages/webpack/react-webpack-plugin/src/workletMetadata.ts b/packages/webpack/react-webpack-plugin/src/workletMetadata.ts new file mode 100644 index 0000000000..608aa2b80b --- /dev/null +++ b/packages/webpack/react-webpack-plugin/src/workletMetadata.ts @@ -0,0 +1,38 @@ +// 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. + +const WORKLET_USAGE_BUILD_INFO_KEY = 'lynxHasWorklet'; +const WORKLET_REGISTER_INTERNAL_RE = /\bregisterWorkletInternal\s*\(/; + +interface ModuleWithBuildInfo { + buildInfo?: Record; +} + +interface LoaderContextWithModule { + _module?: ModuleWithBuildInfo; +} + +function detectWorkletUsage(transformedCode: string): boolean { + return WORKLET_REGISTER_INTERNAL_RE.test(transformedCode); +} + +function setModuleWorkletUsage( + loaderContext: unknown, + hasWorklet: boolean, +): void { + const module = (loaderContext as LoaderContextWithModule)._module; + if (!module) { + return; + } + + module.buildInfo ??= {}; + module.buildInfo[WORKLET_USAGE_BUILD_INFO_KEY] = hasWorklet; +} + +function moduleHasWorkletUsage(module: unknown): boolean { + const buildInfo = (module as ModuleWithBuildInfo)?.buildInfo; + return buildInfo?.[WORKLET_USAGE_BUILD_INFO_KEY] === true; +} + +export { detectWorkletUsage, moduleHasWorkletUsage, setModuleWorkletUsage }; diff --git a/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/not-using/index.js b/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/not-using/index.js index adf8037b67..9bf792c556 100644 --- a/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/not-using/index.js +++ b/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/not-using/index.js @@ -2,6 +2,7 @@ // @ts-check import fs from 'node:fs/promises'; +import { existsSync } from 'node:fs'; import path from 'node:path'; import './a.jsx'; @@ -21,3 +22,9 @@ it('should not have worklet-runtime', async () => { expect(json['lepusCode']['lepusChunk']['worklet-runtime']) .toBe(undefined); }); + +it('should not keep compiled worklet-runtime assets when injection is not needed', () => { + const root = path.dirname(__filename); + expect(existsSync(path.join(root, 'worklet-runtime.js'))).toBe(false); + expect(existsSync(path.join(root, 'worklet-runtime.js.map'))).toBe(false); +}); diff --git a/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/standard-path/a.jsx b/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/standard-path/a.jsx new file mode 100644 index 0000000000..deaf91df33 --- /dev/null +++ b/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/standard-path/a.jsx @@ -0,0 +1,13 @@ +export function a2() { + const onTapMT = () => { + 'main thread'; + }; + + return ( + + + hello world + + + ); +} diff --git a/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/standard-path/index.js b/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/standard-path/index.js new file mode 100644 index 0000000000..c695d1bba9 --- /dev/null +++ b/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/standard-path/index.js @@ -0,0 +1,45 @@ +/// +// @ts-check + +import fs from 'node:fs/promises'; +import { existsSync } from 'node:fs'; +import path from 'node:path'; + +import './a.jsx'; + +it('should keep worklet-runtime injection name unchanged', async () => { + const source = await fs.readFile( + path.resolve( + path.join( + path.dirname(__filename), + '.rspeedy', + 'tasm.json', + ), + ), + 'utf-8', + ); + const json = JSON.parse(source); + expect(json['lepusCode']['lepusChunk']['worklet-runtime'].length > 0) + .toBe(true); +}); + +it('should not keep compiled worklet-runtime assets in final output when injection is needed', () => { + const root = path.dirname(__filename); + expect(existsSync(path.join(root, 'worklet-runtime.js'))).toBe(false); + expect(existsSync(path.join(root, 'worklet-runtime.js.map'))).toBe(false); +}); + +it('should inject worklet-runtime from compiled asset content', async () => { + const root = path.dirname(__filename); + const templateSource = await fs.readFile( + path.join(root, '.rspeedy', 'tasm.json'), + 'utf-8', + ); + + const templateJson = JSON.parse(templateSource); + const injected = templateJson['lepusCode']['lepusChunk']['worklet-runtime']; + expect(injected.includes('sourceMappingURL=worklet-runtime.js.map')).toBe( + true, + ); + expect(injected.startsWith('(function(){')).toBe(true); +}); diff --git a/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/standard-path/rspack.config.js b/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/standard-path/rspack.config.js new file mode 100644 index 0000000000..07b489a806 --- /dev/null +++ b/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/standard-path/rspack.config.js @@ -0,0 +1,31 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { createConfig } from '../../../create-react-config.js'; +import { + LynxEncodePlugin, + LynxTemplatePlugin, +} from '@lynx-js/template-webpack-plugin'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const defaultConfig = createConfig({}, { + mainThreadChunks: ['main__main-thread.js'], +}, {}); + +/** @type {import('@rspack/core').Configuration} */ +export default { + context: __dirname, + mode: 'production', + devtool: 'source-map', + ...defaultConfig, + plugins: [ + ...defaultConfig.plugins, + new LynxEncodePlugin(), + new LynxTemplatePlugin({ + chunks: ['main__main-thread', 'main__background'], + filename: 'main/template.json', + intermediate: '.rspeedy', + }), + ], +}; diff --git a/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/standard-path/test.config.cjs b/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/standard-path/test.config.cjs new file mode 100644 index 0000000000..a9b8c33fb3 --- /dev/null +++ b/packages/webpack/react-webpack-plugin/test/cases/worklet-runtime/standard-path/test.config.cjs @@ -0,0 +1,7 @@ +/** @type {import("@lynx-js/test-tools").TConfigCaseConfig} */ +module.exports = { + bundlePath: [ + 'main__main-thread.js', + 'main__background.js', + ], +};