Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/green-tips-bake.md
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 7 additions & 0 deletions .changeset/silent-cars-trade.md
Original file line number Diff line number Diff line change
@@ -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.
52 changes: 43 additions & 9 deletions packages/rspeedy/plugin-react/src/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> }
>(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')
Expand Down Expand Up @@ -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).
Comment thread
upupming marked this conversation as resolved.
test: createRuntimeWrapperTestPattern(
lepusChunkNames,
),
experimental_isLazyBundle,
}])
.end()
Expand All @@ -261,10 +273,6 @@ export function applyEntry(
extractStr = false
}

const { resolve } = api.useExposed<
{ resolve: (request: string) => Promise<string> }
>(Symbol.for('@lynx-js/react/internal:resolve'))!

chain
.plugin(PLUGIN_NAME_REACT)
.after(PLUGIN_NAME_TEMPLATE)
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
34 changes: 34 additions & 0 deletions packages/rspeedy/plugin-react/test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
65 changes: 54 additions & 11 deletions packages/webpack/react-webpack-plugin/src/ReactWebpackPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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);
}
}
}
},
);
});
}

Expand Down
Loading
Loading