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/fifty-rats-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/react-rsbuild-plugin": patch
---

Avoid IIFE in `main-thread.js` to resolve memory leak when using `<list />`.
5 changes: 5 additions & 0 deletions .changeset/loose-zoos-say.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/runtime-wrapper-webpack-plugin": patch
---

Wrap with IIFE when `output.iife: false` to avoid naming conflict.
8 changes: 8 additions & 0 deletions packages/rspeedy/plugin-react/src/pluginReactLynx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,14 @@ export function pluginReactLynx(
})
}

// This is used to avoid the IIFE in main-thread.js, which would cause memory leak.
// TODO: remove this when required Rspeedy version bumped to ^0.10.0
config = mergeRsbuildConfig({
tools: {
rspack: { output: { iife: false } },
},
}, config)

return config
})

Expand Down
60 changes: 60 additions & 0 deletions packages/rspeedy/plugin-react/test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,66 @@ describe('Config', () => {
})
})

describe('Output IIFE', () => {
test('defaults', async () => {
const { pluginReactLynx } = await import('../src/pluginReactLynx.js')
const rspeedy = await createRspeedy({
rspeedyConfig: {
plugins: [
pluginReactLynx(),
],
},
})

const [config] = await rspeedy.initConfigs()

expect(config?.output?.iife).toBe(false)
})

test('with output.iife: false', async () => {
const { pluginReactLynx } = await import('../src/pluginReactLynx.js')
const rspeedy = await createRspeedy({
rspeedyConfig: {
tools: {
rspack: {
output: {
iife: false,
},
},
},
plugins: [
pluginReactLynx(),
],
},
})

const [config] = await rspeedy.initConfigs()

expect(config?.output?.iife).toBe(false)
})
test('with output.iife: true', async () => {
const { pluginReactLynx } = await import('../src/pluginReactLynx.js')
const rspeedy = await createRspeedy({
rspeedyConfig: {
tools: {
rspack: {
output: {
iife: true,
},
},
},
plugins: [
pluginReactLynx(),
],
},
})

const [config] = await rspeedy.initConfigs()

expect(config?.output?.iife).toBe(true)
})
})

describe('Bundle Splitting', () => {
test('default', async () => {
const { pluginReactLynx } = await import('../src/pluginReactLynx.js')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ class RuntimeWrapperWebpackPluginImpl {
if (typeof options.injectVars === 'function') {
injectStr = options.injectVars(defaultInjectVars).join(',');
}
const iife = compiler.options.output.iife ?? true;

// banner
new BannerPlugin({
Expand All @@ -168,6 +169,7 @@ class RuntimeWrapperWebpackPluginImpl {
overrideRuntimePromise: true,
moduleId: '[name].js',
targetSdkVersion,
iife,
})
// In standalone lazy bundle mode, the lazy bundle will
// also has chunk.id "main", it will be conflict with the
Expand All @@ -189,7 +191,7 @@ class RuntimeWrapperWebpackPluginImpl {
const footer = this.#getBannerType(filename) === 'script'
? loadScriptFooter
: loadBundleFooter;
return amdFooter('[name].js') + footer;
return amdFooter('[name].js', iife) + footer;
},
}).apply(compiler);
}
Expand Down Expand Up @@ -253,12 +255,18 @@ const amdBanner = ({
moduleId,
overrideRuntimePromise,
targetSdkVersion,
iife,
}: {
injectStr: string;
moduleId: string;
overrideRuntimePromise: boolean;
targetSdkVersion: string;
iife: boolean;
}) => {
const iifeWrapper = iife ? '' : `
// This needs to be wrapped in an IIFE because it needs to be isolated against Lynx injected variables.
(() => {`;

return (
`
tt.define("${moduleId}", function(require, module, exports, ${injectStr}) {
Expand All @@ -270,11 +278,13 @@ ${overrideRuntimePromise ? `var Promise = lynx.Promise;` : ''}
fetch = fetch || lynx.fetch;
requestAnimationFrame = requestAnimationFrame || lynx.requestAnimationFrame;
cancelAnimationFrame = cancelAnimationFrame || lynx.cancelAnimationFrame;
${iifeWrapper}
`
);
};

const amdFooter = (moduleId: string) => `
const amdFooter = (moduleId: string, iife: boolean) => `
${iife ? '' : '})();'}
});
return tt.require("${moduleId}");`;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const fetch = function fetch() {
return 42;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2025 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 { fetch as myFetch } from './fetch.js';

expect(myFetch).not.toBe(lynx.fetch);

expect(myFetch()).toBe(42);

const fetch = () => {
return 42;
};

expect(fetch()).toBe(42);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
// 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 config from './webpack.config.js';

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
// 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 { RuntimeWrapperWebpackPlugin } from '../../../../src';

/** @type {import('webpack').Configuration} */
export default {
plugins: [
new RuntimeWrapperWebpackPlugin(),
],
output: {
iife: false,
},
optimization: {
avoidEntryIife: true,
concatenateModules: true,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const fetch = function fetch() {
return 42;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2025 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 { fetch as myFetch } from './fetch.js';

expect(myFetch).not.toBe(lynx.fetch);

expect(myFetch()).toBe(42);

const fetch = () => {
return 42;
};

expect(fetch()).toBe(42);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
// 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 config from './webpack.config.js';

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
// 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 { RuntimeWrapperWebpackPlugin } from '../../../../src';

/** @type {import('webpack').Configuration} */
export default {
plugins: [
new RuntimeWrapperWebpackPlugin(),
],
output: {
iife: true,
},
optimization: {
avoidEntryIife: true,
concatenateModules: true,
},
};
Loading