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/flat-hats-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/chunk-loading-webpack-plugin": patch
---

Add `StartupChunkDependenciesRuntimeModule` to fix `RuntimeGlobals.ensureChunkHandler` not found when using chunk splitting
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import { RuntimeGlobals as LynxRuntimeGlobals } from '@lynx-js/webpack-runtime-g

import { createChunkLoadingRuntimeModule } from './ChunkLoadingRuntimeModule.js';
import { createCssChunkLoadingRuntimeModule } from './CssChunkLoadingRuntimeModule.js';
import { StartupChunkDependenciesPlugin } from './StartupChunkDependenciesPlugin.js';

import type { ChunkLoadingWebpackPluginOptions } from './index.js';

export class ChunkLoadingWebpackPluginImpl {
name = 'ChunkLoadingWebpackPlugin';
_asyncChunkLoading = true;

static chunkLoadingValue = 'lynx';

Expand All @@ -34,6 +36,11 @@ export class ChunkLoadingWebpackPluginImpl {
return;
}

new StartupChunkDependenciesPlugin({
chunkLoading: ChunkLoadingWebpackPluginImpl.chunkLoadingValue,
asyncChunkLoading: this._asyncChunkLoading,
}).apply(compiler);

// javascript chunk loading
compiler.hooks.thisCompilation.tap(this.name, (compilation) => {
const ChunkLoadingRuntimeModule = createChunkLoadingRuntimeModule(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// 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 { Chunk, Compiler } from 'webpack';

import { createStartupChunkDependenciesRuntimeModule } from './StartupChunkDependenciesRuntimeModule.js';
import { createStartupEntrypointRuntimeModule } from './StartupEntrypointRuntimeModule.js';

/**
* The options for StartupChunkDependenciesPlugin
*/
interface StartupChunkDependenciesPluginOptions {
/**
* Specifies the chunk loading method
* @defaultValue 'lynx'
* @remarks Currently only 'lynx' is supported
*/
chunkLoading: string;

/**
* Whether to enable async chunk loading
* @defaultValue true
* @remarks Currently only async loading mode is supported
*/
asyncChunkLoading: boolean;
}

const PLUGIN_NAME = 'StartupChunkDependenciesPlugin';

export class StartupChunkDependenciesPlugin {
chunkLoading: string;
asyncChunkLoading: boolean;

constructor(
public options: StartupChunkDependenciesPluginOptions,
) {
this.chunkLoading = options.chunkLoading;
this.asyncChunkLoading = typeof options.asyncChunkLoading === 'boolean'
? options.asyncChunkLoading
: true;
}

apply(compiler: Compiler): void {
const { RuntimeGlobals } = compiler.webpack;

const StartupChunkDependenciesRuntimeModule =
createStartupChunkDependenciesRuntimeModule(
compiler.webpack,
);

const StartupEntrypointRuntimeModule = createStartupEntrypointRuntimeModule(
compiler.webpack,
);

compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
const globalChunkLoading = compilation.outputOptions.chunkLoading;

const isEnabledForChunk = (chunk: Chunk): boolean => {
const options = chunk.getEntryOptions();
const chunkLoading = options && options.chunkLoading !== undefined
? options.chunkLoading
: globalChunkLoading;
return chunkLoading === this.chunkLoading;
};

compilation.hooks.additionalTreeRuntimeRequirements.tap(
PLUGIN_NAME,
(chunk, set) => {
if (!isEnabledForChunk(chunk)) return;

if (compilation.chunkGraph.hasChunkEntryDependentChunks(chunk)) {
set.add(RuntimeGlobals.startup);
set.add(RuntimeGlobals.ensureChunk);
set.add(RuntimeGlobals.ensureChunkIncludeEntries);
compilation.addRuntimeModule(
chunk,
new StartupChunkDependenciesRuntimeModule(this.asyncChunkLoading),
);
}
},
);

compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.startupEntrypoint)
.tap(PLUGIN_NAME, (chunk, set) => {
if (!isEnabledForChunk(chunk)) return;

set.add(RuntimeGlobals.require);
set.add(RuntimeGlobals.ensureChunk);
set.add(RuntimeGlobals.ensureChunkIncludeEntries);
compilation.addRuntimeModule(
chunk,
new StartupEntrypointRuntimeModule(this.asyncChunkLoading),
);
});
});
Comment thread
gaoachao marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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 { RuntimeModule } from 'webpack';

type StartupChunkDependenciesRuntimeModule = new(
asyncChunkLoading: boolean,
) => RuntimeModule;

const runtimeTemplateBasicFunction = (args: string, body: string[]) => {
return `(${args}) => {\n${body.join('\n')}\n}`;
};

export function createStartupChunkDependenciesRuntimeModule(
webpack: typeof import('webpack'),
): StartupChunkDependenciesRuntimeModule {
const { RuntimeGlobals, RuntimeModule, Template } = webpack;
return class StartupChunkDependenciesRuntimeModule extends RuntimeModule {
asyncChunkLoading: boolean;

constructor(asyncChunkLoading: boolean) {
super('Lynx startup chunk dependencies', RuntimeModule.STAGE_ATTACH);
this.asyncChunkLoading = asyncChunkLoading;
}

override generate(): string {
const chunkGraph = this.chunkGraph!;
const chunk = this.chunk!;
const chunkIds = Array.from(
chunkGraph.getChunkEntryDependentChunksIterable(chunk),
).map(chunk => chunk.id);
let startupCode: string[];

if (this.asyncChunkLoading === false) {
startupCode = chunkIds
.map(id => `${RuntimeGlobals.ensureChunk}(${JSON.stringify(id)});`)
.concat('return next();');
// lazy bundle can't exports Promise
// TODO: handle Promise in lazy bundle exports to support chunk splitting
} else if (chunkIds.length === 0) {
startupCode = ['return next();'];
} else if (chunkIds.length === 1) {
startupCode = [
`return ${RuntimeGlobals.ensureChunk}(${
JSON.stringify(chunkIds[0])
}).then(next);`,
];
} else if (chunkIds.length > 2) {
startupCode = [
`return Promise.all(${
JSON.stringify(chunkIds)
}.map(${RuntimeGlobals.ensureChunk}, ${RuntimeGlobals.require})).then(next);`,
];
} else {
startupCode = [
'return Promise.all([',
Template.indent(
chunkIds.map(id =>
`${RuntimeGlobals.ensureChunk}(${JSON.stringify(id)})`
).join(',\n'),
),
']).then(next);',
];
}

return Template.asString([
`var next = ${RuntimeGlobals.startup};`,
`${RuntimeGlobals.startup} = ${
runtimeTemplateBasicFunction(
'',
startupCode,
)
};`,
]);
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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 { RuntimeModule } from 'webpack';

type StartupEntrypointRuntimeModule = new(
asyncChunkLoading: boolean,
) => RuntimeModule;

const runtimeTemplateBasicFunction = (args: string, body: string[]) => {
return `(${args}) => {\n${body.join('\n')}\n}`;
};

const runtimeTemplateReturningFunction = (returnValue: string, args = '') => {
return `(${args}) => (${returnValue})`;
};

export function createStartupEntrypointRuntimeModule(
webpack: typeof import('webpack'),
): StartupEntrypointRuntimeModule {
const { RuntimeGlobals, RuntimeModule } = webpack;
return class StartupEntrypointRuntimeModule extends RuntimeModule {
asyncChunkLoading: boolean;

constructor(asyncChunkLoading: boolean) {
super('Lynx startup entrypoint', RuntimeModule.STAGE_ATTACH);
this.asyncChunkLoading = asyncChunkLoading;
}

override generate(): string {
return `${RuntimeGlobals.startupEntrypoint} = ${
runtimeTemplateBasicFunction('result, chunkIds, fn', [
'// arguments: chunkIds, moduleId are deprecated',
'var moduleId = chunkIds;',
`if(!fn) chunkIds = result, fn = ${
runtimeTemplateReturningFunction(
`${RuntimeGlobals.require}(${RuntimeGlobals.entryModuleId} = moduleId)`,
)
};`,
...(this.asyncChunkLoading
? [
`return Promise.all(chunkIds.map(${RuntimeGlobals.ensureChunk}, ${RuntimeGlobals.require})).then(${
runtimeTemplateBasicFunction('', [
'var r = fn();',
'return r === undefined ? result : r;',
])
})`,
]
: [
`chunkIds.map(${RuntimeGlobals.ensureChunk}, ${RuntimeGlobals.require})`,
'var r = fn();',
'return r === undefined ? result : r;',
]),
])
}`;
}
};
}
Loading