support overridables in initial chunks

This commit is contained in:
Tobias Koppers 2020-04-30 00:39:36 +02:00
parent e08bfc4bd6
commit e9ae850d66
10 changed files with 183 additions and 72 deletions

View File

@ -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);

View File

@ -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: {

View File

@ -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;

View File

@ -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
)});`
])
)
);

View File

@ -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();"]

View File

@ -0,0 +1 @@
module.exports = "original3-cjs";

View File

@ -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");

View File

@ -0,0 +1 @@
export default "original3";

View File

@ -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: {

1
types.d.ts vendored
View File

@ -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> {