mirror of https://github.com/webpack/webpack.git
support overridables in initial chunks
This commit is contained in:
parent
e08bfc4bd6
commit
e9ae850d66
|
|
@ -62,6 +62,14 @@ class AsyncDependenciesBlock extends DependenciesBlock {
|
|||
super.updateHash(hash, chunkGraph);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ChunkGroup} parentChunkGroup the parent chunk group
|
||||
* @returns {boolean} true when this dependencies block should be loaded async
|
||||
*/
|
||||
isAsync(parentChunkGroup) {
|
||||
return true;
|
||||
}
|
||||
|
||||
serialize(context) {
|
||||
const { write } = context;
|
||||
write(this.groupOptions);
|
||||
|
|
|
|||
|
|
@ -355,6 +355,75 @@ const visitModules = (
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {DependenciesBlock} block the block
|
||||
* @returns {void}
|
||||
*/
|
||||
const processBlock = block => {
|
||||
// get prepared block info
|
||||
const blockModules = blockModulesMap.get(block);
|
||||
|
||||
if (blockModules !== undefined) {
|
||||
const { minAvailableModules } = chunkGroupInfo;
|
||||
// Buffer items because order need to be reversed to get indices correct
|
||||
// Traverse all referenced modules
|
||||
for (const refModule of blockModules) {
|
||||
if (chunkGraph.isModuleInChunk(refModule, chunk)) {
|
||||
// skip early if already connected
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
minAvailableModules.has(refModule) ||
|
||||
minAvailableModules.plus.has(refModule)
|
||||
) {
|
||||
// already in parent chunks, skip it for now
|
||||
skipBuffer.push(refModule);
|
||||
continue;
|
||||
}
|
||||
// enqueue, then add and enter to be in the correct order
|
||||
// this is relevant with circular dependencies
|
||||
queueBuffer.push({
|
||||
action: ADD_AND_ENTER_MODULE,
|
||||
block: refModule,
|
||||
module: refModule,
|
||||
chunk,
|
||||
chunkGroup,
|
||||
chunkGroupInfo
|
||||
});
|
||||
}
|
||||
// Add buffered items in reverse order
|
||||
if (skipBuffer.length > 0) {
|
||||
let { skippedItems } = chunkGroupInfo;
|
||||
if (skippedItems === undefined) {
|
||||
chunkGroupInfo.skippedItems = skippedItems = new Set();
|
||||
}
|
||||
for (let i = skipBuffer.length - 1; i >= 0; i--) {
|
||||
skippedItems.add(skipBuffer[i]);
|
||||
}
|
||||
skipBuffer.length = 0;
|
||||
}
|
||||
if (queueBuffer.length > 0) {
|
||||
for (let i = queueBuffer.length - 1; i >= 0; i--) {
|
||||
queue.push(queueBuffer[i]);
|
||||
}
|
||||
queueBuffer.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse all Blocks
|
||||
for (const b of block.blocks) {
|
||||
if (b.isAsync(chunkGroup)) {
|
||||
iteratorBlock(b);
|
||||
} else {
|
||||
processBlock(b);
|
||||
}
|
||||
}
|
||||
|
||||
if (block.blocks.length > 0 && module !== block) {
|
||||
blocksWithNestedBlocks.add(block);
|
||||
}
|
||||
};
|
||||
|
||||
const processQueue = () => {
|
||||
while (queue.length) {
|
||||
const queueItem = queue.pop();
|
||||
|
|
@ -398,62 +467,7 @@ const visitModules = (
|
|||
}
|
||||
// fallthrough
|
||||
case PROCESS_BLOCK: {
|
||||
// get prepared block info
|
||||
const blockModules = blockModulesMap.get(block);
|
||||
|
||||
if (blockModules !== undefined) {
|
||||
const { minAvailableModules } = chunkGroupInfo;
|
||||
// Buffer items because order need to be reversed to get indices correct
|
||||
// Traverse all referenced modules
|
||||
for (const refModule of blockModules) {
|
||||
if (chunkGraph.isModuleInChunk(refModule, chunk)) {
|
||||
// skip early if already connected
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
minAvailableModules.has(refModule) ||
|
||||
minAvailableModules.plus.has(refModule)
|
||||
) {
|
||||
// already in parent chunks, skip it for now
|
||||
skipBuffer.push(refModule);
|
||||
continue;
|
||||
}
|
||||
// enqueue, then add and enter to be in the correct order
|
||||
// this is relevant with circular dependencies
|
||||
queueBuffer.push({
|
||||
action: ADD_AND_ENTER_MODULE,
|
||||
block: refModule,
|
||||
module: refModule,
|
||||
chunk,
|
||||
chunkGroup,
|
||||
chunkGroupInfo
|
||||
});
|
||||
}
|
||||
// Add buffered items in reverse order
|
||||
if (skipBuffer.length > 0) {
|
||||
let { skippedItems } = chunkGroupInfo;
|
||||
if (skippedItems === undefined) {
|
||||
chunkGroupInfo.skippedItems = skippedItems = new Set();
|
||||
}
|
||||
for (let i = skipBuffer.length - 1; i >= 0; i--) {
|
||||
skippedItems.add(skipBuffer[i]);
|
||||
}
|
||||
skipBuffer.length = 0;
|
||||
}
|
||||
if (queueBuffer.length > 0) {
|
||||
for (let i = queueBuffer.length - 1; i >= 0; i--) {
|
||||
queue.push(queueBuffer[i]);
|
||||
}
|
||||
queueBuffer.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse all Blocks
|
||||
for (const b of block.blocks) iteratorBlock(b);
|
||||
|
||||
if (block.blocks.length > 0 && module !== block) {
|
||||
blocksWithNestedBlocks.add(block);
|
||||
}
|
||||
processBlock(block);
|
||||
break;
|
||||
}
|
||||
case LEAVE_MODULE: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Tobias Koppers @sokra
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const AsyncDependenciesBlock = require("../AsyncDependenciesBlock");
|
||||
|
||||
/** @typedef {import("../ChunkGroup")} ChunkGroup */
|
||||
|
||||
class OverridableDependenciesBlock extends AsyncDependenciesBlock {
|
||||
constructor() {
|
||||
super({});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ChunkGroup} parentChunkGroup the parent chunk group
|
||||
* @returns {boolean} true when this dependencies block should be loaded async
|
||||
*/
|
||||
isAsync(parentChunkGroup) {
|
||||
return !parentChunkGroup.isInitial();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OverridableDependenciesBlock;
|
||||
|
|
@ -6,11 +6,11 @@
|
|||
"use strict";
|
||||
|
||||
const { RawSource } = require("webpack-sources");
|
||||
const AsyncDependenciesBlock = require("../AsyncDependenciesBlock");
|
||||
const Module = require("../Module");
|
||||
const RuntimeGlobals = require("../RuntimeGlobals");
|
||||
const Template = require("../Template");
|
||||
const makeSerializable = require("../util/makeSerializable");
|
||||
const OverridableDependenciesBlock = require("./OverridableDependenciesBlock");
|
||||
const OverridableOriginalDependency = require("./OverridableOriginalDependency");
|
||||
|
||||
/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */
|
||||
|
|
@ -75,7 +75,7 @@ class OverridableModule extends Module {
|
|||
build(options, compilation, resolver, fs, callback) {
|
||||
this.buildMeta = {};
|
||||
this.buildInfo = {};
|
||||
const block = new AsyncDependenciesBlock({});
|
||||
const block = new OverridableDependenciesBlock();
|
||||
const dep = new OverridableOriginalDependency(this.originalRequest);
|
||||
block.addDependency(dep);
|
||||
this.addBlock(block);
|
||||
|
|
@ -123,21 +123,24 @@ class OverridableModule extends Module {
|
|||
chunkGraph,
|
||||
runtimeRequirements
|
||||
});
|
||||
const factory = runtimeTemplate.returningFunction(
|
||||
runtimeTemplate.moduleRaw({
|
||||
module: originalRequest,
|
||||
chunkGraph,
|
||||
request: "",
|
||||
runtimeRequirements
|
||||
})
|
||||
);
|
||||
const sources = new Map();
|
||||
sources.set(
|
||||
"overridable",
|
||||
new RawSource(
|
||||
Template.asString([
|
||||
`return ${ensureChunk}.then(${runtimeTemplate.returningFunction(
|
||||
runtimeTemplate.returningFunction(
|
||||
runtimeTemplate.moduleRaw({
|
||||
module: originalRequest,
|
||||
chunkGraph,
|
||||
request: "",
|
||||
runtimeRequirements
|
||||
})
|
||||
)
|
||||
)})`
|
||||
ensureChunk.startsWith("Promise.resolve(")
|
||||
? `return ${factory};`
|
||||
: `return ${ensureChunk}.then(${runtimeTemplate.returningFunction(
|
||||
factory
|
||||
)});`
|
||||
])
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,13 +22,16 @@ class OverridablesRuntimeModule extends RuntimeModule {
|
|||
generate() {
|
||||
const {
|
||||
runtimeTemplate,
|
||||
moduleGraph,
|
||||
chunkGraph,
|
||||
codeGenerationResults
|
||||
} = this.compilation;
|
||||
const chunkToOverridableMapping = {};
|
||||
const idToNameMapping = {};
|
||||
const overridableToFallbackMapping = new Map();
|
||||
for (const chunk of this.chunk.getAllAsyncChunks()) {
|
||||
const initialOverridables = {};
|
||||
const asyncChunks = this.chunk.getAllAsyncChunks();
|
||||
for (const chunk of asyncChunks) {
|
||||
const modules = chunkGraph.getChunkModulesIterableBySourceType(
|
||||
chunk,
|
||||
"overridable"
|
||||
|
|
@ -47,8 +50,28 @@ class OverridablesRuntimeModule extends RuntimeModule {
|
|||
overridableToFallbackMapping.set(id, source.source());
|
||||
}
|
||||
}
|
||||
for (const chunk of this.chunk.getAllReferencedChunks()) {
|
||||
if (asyncChunks.has(chunk)) continue;
|
||||
const modules = chunkGraph.getChunkModulesIterableBySourceType(
|
||||
chunk,
|
||||
"overridable"
|
||||
);
|
||||
if (!modules) continue;
|
||||
for (const m of modules) {
|
||||
const module = /** @type {OverridableModule} */ (m);
|
||||
const name = module.name;
|
||||
const id = chunkGraph.getModuleId(module);
|
||||
idToNameMapping[id] = name;
|
||||
const fallbackModule = moduleGraph.getModule(
|
||||
module.blocks[0].dependencies[0]
|
||||
);
|
||||
const fallbackId = chunkGraph.getModuleId(fallbackModule);
|
||||
initialOverridables[id] = fallbackId;
|
||||
}
|
||||
}
|
||||
return Template.asString([
|
||||
`${RuntimeGlobals.overrides} = {};`,
|
||||
"var installedModules = {};",
|
||||
`var chunkMapping = ${JSON.stringify(
|
||||
chunkToOverridableMapping,
|
||||
null,
|
||||
|
|
@ -67,18 +90,40 @@ class OverridablesRuntimeModule extends RuntimeModule {
|
|||
).join(",\n")
|
||||
),
|
||||
"};",
|
||||
Object.keys(initialOverridables).length
|
||||
? Template.asString([
|
||||
`var initialOverridables = ${JSON.stringify(
|
||||
initialOverridables,
|
||||
null,
|
||||
"\t"
|
||||
)};`,
|
||||
`for(var id in initialOverridables) if(${
|
||||
RuntimeGlobals.hasOwnProperty
|
||||
}(initialOverridables, id)) __webpack_modules__[id] = (${runtimeTemplate.returningFunction(
|
||||
`${runtimeTemplate.basicFunction("module", [
|
||||
"// Handle case when module is used sync",
|
||||
"installedModules[id] = 0;",
|
||||
`var override = ${RuntimeGlobals.overrides}[idToNameMapping[id]];`,
|
||||
"module.exports = override ? override()() : __webpack_require__(initialOverridables[id]);"
|
||||
])}`,
|
||||
"id"
|
||||
)})(id);`
|
||||
])
|
||||
: "// no overridables in initial chunks",
|
||||
`${
|
||||
RuntimeGlobals.ensureChunkHandlers
|
||||
}.overridables = ${runtimeTemplate.basicFunction("chunkId, promises", [
|
||||
`if(${RuntimeGlobals.hasOwnProperty}(chunkMapping, chunkId)) {`,
|
||||
Template.indent([
|
||||
`chunkMapping[chunkId].forEach(${runtimeTemplate.basicFunction("id", [
|
||||
"if(__webpack_modules__[id]) return;",
|
||||
`promises.push(Promise.resolve((${
|
||||
`promises.push(${
|
||||
RuntimeGlobals.hasOwnProperty
|
||||
}(installedModules, id) ? installedModules[id] : installedModules[id] = Promise.resolve((${
|
||||
RuntimeGlobals.overrides
|
||||
}[idToNameMapping[id]] || fallbackMapping[id])()).then(${runtimeTemplate.basicFunction(
|
||||
"factory",
|
||||
[
|
||||
"installedModules[id] = 0;",
|
||||
`__webpack_modules__[id] = ${runtimeTemplate.basicFunction(
|
||||
"module",
|
||||
["module.exports = factory();"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
module.exports = "original3-cjs";
|
||||
|
|
@ -8,6 +8,7 @@ __webpack_override__({
|
|||
}));
|
||||
}, 100);
|
||||
}),
|
||||
test3: () => () => "overriden3",
|
||||
package: () =>
|
||||
new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
|
|
@ -31,6 +32,11 @@ it("should be able to not override a esm overridable", () => {
|
|||
});
|
||||
});
|
||||
|
||||
import test3 from "./modules/test3";
|
||||
it("should be able to use an overridable module in the initial chunk, but it's not overriden", () => {
|
||||
expect(test3).toBe("original3");
|
||||
});
|
||||
|
||||
it("should be able to override a cjs overridable", () => {
|
||||
return import("./cjs/test1").then(m => {
|
||||
expect(m.default).toBe("overriden1");
|
||||
|
|
@ -43,6 +49,10 @@ it("should be able to not override a cjs overridable", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("should be able to use an overridable module in the initial chunk, and it's overriden", () => {
|
||||
expect(require("./cjs/test3")).toBe("overriden3");
|
||||
});
|
||||
|
||||
it("should be able to override with a package name shortcut", () => {
|
||||
return import("package").then(m => {
|
||||
expect(m.default).toBe("overriden-package");
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export default "original3";
|
||||
|
|
@ -6,11 +6,13 @@ module.exports = {
|
|||
new OverridablesPlugin([
|
||||
{
|
||||
test1: "./modules/test1.js",
|
||||
test2: "./modules/test2"
|
||||
test2: "./modules/test2",
|
||||
test3: "./modules/test3"
|
||||
},
|
||||
{
|
||||
test1: "./cjs/test1",
|
||||
test2: "./cjs/test2.js",
|
||||
test3: "./cjs/../cjs/test3.js",
|
||||
nested1: ["./options/test2"],
|
||||
nested2: {
|
||||
deep: {
|
||||
|
|
|
|||
|
|
@ -227,6 +227,7 @@ declare abstract class AsyncDependenciesBlock extends DependenciesBlock {
|
|||
request: string;
|
||||
parent: DependenciesBlock;
|
||||
chunkName: string;
|
||||
isAsync(parentChunkGroup: ChunkGroup): boolean;
|
||||
module: any;
|
||||
}
|
||||
declare abstract class AsyncQueue<T, K, R> {
|
||||
|
|
|
|||
Loading…
Reference in New Issue