Skip to content
Merged
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/little-candles-brake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/react-webpack-plugin": patch
---

Avoid wrapping standalone lazy bundles with `var globDynamicComponentEntry`.
5 changes: 5 additions & 0 deletions .changeset/violet-shoes-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/template-webpack-plugin": patch
---

The code of lazy bundle should be minimized.
1 change: 1 addition & 0 deletions packages/rspeedy/plugin-react/src/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ export function applyEntry(
?.disableCreateSelectorQueryIncompatibleWarning ?? false,
firstScreenSyncTiming,
mainThreadChunks,
experimental_isLazyBundle,
}])
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export class ReactWebpackPlugin {
// @public
export interface ReactWebpackPluginOptions {
disableCreateSelectorQueryIncompatibleWarning?: boolean | undefined;
// @alpha
experimental_isLazyBundle?: boolean;
firstScreenSyncTiming?: 'immediately' | 'jsReady';
mainThreadChunks?: string[] | undefined;
}
Expand Down
24 changes: 17 additions & 7 deletions packages/webpack/react-webpack-plugin/src/ReactWebpackPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ interface ReactWebpackPluginOptions {
* The chunk names to be considered as main thread chunks.
*/
mainThreadChunks?: string[] | undefined;

/**
* Whether to enable lazy bundle.
*
* @alpha
*/
experimental_isLazyBundle?: boolean;
}

/**
Expand Down Expand Up @@ -105,6 +112,7 @@ class ReactWebpackPlugin {
disableCreateSelectorQueryIncompatibleWarning: false,
firstScreenSyncTiming: 'immediately',
mainThreadChunks: [],
experimental_isLazyBundle: false,
});

/**
Expand All @@ -119,13 +127,15 @@ class ReactWebpackPlugin {
);
const { BannerPlugin, DefinePlugin, EnvironmentPlugin } = compiler.webpack;

new BannerPlugin({
// TODO: handle cases that do not have `'use strict'`
banner:
`'use strict';var globDynamicComponentEntry=globDynamicComponentEntry||'__Card__';`,
raw: true,
test: options.mainThreadChunks!,
}).apply(compiler);
if (!options.experimental_isLazyBundle) {
new BannerPlugin({
// TODO: handle cases that do not have `'use strict'`
banner:
`'use strict';var globDynamicComponentEntry=globDynamicComponentEntry||'__Card__';`,
raw: true,
test: options.mainThreadChunks!,
}).apply(compiler);
}

new EnvironmentPlugin({
// Default values of null and undefined behave differently.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function bar() {
expect(globDynamicComponentEntry).toBeUndefined();
return import('./baz.js').then(({ baz }) => {
return `bar ${baz()}`;
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export function baz() {
expect(globDynamicComponentEntry).toBeUndefined();
return 'baz';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export async function foo() {
expect(globDynamicComponentEntry).toBeUndefined();
const { bar } = await import('./bar.js');
return 'foo ' + await bar();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/// <reference types="vitest/globals" />

import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';

import { snapshotManager } from '@lynx-js/react/internal';

const importPromise = import('./foo.js');

it('should have globDynamicComponentEntry', async () => {
const tasmJSON = JSON.parse(
await readFile(
resolve(__dirname, '.rspeedy/main/tasm.json'),
'utf-8',
),
);

const mainThreadCode = tasmJSON.lepusCode['root'];
const backgroundCode = await readFile(
__filename,
'utf-8',
);
// main-thread.js has one more globDynamicComponentEntry than background.js
// at the parameter of the IIFE.
expect(mainThreadCode.split('globDynamicComponentEntry').length)
.toBeGreaterThan(backgroundCode.split('globDynamicComponentEntry').length);

const jsx = <view id='xxx' />;
expect(jsx).toBeDefined();

expect(snapshotManager.values.get(jsx.type).entryName).toBe(
globDynamicComponentEntry,
);
});

it('should have module.exports in foo.js template', async () => {
const { foo } = await importPromise;
await expect(foo()).resolves.toBe(`foo bar baz`);

const tasmJSON = JSON.parse(
await readFile(
resolve(__dirname, '.rspeedy/async/foo.js/tasm.json'),
'utf-8',
),
);

expect(tasmJSON.lepusCode).toHaveProperty(
'root',
expect.stringContaining('const module = { exports: {} }'),
);
expect(tasmJSON.lepusCode).toHaveProperty(
'root',
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['/.rspeedy/async/./foo.js-react:background.js']).not
.toContain('function (globDynamicComponentEntry)');
});

it('should have module.exports in bar.js template', async () => {
const tasmJSON = JSON.parse(
await readFile(
resolve(__dirname, '.rspeedy/async/bar.js/tasm.json'),
'utf-8',
),
);

expect(tasmJSON.lepusCode).toHaveProperty(
'root',
expect.stringContaining('const module = { exports: {} }'),
);
expect(tasmJSON.lepusCode).toHaveProperty(
'root',
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['/.rspeedy/async/./bar.js-react:background.js']).not
.toContain('function (globDynamicComponentEntry)');
});

it('should have module.exports in baz.js template', async () => {
const tasmJSON = JSON.parse(
await readFile(
resolve(__dirname, '.rspeedy/async/baz.js/tasm.json'),
'utf-8',
),
);

expect(tasmJSON.lepusCode).toHaveProperty(
'root',
expect.stringContaining('const module = { exports: {} }'),
);
expect(tasmJSON.lepusCode).toHaveProperty(
'root',
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['/.rspeedy/async/./baz.js-react:background.js']).not
.toContain('function (globDynamicComponentEntry)');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
LynxEncodePlugin,
LynxTemplatePlugin,
} from '@lynx-js/template-webpack-plugin';

import { createConfig } from '../../../create-react-config.js';

const config = createConfig();

/** @type {import('@rspack/core').Configuration} */
export default {
context: __dirname,
...config,
output: {
...config.output,
chunkFilename: '.rspeedy/async/[name].js',
},
plugins: [
...config.plugins,
new LynxEncodePlugin(),
new LynxTemplatePlugin({
...LynxTemplatePlugin.defaultOptions,
experimental_isLazyBundle: true,
chunks: ['main:main-thread', 'main:background'],
filename: 'main/template.js',
intermediate: '.rspeedy/main',
}),
],
mode: 'production',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import("@lynx-js/test-tools").TConfigCaseConfig} */
module.exports = {
bundlePath: [
// We do not run main-thread.js since the async chunk has been modified.
// 'main:main-thread.js',
'main:background.js',
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function bar() {
expect(globDynamicComponentEntry).toBeUndefined();
return import('./baz.js').then(({ baz }) => {
return `bar ${baz()}`;
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export function baz() {
expect(globDynamicComponentEntry).toBeUndefined();
return 'baz';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export async function foo() {
expect(globDynamicComponentEntry).toBeUndefined();
const { bar } = await import('./bar.js');
return 'foo ' + await bar();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/// <reference types="vitest/globals" />

import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';

const importPromise = import('./foo.js');

it('should have module.exports in foo.js template', async () => {
const { foo } = await importPromise;
await expect(foo()).resolves.toBe(`foo bar baz`);

const tasmJSON = JSON.parse(
await readFile(
resolve(__dirname, '.rspeedy/async/foo.js/tasm.json'),
'utf-8',
),
);

expect(tasmJSON.lepusCode).toHaveProperty(
'root',
expect.stringContaining('const module = { exports: {} }'),
);
expect(tasmJSON.lepusCode).toHaveProperty(
'root',
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['/.rspeedy/async/./foo.js-react:background.js']).not
.toContain('function (globDynamicComponentEntry)');
});

it('should have module.exports in bar.js template', async () => {
const tasmJSON = JSON.parse(
await readFile(
resolve(__dirname, '.rspeedy/async/bar.js/tasm.json'),
'utf-8',
),
);

expect(tasmJSON.lepusCode).toHaveProperty(
'root',
expect.stringContaining('const module = { exports: {} }'),
);
expect(tasmJSON.lepusCode).toHaveProperty(
'root',
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['/.rspeedy/async/./bar.js-react:background.js']).not
.toContain('function (globDynamicComponentEntry)');
});

it('should have module.exports in baz.js template', async () => {
const tasmJSON = JSON.parse(
await readFile(
resolve(__dirname, '.rspeedy/async/baz.js/tasm.json'),
'utf-8',
),
);

expect(tasmJSON.lepusCode).toHaveProperty(
'root',
expect.stringContaining('const module = { exports: {} }'),
);
expect(tasmJSON.lepusCode).toHaveProperty(
'root',
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['/.rspeedy/async/./baz.js-react:background.js']).not
.toContain('function (globDynamicComponentEntry)');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
LynxEncodePlugin,
LynxTemplatePlugin,
} from '@lynx-js/template-webpack-plugin';

import { createConfig } from '../../../create-react-config.js';

const config = createConfig();

/** @type {import('@rspack/core').Configuration} */
export default {
context: __dirname,
...config,
output: {
...config.output,
chunkFilename: '.rspeedy/async/[name].js',
},
plugins: [
...config.plugins,
new LynxEncodePlugin(),
new LynxTemplatePlugin({
...LynxTemplatePlugin.defaultOptions,
chunks: ['main:main-thread', 'main:background'],
filename: 'main/template.js',
intermediate: '.rspeedy/main',
}),
],
mode: 'production',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import("@lynx-js/test-tools").TConfigCaseConfig} */
module.exports = {
bundlePath: [
// We do not run main-thread.js since the async chunk has been modified.
// 'main:main-thread.js',
'main:background.js',
],
};
Loading