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
7 changes: 7 additions & 0 deletions .changeset/light-donuts-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@lynx-js/template-webpack-plugin": minor
Comment thread
gaoachao marked this conversation as resolved.
---

Fixed an issue in rspeedy where chunks weren't being properly shared between multiple entries during chunk splitting. This resulted in duplicate chunks being generated, increasing bundle size and reducing performance.

**BREAKING CHANGE**: `encodeData.manifest` now only contains the `/app-service.js` entry.
9 changes: 9 additions & 0 deletions examples/react/lynx.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin';
const enableBundleAnalysis = !!process.env['RSPEEDY_BUNDLE_ANALYSIS'];

export default defineConfig({
// performance: {
// chunkSplit: {
// strategy: 'split-by-experience',
// override: {
// // See: https://github.com/web-infra-dev/rspack/issues/9812
// filename: '[name].[contenthash:8].js',
// },
// },
// },
plugins: [
pluginReactLynx(),
pluginQRCode({
Expand Down
2 changes: 1 addition & 1 deletion packages/webpack/css-extract-webpack-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"webpack": "^5.99.8"
},
"peerDependencies": {
"@lynx-js/template-webpack-plugin": "^0.5.0 || ^0.6.0"
"@lynx-js/template-webpack-plugin": "^0.5.0 || ^0.6.0 || ^0.7.0"
},
"engines": {
"node": ">=18"
Expand Down
2 changes: 1 addition & 1 deletion packages/webpack/react-webpack-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
},
"peerDependencies": {
"@lynx-js/react": "^0.103.0 || ^0.104.0 || ^0.105.0 || ^0.106.0 || ^0.107.0 || ^0.108.0",
"@lynx-js/template-webpack-plugin": "^0.4.0 || ^0.5.0 || ^0.6.0"
"@lynx-js/template-webpack-plugin": "^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0"
},
"peerDependenciesMeta": {
"@lynx-js/react": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,20 @@ it('should have module.exports in foo.js template', async () => {
expect.stringContaining('function (globDynamicComponentEntry)'),
);

expect(tasmJSON.manifest['/.rspeedy/async/./foo.js-react:background.js'])
.toBeDefined();
expect(tasmJSON.manifest['/.rspeedy/async/./foo.js-react:background.js']).not
.toContain('const module = { exports: {} }');
expect(tasmJSON.manifest['/app-service.js']).toBeDefined();
expect(tasmJSON.manifest['/.rspeedy/async/./foo.js-react:background.js']).not
.toContain('function (globDynamicComponentEntry)');
.toBeDefined();
expect(tasmJSON.manifest['/app-service.js']).contain(
`lynx.requireModule('/.rspeedy/async/./foo.js-react:background.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);

const outputFoo = await readFile(
resolve(__dirname, '.rspeedy/async/./foo.js-react:background.js'),
'utf-8',
);

expect(outputFoo).not.toContain('const module = { exports: {} }');
expect(outputFoo).not.toContain('function (globDynamicComponentEntry)');
});

it('should have module.exports in bar.js template', async () => {
Expand All @@ -78,12 +86,20 @@ it('should have module.exports in bar.js template', async () => {
expect.stringContaining('function (globDynamicComponentEntry)'),
);

expect(tasmJSON.manifest['/.rspeedy/async/./bar.js-react:background.js'])
.toBeDefined();
expect(tasmJSON.manifest['/app-service.js']).toBeDefined();
expect(tasmJSON.manifest['/.rspeedy/async/./bar.js-react:background.js']).not
.toContain('const module = { exports: {} }');
expect(tasmJSON.manifest['/.rspeedy/async/./bar.js-react:background.js']).not
.toContain('function (globDynamicComponentEntry)');
.toBeDefined();
expect(tasmJSON.manifest['/app-service.js']).contain(
`lynx.requireModule('/.rspeedy/async/./bar.js-react:background.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);

const outputBar = await readFile(
resolve(__dirname, '.rspeedy/async/./bar.js-react:background.js'),
'utf-8',
);

expect(outputBar).not.toContain('const module = { exports: {} }');
expect(outputBar).not.toContain('function (globDynamicComponentEntry)');
});

it('should have module.exports in baz.js template', async () => {
Expand All @@ -103,10 +119,18 @@ it('should have module.exports in baz.js template', async () => {
expect.stringContaining('function (globDynamicComponentEntry)'),
);

expect(tasmJSON.manifest['/.rspeedy/async/./baz.js-react:background.js'])
.toBeDefined();
expect(tasmJSON.manifest['/.rspeedy/async/./baz.js-react:background.js']).not
.toContain('const module = { exports: {} }');
expect(tasmJSON.manifest['/app-service.js']).toBeDefined();
expect(tasmJSON.manifest['/.rspeedy/async/./baz.js-react:background.js']).not
.toContain('function (globDynamicComponentEntry)');
.toBeDefined();
expect(tasmJSON.manifest['/app-service.js']).contain(
`lynx.requireModule('/.rspeedy/async/./baz.js-react:background.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);

const outputBaz = await readFile(
resolve(__dirname, '.rspeedy/async/./baz.js-react:background.js'),
'utf-8',
);

expect(outputBaz).not.toContain('const module = { exports: {} }');
expect(outputBaz).not.toContain('function (globDynamicComponentEntry)');
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,20 @@ it('should have module.exports in foo.js template', async () => {
expect.stringContaining('function (globDynamicComponentEntry)'),
);

expect(tasmJSON.manifest['/.rspeedy/async/./foo.js-react:background.js'])
.toBeDefined();
expect(tasmJSON.manifest['/.rspeedy/async/./foo.js-react:background.js']).not
.toContain('const module = { exports: {} }');
expect(tasmJSON.manifest['/app-service.js']).toBeDefined();
expect(tasmJSON.manifest['/.rspeedy/async/./foo.js-react:background.js']).not
.toContain('function (globDynamicComponentEntry)');
.toBeDefined();
expect(tasmJSON.manifest['/app-service.js']).contain(
`lynx.requireModule('/.rspeedy/async/./foo.js-react:background.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);

const outputFoo = await readFile(
resolve(__dirname, '.rspeedy/async/./foo.js-react:background.js'),
'utf-8',
);

expect(outputFoo).not.toContain('const module = { exports: {} }');
expect(outputFoo).not.toContain('function (globDynamicComponentEntry)');
});

it('should have module.exports in bar.js template', async () => {
Expand All @@ -50,12 +58,20 @@ it('should have module.exports in bar.js template', async () => {
expect.stringContaining('function (globDynamicComponentEntry)'),
);

expect(tasmJSON.manifest['/.rspeedy/async/./bar.js-react:background.js'])
.toBeDefined();
expect(tasmJSON.manifest['/.rspeedy/async/./bar.js-react:background.js']).not
.toContain('const module = { exports: {} }');
expect(tasmJSON.manifest['/app-service.js']).toBeDefined();
expect(tasmJSON.manifest['/.rspeedy/async/./bar.js-react:background.js']).not
.toContain('function (globDynamicComponentEntry)');
.toBeDefined();
expect(tasmJSON.manifest['/app-service.js']).contain(
`lynx.requireModule('/.rspeedy/async/./bar.js-react:background.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);

const outputBar = await readFile(
resolve(__dirname, '.rspeedy/async/./bar.js-react:background.js'),
'utf-8',
);

expect(outputBar).not.toContain('const module = { exports: {} }');
expect(outputBar).not.toContain('function (globDynamicComponentEntry)');
});

it('should have module.exports in baz.js template', async () => {
Expand All @@ -75,10 +91,18 @@ it('should have module.exports in baz.js template', async () => {
expect.stringContaining('function (globDynamicComponentEntry)'),
);

expect(tasmJSON.manifest['/.rspeedy/async/./baz.js-react:background.js'])
.toBeDefined();
expect(tasmJSON.manifest['/.rspeedy/async/./baz.js-react:background.js']).not
.toContain('const module = { exports: {} }');
expect(tasmJSON.manifest['/app-service.js']).toBeDefined();
expect(tasmJSON.manifest['/.rspeedy/async/./baz.js-react:background.js']).not
.toContain('function (globDynamicComponentEntry)');
.toBeDefined();
expect(tasmJSON.manifest['/app-service.js']).contain(
`lynx.requireModule('/.rspeedy/async/./baz.js-react:background.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);

const outputBaz = await readFile(
resolve(__dirname, '.rspeedy/async/./baz.js-react:background.js'),
'utf-8',
);

expect(outputBaz).not.toContain('const module = { exports: {} }');
expect(outputBaz).not.toContain('function (globDynamicComponentEntry)');
});
25 changes: 14 additions & 11 deletions packages/webpack/template-webpack-plugin/src/LynxEncodePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,17 @@ export class LynxEncodePluginImpl {
const { encodeData } = args;
const { manifest } = encodeData;

let 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,
Expand All @@ -169,27 +180,19 @@ export class LynxEncodePluginImpl {
// lynx.requireModule('initial-chunk1')
// lynx.requireModule('initial-chunk2')
// `,
// 'initial-chunk1': `<content-of-initial-chunk>`,
// 'initial-chunk2': `<content-of-initial-chunk>`,
// },
// ```
'/app-service.js': [
this.#appServiceBanner(),
Object.keys(manifest)
.map((name) =>
`module.exports=lynx.requireModule('${
Comment thread
gaoachao marked this conversation as resolved.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using lynx.requireModuleAsync when dealing with multiple chunks, as it allows scripts to load in parallel.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this could be done in future PRs. Using a new option like scriptLoading

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: can we avoid the multiple module.exports?

this.#formatJSName(name)
this.#formatJSName(name, publicPath)
}',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`
)
.join(','),
this.#appServiceFooter(),
].join(''),
...(Object.fromEntries(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

directly removing all the manifest would be a huge breaking change (and I don't think we are ready for that).

So maybe add an option like output.inlineScripts?

Object.entries(manifest).map(([name, source]) => [
this.#formatJSName(name),
source,
]),
)),
};

return args;
Expand Down Expand Up @@ -230,8 +233,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<LynxEncodePluginOptions>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,16 @@ it('should generate correct foo template', async () => {

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).toHaveProperty(
'/foo:background.rspack.bundle.js',
expect.stringContaining('function foo()'),

expect(manifest['/app-service.js']).toContain(
`lynx.requireModule('/foo:background.rspack.bundle.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ it('should have correct tasm.json', async () => {
);
expect(fs.existsSync(target));

const content = await fs.promises.readFile(target, 'utf-8');
const output = path.resolve(__dirname, 'a.js');
expect(fs.existsSync(output));
const outputContent = await fs.promises.readFile(output, 'utf-8');
expect(outputContent).toContain(['**', 'aaa', '**'].join(''));

const content = await fs.promises.readFile(target, 'utf-8');
const { manifest } = JSON.parse(content);

expect(manifest['/a/a.js']).toContain(['**', 'aaa', '**'].join(''));
expect(manifest['/app-service.js']).toContain(
`lynx.requireModule('/a/a.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);
});

it('should generate correct bundle', async () => {
Expand All @@ -47,11 +53,22 @@ it('should generate correct bundle', async () => {
expect([foo, bar, baz].every(p => fs.existsSync(p))).toBeTruthy();

const fooContent = await fs.promises.readFile(foo, 'utf-8');
expect(fooContent).toContain('function foo()');
expect(fooContent).toContain(
`lynx.requireModule('/foo/foo.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);

const barContent = await fs.promises.readFile(bar, 'utf-8');
expect(barContent).toContain('function bar()');
expect(barContent).toContain(
`lynx.requireModule('/bar/bar.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);

const bazContent = await fs.promises.readFile(baz, 'utf-8');
expect(bazContent).toContain('function baz()');
expect(bazContent).toContain(
`lynx.requireModule('/baz/baz.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);

const asyncTemplates = await fs.promises.readdir(
path.resolve(__dirname, '../async'),
);
expect(asyncTemplates).toHaveLength(3); // foo, bar, baz
});
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ it('should have correct tasm.json', async () => {
);
expect(fs.existsSync(target));

const content = await fs.promises.readFile(target, 'utf-8');
const output = path.resolve(__dirname, 'b.js');
expect(fs.existsSync(output));
const outputContent = await fs.promises.readFile(output, 'utf-8');
expect(outputContent).toContain(['**', 'bbb', '**'].join(''));

const content = await fs.promises.readFile(target, 'utf-8');
const { manifest } = JSON.parse(content);

expect(manifest['/b/b.js']).toContain(['**', 'bbb', '**'].join(''));
expect(manifest['/app-service.js']).toContain(
`lynx.requireModule('/b/b.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);
});

it('should generate correct bundle', async () => {
Expand All @@ -47,13 +53,19 @@ it('should generate correct bundle', async () => {
expect([foo, bar, baz].every(p => fs.existsSync(p))).toBeTruthy();

const fooContent = await fs.promises.readFile(foo, 'utf-8');
expect(fooContent).toContain('function foo()');
expect(fooContent).toContain(
`lynx.requireModule('/foo/foo.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);

const barContent = await fs.promises.readFile(bar, 'utf-8');
expect(barContent).toContain('function bar()');
expect(barContent).toContain(
`lynx.requireModule('/bar/bar.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);

const bazContent = await fs.promises.readFile(baz, 'utf-8');
expect(bazContent).toContain('function baz()');
expect(bazContent).toContain(
`lynx.requireModule('/baz/baz.js',globDynamicComponentEntry?globDynamicComponentEntry:'__Card__')`,
);

const asyncTemplates = await fs.promises.readdir(
path.resolve(__dirname, '../async'),
Expand Down
Loading
Loading