first commit

This commit is contained in:
monjack
2025-06-20 18:01:48 +08:00
commit 6daa6d65c1
24611 changed files with 2512443 additions and 0 deletions

View File

@ -0,0 +1,156 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { ConcatSource, PrefixSource, RawSource } = require("webpack-sources");
const { RuntimeGlobals } = require("..");
const HotUpdateChunk = require("../HotUpdateChunk");
const Template = require("../Template");
const { getCompilationHooks } = require("./JavascriptModulesPlugin");
const {
generateEntryStartup,
updateHashForEntryStartup
} = require("./StartupHelpers");
/** @typedef {import("../Compiler")} Compiler */
class ArrayPushCallbackChunkFormatPlugin {
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.thisCompilation.tap(
"ArrayPushCallbackChunkFormatPlugin",
compilation => {
compilation.hooks.additionalChunkRuntimeRequirements.tap(
"ArrayPushCallbackChunkFormatPlugin",
(chunk, set, { chunkGraph }) => {
if (chunk.hasRuntime()) return;
if (chunkGraph.getNumberOfEntryModules(chunk) > 0) {
set.add(RuntimeGlobals.onChunksLoaded);
set.add(RuntimeGlobals.exports);
set.add(RuntimeGlobals.require);
}
set.add(RuntimeGlobals.chunkCallback);
}
);
const hooks = getCompilationHooks(compilation);
hooks.renderChunk.tap(
"ArrayPushCallbackChunkFormatPlugin",
(modules, renderContext) => {
const { chunk, chunkGraph, runtimeTemplate } = renderContext;
const hotUpdateChunk =
chunk instanceof HotUpdateChunk ? chunk : null;
const globalObject = runtimeTemplate.globalObject;
const source = new ConcatSource();
const runtimeModules =
chunkGraph.getChunkRuntimeModulesInOrder(chunk);
if (hotUpdateChunk) {
const hotUpdateGlobal =
runtimeTemplate.outputOptions.hotUpdateGlobal;
source.add(
`${globalObject}[${JSON.stringify(hotUpdateGlobal)}](`
);
source.add(`${JSON.stringify(chunk.id)},`);
source.add(modules);
if (runtimeModules.length > 0) {
source.add(",\n");
const runtimePart = Template.renderChunkRuntimeModules(
runtimeModules,
renderContext
);
source.add(runtimePart);
}
source.add(")");
} else {
const chunkLoadingGlobal =
runtimeTemplate.outputOptions.chunkLoadingGlobal;
source.add(
`(${globalObject}[${JSON.stringify(
chunkLoadingGlobal
)}] = ${globalObject}[${JSON.stringify(
chunkLoadingGlobal
)}] || []).push([`
);
source.add(`${JSON.stringify(chunk.ids)},`);
source.add(modules);
const entries = Array.from(
chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)
);
if (runtimeModules.length > 0 || entries.length > 0) {
const runtime = new ConcatSource(
`${
runtimeTemplate.supportsArrowFunction()
? `${RuntimeGlobals.require} =>`
: `function(${RuntimeGlobals.require})`
} { // webpackRuntimeModules\n`
);
if (runtimeModules.length > 0) {
runtime.add(
Template.renderRuntimeModules(runtimeModules, {
...renderContext,
codeGenerationResults: compilation.codeGenerationResults
})
);
}
if (entries.length > 0) {
const startupSource = new RawSource(
generateEntryStartup(
chunkGraph,
runtimeTemplate,
entries,
chunk,
true
)
);
runtime.add(
hooks.renderStartup.call(
startupSource,
entries[entries.length - 1][0],
{
...renderContext,
inlined: false
}
)
);
if (
chunkGraph
.getChunkRuntimeRequirements(chunk)
.has(RuntimeGlobals.returnExportsFromRuntime)
) {
runtime.add(`return ${RuntimeGlobals.exports};\n`);
}
}
runtime.add("}\n");
source.add(",\n");
source.add(new PrefixSource("/******/ ", runtime));
}
source.add("])");
}
return source;
}
);
hooks.chunkHash.tap(
"ArrayPushCallbackChunkFormatPlugin",
(chunk, hash, { chunkGraph, runtimeTemplate }) => {
if (chunk.hasRuntime()) return;
hash.update(
`ArrayPushCallbackChunkFormatPlugin1${runtimeTemplate.outputOptions.chunkLoadingGlobal}${runtimeTemplate.outputOptions.hotUpdateGlobal}${runtimeTemplate.globalObject}`
);
const entries = Array.from(
chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)
);
updateHashForEntryStartup(hash, chunkGraph, entries, chunk);
}
);
}
);
}
}
module.exports = ArrayPushCallbackChunkFormatPlugin;

View File

@ -0,0 +1,594 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("estree").Node} Node */
/** @typedef {import("./JavascriptParser").Range} Range */
/** @typedef {import("./JavascriptParser").VariableInfo} VariableInfo */
const TypeUnknown = 0;
const TypeUndefined = 1;
const TypeNull = 2;
const TypeString = 3;
const TypeNumber = 4;
const TypeBoolean = 5;
const TypeRegExp = 6;
const TypeConditional = 7;
const TypeArray = 8;
const TypeConstArray = 9;
const TypeIdentifier = 10;
const TypeWrapped = 11;
const TypeTemplateString = 12;
const TypeBigInt = 13;
class BasicEvaluatedExpression {
constructor() {
this.type = TypeUnknown;
/** @type {Range | undefined} */
this.range = undefined;
/** @type {boolean} */
this.falsy = false;
/** @type {boolean} */
this.truthy = false;
/** @type {boolean | undefined} */
this.nullish = undefined;
/** @type {boolean} */
this.sideEffects = true;
/** @type {boolean | undefined} */
this.bool = undefined;
/** @type {number | undefined} */
this.number = undefined;
/** @type {bigint | undefined} */
this.bigint = undefined;
/** @type {RegExp | undefined} */
this.regExp = undefined;
/** @type {string | undefined} */
this.string = undefined;
/** @type {BasicEvaluatedExpression[] | undefined} */
this.quasis = undefined;
/** @type {BasicEvaluatedExpression[] | undefined} */
this.parts = undefined;
/** @type {EXPECTED_ANY[] | undefined} */
this.array = undefined;
/** @type {BasicEvaluatedExpression[] | undefined} */
this.items = undefined;
/** @type {BasicEvaluatedExpression[] | undefined} */
this.options = undefined;
/** @type {BasicEvaluatedExpression | undefined | null} */
this.prefix = undefined;
/** @type {BasicEvaluatedExpression | undefined | null} */
this.postfix = undefined;
/** @type {BasicEvaluatedExpression[] | undefined} */
this.wrappedInnerExpressions = undefined;
/** @type {string | VariableInfo | undefined} */
this.identifier = undefined;
/** @type {string | VariableInfo | undefined} */
this.rootInfo = undefined;
/** @type {(() => string[]) | undefined} */
this.getMembers = undefined;
/** @type {(() => boolean[]) | undefined} */
this.getMembersOptionals = undefined;
/** @type {(() => Range[]) | undefined} */
this.getMemberRanges = undefined;
/** @type {Node | undefined} */
this.expression = undefined;
}
isUnknown() {
return this.type === TypeUnknown;
}
isNull() {
return this.type === TypeNull;
}
isUndefined() {
return this.type === TypeUndefined;
}
isString() {
return this.type === TypeString;
}
isNumber() {
return this.type === TypeNumber;
}
isBigInt() {
return this.type === TypeBigInt;
}
isBoolean() {
return this.type === TypeBoolean;
}
isRegExp() {
return this.type === TypeRegExp;
}
isConditional() {
return this.type === TypeConditional;
}
isArray() {
return this.type === TypeArray;
}
isConstArray() {
return this.type === TypeConstArray;
}
isIdentifier() {
return this.type === TypeIdentifier;
}
isWrapped() {
return this.type === TypeWrapped;
}
isTemplateString() {
return this.type === TypeTemplateString;
}
/**
* Is expression a primitive or an object type value?
* @returns {boolean | undefined} true: primitive type, false: object type, undefined: unknown/runtime-defined
*/
isPrimitiveType() {
switch (this.type) {
case TypeUndefined:
case TypeNull:
case TypeString:
case TypeNumber:
case TypeBoolean:
case TypeBigInt:
case TypeWrapped:
case TypeTemplateString:
return true;
case TypeRegExp:
case TypeArray:
case TypeConstArray:
return false;
default:
return undefined;
}
}
/**
* Is expression a runtime or compile-time value?
* @returns {boolean} true: compile time value, false: runtime value
*/
isCompileTimeValue() {
switch (this.type) {
case TypeUndefined:
case TypeNull:
case TypeString:
case TypeNumber:
case TypeBoolean:
case TypeRegExp:
case TypeConstArray:
case TypeBigInt:
return true;
default:
return false;
}
}
/**
* Gets the compile-time value of the expression
* @returns {undefined | null | string | number | boolean | RegExp | EXPECTED_ANY[] | bigint} the javascript value
*/
asCompileTimeValue() {
switch (this.type) {
case TypeUndefined:
return;
case TypeNull:
return null;
case TypeString:
return this.string;
case TypeNumber:
return this.number;
case TypeBoolean:
return this.bool;
case TypeRegExp:
return this.regExp;
case TypeConstArray:
return this.array;
case TypeBigInt:
return this.bigint;
default:
throw new Error(
"asCompileTimeValue must only be called for compile-time values"
);
}
}
isTruthy() {
return this.truthy;
}
isFalsy() {
return this.falsy;
}
isNullish() {
return this.nullish;
}
/**
* Can this expression have side effects?
* @returns {boolean} false: never has side effects
*/
couldHaveSideEffects() {
return this.sideEffects;
}
/**
* Creates a boolean representation of this evaluated expression.
* @returns {boolean | undefined} true: truthy, false: falsy, undefined: unknown
*/
asBool() {
if (this.truthy) return true;
if (this.falsy || this.nullish) return false;
if (this.isBoolean()) return this.bool;
if (this.isNull()) return false;
if (this.isUndefined()) return false;
if (this.isString()) return this.string !== "";
if (this.isNumber()) return this.number !== 0;
if (this.isBigInt()) return this.bigint !== BigInt(0);
if (this.isRegExp()) return true;
if (this.isArray()) return true;
if (this.isConstArray()) return true;
if (this.isWrapped()) {
return (this.prefix && this.prefix.asBool()) ||
(this.postfix && this.postfix.asBool())
? true
: undefined;
}
if (this.isTemplateString()) {
const str = this.asString();
if (typeof str === "string") return str !== "";
}
}
/**
* Creates a nullish coalescing representation of this evaluated expression.
* @returns {boolean | undefined} true: nullish, false: not nullish, undefined: unknown
*/
asNullish() {
const nullish = this.isNullish();
if (nullish === true || this.isNull() || this.isUndefined()) return true;
if (nullish === false) return false;
if (this.isTruthy()) return false;
if (this.isBoolean()) return false;
if (this.isString()) return false;
if (this.isNumber()) return false;
if (this.isBigInt()) return false;
if (this.isRegExp()) return false;
if (this.isArray()) return false;
if (this.isConstArray()) return false;
if (this.isTemplateString()) return false;
if (this.isRegExp()) return false;
}
/**
* Creates a string representation of this evaluated expression.
* @returns {string | undefined} the string representation or undefined if not possible
*/
asString() {
if (this.isBoolean()) return `${this.bool}`;
if (this.isNull()) return "null";
if (this.isUndefined()) return "undefined";
if (this.isString()) return this.string;
if (this.isNumber()) return `${this.number}`;
if (this.isBigInt()) return `${this.bigint}`;
if (this.isRegExp()) return `${this.regExp}`;
if (this.isArray()) {
const array = [];
for (const item of /** @type {BasicEvaluatedExpression[]} */ (
this.items
)) {
const itemStr = item.asString();
if (itemStr === undefined) return;
array.push(itemStr);
}
return `${array}`;
}
if (this.isConstArray()) return `${this.array}`;
if (this.isTemplateString()) {
let str = "";
for (const part of /** @type {BasicEvaluatedExpression[]} */ (
this.parts
)) {
const partStr = part.asString();
if (partStr === undefined) return;
str += partStr;
}
return str;
}
}
/**
* @param {string} string value
* @returns {BasicEvaluatedExpression} basic evaluated expression
*/
setString(string) {
this.type = TypeString;
this.string = string;
this.sideEffects = false;
return this;
}
setUndefined() {
this.type = TypeUndefined;
this.sideEffects = false;
return this;
}
setNull() {
this.type = TypeNull;
this.sideEffects = false;
return this;
}
/**
* Set's the value of this expression to a number
* @param {number} number number to set
* @returns {this} this
*/
setNumber(number) {
this.type = TypeNumber;
this.number = number;
this.sideEffects = false;
return this;
}
/**
* Set's the value of this expression to a BigInt
* @param {bigint} bigint bigint to set
* @returns {this} this
*/
setBigInt(bigint) {
this.type = TypeBigInt;
this.bigint = bigint;
this.sideEffects = false;
return this;
}
/**
* Set's the value of this expression to a boolean
* @param {boolean} bool boolean to set
* @returns {this} this
*/
setBoolean(bool) {
this.type = TypeBoolean;
this.bool = bool;
this.sideEffects = false;
return this;
}
/**
* Set's the value of this expression to a regular expression
* @param {RegExp} regExp regular expression to set
* @returns {this} this
*/
setRegExp(regExp) {
this.type = TypeRegExp;
this.regExp = regExp;
this.sideEffects = false;
return this;
}
/**
* Set's the value of this expression to a particular identifier and its members.
* @param {string | VariableInfo} identifier identifier to set
* @param {string | VariableInfo} rootInfo root info
* @param {() => string[]} getMembers members
* @param {() => boolean[]=} getMembersOptionals optional members
* @param {() => Range[]=} getMemberRanges ranges of progressively increasing sub-expressions
* @returns {this} this
*/
setIdentifier(
identifier,
rootInfo,
getMembers,
getMembersOptionals,
getMemberRanges
) {
this.type = TypeIdentifier;
this.identifier = identifier;
this.rootInfo = rootInfo;
this.getMembers = getMembers;
this.getMembersOptionals = getMembersOptionals;
this.getMemberRanges = getMemberRanges;
this.sideEffects = true;
return this;
}
/**
* Wraps an array of expressions with a prefix and postfix expression.
* @param {BasicEvaluatedExpression | null | undefined} prefix Expression to be added before the innerExpressions
* @param {BasicEvaluatedExpression | null | undefined} postfix Expression to be added after the innerExpressions
* @param {BasicEvaluatedExpression[] | undefined} innerExpressions Expressions to be wrapped
* @returns {this} this
*/
setWrapped(prefix, postfix, innerExpressions) {
this.type = TypeWrapped;
this.prefix = prefix;
this.postfix = postfix;
this.wrappedInnerExpressions = innerExpressions;
this.sideEffects = true;
return this;
}
/**
* Stores the options of a conditional expression.
* @param {BasicEvaluatedExpression[]} options optional (consequent/alternate) expressions to be set
* @returns {this} this
*/
setOptions(options) {
this.type = TypeConditional;
this.options = options;
this.sideEffects = true;
return this;
}
/**
* Adds options to a conditional expression.
* @param {BasicEvaluatedExpression[]} options optional (consequent/alternate) expressions to be added
* @returns {this} this
*/
addOptions(options) {
if (!this.options) {
this.type = TypeConditional;
this.options = [];
this.sideEffects = true;
}
for (const item of options) {
this.options.push(item);
}
return this;
}
/**
* Set's the value of this expression to an array of expressions.
* @param {BasicEvaluatedExpression[]} items expressions to set
* @returns {this} this
*/
setItems(items) {
this.type = TypeArray;
this.items = items;
this.sideEffects = items.some(i => i.couldHaveSideEffects());
return this;
}
/**
* Set's the value of this expression to an array of strings.
* @param {string[]} array array to set
* @returns {this} this
*/
setArray(array) {
this.type = TypeConstArray;
this.array = array;
this.sideEffects = false;
return this;
}
/**
* Set's the value of this expression to a processed/unprocessed template string. Used
* for evaluating TemplateLiteral expressions in the JavaScript Parser.
* @param {BasicEvaluatedExpression[]} quasis template string quasis
* @param {BasicEvaluatedExpression[]} parts template string parts
* @param {"cooked" | "raw"} kind template string kind
* @returns {this} this
*/
setTemplateString(quasis, parts, kind) {
this.type = TypeTemplateString;
this.quasis = quasis;
this.parts = parts;
this.templateStringKind = kind;
this.sideEffects = parts.some(p => p.sideEffects);
return this;
}
setTruthy() {
this.falsy = false;
this.truthy = true;
this.nullish = false;
return this;
}
setFalsy() {
this.falsy = true;
this.truthy = false;
return this;
}
/**
* Set's the value of the expression to nullish.
* @param {boolean} value true, if the expression is nullish
* @returns {this} this
*/
setNullish(value) {
this.nullish = value;
if (value) return this.setFalsy();
return this;
}
/**
* Set's the range for the expression.
* @param {Range} range range to set
* @returns {this} this
*/
setRange(range) {
this.range = range;
return this;
}
/**
* Set whether or not the expression has side effects.
* @param {boolean} sideEffects true, if the expression has side effects
* @returns {this} this
*/
setSideEffects(sideEffects = true) {
this.sideEffects = sideEffects;
return this;
}
/**
* Set the expression node for the expression.
* @param {Node | undefined} expression expression
* @returns {this} this
*/
setExpression(expression) {
this.expression = expression;
return this;
}
}
/**
* @param {string} flags regexp flags
* @returns {boolean} is valid flags
*/
BasicEvaluatedExpression.isValidRegExpFlags = flags => {
const len = flags.length;
if (len === 0) return true;
if (len > 4) return false;
// cspell:word gimy
let remaining = 0b0000; // bit per RegExp flag: gimy
for (let i = 0; i < len; i++)
switch (flags.charCodeAt(i)) {
case 103 /* g */:
if (remaining & 0b1000) return false;
remaining |= 0b1000;
break;
case 105 /* i */:
if (remaining & 0b0100) return false;
remaining |= 0b0100;
break;
case 109 /* m */:
if (remaining & 0b0010) return false;
remaining |= 0b0010;
break;
case 121 /* y */:
if (remaining & 0b0001) return false;
remaining |= 0b0001;
break;
default:
return false;
}
return true;
};
module.exports = BasicEvaluatedExpression;

View File

@ -0,0 +1,33 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Entrypoint = require("../Entrypoint");
/** @typedef {import("../Chunk")} Chunk */
/**
* @param {Entrypoint} entrypoint a chunk group
* @param {(Chunk | null)=} excludedChunk1 current chunk which is excluded
* @param {(Chunk | null)=} excludedChunk2 runtime chunk which is excluded
* @returns {Set<Chunk>} chunks
*/
const getAllChunks = (entrypoint, excludedChunk1, excludedChunk2) => {
const queue = new Set([entrypoint]);
const chunks = new Set();
for (const entrypoint of queue) {
for (const chunk of entrypoint.chunks) {
if (chunk === excludedChunk1) continue;
if (chunk === excludedChunk2) continue;
chunks.add(chunk);
}
for (const parent of entrypoint.parentsIterable) {
if (parent instanceof Entrypoint) queue.add(parent);
}
}
return chunks;
};
module.exports.getAllChunks = getAllChunks;

View File

@ -0,0 +1,175 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { ConcatSource, RawSource } = require("webpack-sources");
const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const { getUndoPath } = require("../util/identifier");
const {
getChunkFilenameTemplate,
getCompilationHooks
} = require("./JavascriptModulesPlugin");
const {
generateEntryStartup,
updateHashForEntryStartup
} = require("./StartupHelpers");
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Entrypoint")} Entrypoint */
class CommonJsChunkFormatPlugin {
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.thisCompilation.tap(
"CommonJsChunkFormatPlugin",
compilation => {
compilation.hooks.additionalChunkRuntimeRequirements.tap(
"CommonJsChunkFormatPlugin",
(chunk, set, { chunkGraph }) => {
if (chunk.hasRuntime()) return;
if (chunkGraph.getNumberOfEntryModules(chunk) > 0) {
set.add(RuntimeGlobals.require);
set.add(RuntimeGlobals.startupEntrypoint);
set.add(RuntimeGlobals.externalInstallChunk);
}
}
);
const hooks = getCompilationHooks(compilation);
hooks.renderChunk.tap(
"CommonJsChunkFormatPlugin",
(modules, renderContext) => {
const { chunk, chunkGraph, runtimeTemplate } = renderContext;
const source = new ConcatSource();
source.add(`exports.id = ${JSON.stringify(chunk.id)};\n`);
source.add(`exports.ids = ${JSON.stringify(chunk.ids)};\n`);
source.add("exports.modules = ");
source.add(modules);
source.add(";\n");
const runtimeModules =
chunkGraph.getChunkRuntimeModulesInOrder(chunk);
if (runtimeModules.length > 0) {
source.add("exports.runtime =\n");
source.add(
Template.renderChunkRuntimeModules(
runtimeModules,
renderContext
)
);
}
const entries = Array.from(
chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)
);
if (entries.length > 0) {
const runtimeChunk =
/** @type {Entrypoint} */
(entries[0][1]).getRuntimeChunk();
const currentOutputName = compilation
.getPath(
getChunkFilenameTemplate(chunk, compilation.outputOptions),
{
chunk,
contentHashType: "javascript"
}
)
.replace(/^\/+/g, "")
.split("/");
const runtimeOutputName = compilation
.getPath(
getChunkFilenameTemplate(
/** @type {Chunk} */
(runtimeChunk),
compilation.outputOptions
),
{
chunk: /** @type {Chunk} */ (runtimeChunk),
contentHashType: "javascript"
}
)
.replace(/^\/+/g, "")
.split("/");
// remove common parts
while (
currentOutputName.length > 1 &&
runtimeOutputName.length > 1 &&
currentOutputName[0] === runtimeOutputName[0]
) {
currentOutputName.shift();
runtimeOutputName.shift();
}
const last = runtimeOutputName.join("/");
// create final path
const runtimePath =
getUndoPath(currentOutputName.join("/"), last, true) + last;
const entrySource = new ConcatSource();
entrySource.add(
`(${
runtimeTemplate.supportsArrowFunction()
? "() => "
: "function() "
}{\n`
);
entrySource.add("var exports = {};\n");
entrySource.add(source);
entrySource.add(";\n\n// load runtime\n");
entrySource.add(
`var ${RuntimeGlobals.require} = require(${JSON.stringify(
runtimePath
)});\n`
);
entrySource.add(
`${RuntimeGlobals.externalInstallChunk}(exports);\n`
);
const startupSource = new RawSource(
generateEntryStartup(
chunkGraph,
runtimeTemplate,
entries,
chunk,
false
)
);
entrySource.add(
hooks.renderStartup.call(
startupSource,
entries[entries.length - 1][0],
{
...renderContext,
inlined: false
}
)
);
entrySource.add("\n})()");
return entrySource;
}
return source;
}
);
hooks.chunkHash.tap(
"CommonJsChunkFormatPlugin",
(chunk, hash, { chunkGraph }) => {
if (chunk.hasRuntime()) return;
hash.update("CommonJsChunkFormatPlugin");
hash.update("1");
const entries = Array.from(
chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)
);
updateHashForEntryStartup(hash, chunkGraph, entries, chunk);
}
);
}
);
}
}
module.exports = CommonJsChunkFormatPlugin;

View File

@ -0,0 +1,121 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("../../declarations/WebpackOptions").ChunkLoadingType} ChunkLoadingType */
/** @typedef {import("../Compiler")} Compiler */
/** @type {WeakMap<Compiler, Set<ChunkLoadingType>>} */
const enabledTypes = new WeakMap();
/**
* @param {Compiler} compiler compiler
* @returns {Set<ChunkLoadingType>} enabled types
*/
const getEnabledTypes = compiler => {
let set = enabledTypes.get(compiler);
if (set === undefined) {
set = new Set();
enabledTypes.set(compiler, set);
}
return set;
};
class EnableChunkLoadingPlugin {
/**
* @param {ChunkLoadingType} type library type that should be available
*/
constructor(type) {
this.type = type;
}
/**
* @param {Compiler} compiler the compiler instance
* @param {ChunkLoadingType} type type of library
* @returns {void}
*/
static setEnabled(compiler, type) {
getEnabledTypes(compiler).add(type);
}
/**
* @param {Compiler} compiler the compiler instance
* @param {ChunkLoadingType} type type of library
* @returns {void}
*/
static checkEnabled(compiler, type) {
if (!getEnabledTypes(compiler).has(type)) {
throw new Error(
`Chunk loading type "${type}" is not enabled. ` +
"EnableChunkLoadingPlugin need to be used to enable this type of chunk loading. " +
'This usually happens through the "output.enabledChunkLoadingTypes" option. ' +
'If you are using a function as entry which sets "chunkLoading", you need to add all potential chunk loading types to "output.enabledChunkLoadingTypes". ' +
`These types are enabled: ${Array.from(
getEnabledTypes(compiler)
).join(", ")}`
);
}
}
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
const { type } = this;
// Only enable once
const enabled = getEnabledTypes(compiler);
if (enabled.has(type)) return;
enabled.add(type);
if (typeof type === "string") {
switch (type) {
case "jsonp": {
const JsonpChunkLoadingPlugin = require("../web/JsonpChunkLoadingPlugin");
new JsonpChunkLoadingPlugin().apply(compiler);
break;
}
case "import-scripts": {
const ImportScriptsChunkLoadingPlugin = require("../webworker/ImportScriptsChunkLoadingPlugin");
new ImportScriptsChunkLoadingPlugin().apply(compiler);
break;
}
case "require": {
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
const CommonJsChunkLoadingPlugin = require("../node/CommonJsChunkLoadingPlugin");
new CommonJsChunkLoadingPlugin({
asyncChunkLoading: false
}).apply(compiler);
break;
}
case "async-node": {
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
const CommonJsChunkLoadingPlugin = require("../node/CommonJsChunkLoadingPlugin");
new CommonJsChunkLoadingPlugin({
asyncChunkLoading: true
}).apply(compiler);
break;
}
case "import":
case "universal": {
const ModuleChunkLoadingPlugin = require("../esm/ModuleChunkLoadingPlugin");
new ModuleChunkLoadingPlugin().apply(compiler);
break;
}
default:
throw new Error(`Unsupported chunk loading type ${type}.
Plugins which provide custom chunk loading types must call EnableChunkLoadingPlugin.setEnabled(compiler, type) to disable this error.`);
}
} else {
// TODO support plugin instances here
// apply them to the compiler
}
}
}
module.exports = EnableChunkLoadingPlugin;

View File

@ -0,0 +1,265 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const util = require("util");
const { RawSource, ReplaceSource } = require("webpack-sources");
const Generator = require("../Generator");
const InitFragment = require("../InitFragment");
const { JS_TYPES } = require("../ModuleSourceTypesConstants");
const HarmonyCompatibilityDependency = require("../dependencies/HarmonyCompatibilityDependency");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../DependenciesBlock")} DependenciesBlock */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../DependencyTemplate")} DependencyTemplate */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../DependencyTemplates")} DependencyTemplates */
/** @typedef {import("../Generator").GenerateContext} GenerateContext */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
/** @typedef {import("../Module").SourceTypes} SourceTypes */
/** @typedef {import("../NormalModule")} NormalModule */
/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
// TODO: clean up this file
// replace with newer constructs
const deprecatedGetInitFragments = util.deprecate(
/**
* @param {DependencyTemplate} template template
* @param {Dependency} dependency dependency
* @param {DependencyTemplateContext} templateContext template context
* @returns {InitFragment<GenerateContext>[]} init fragments
*/
(template, dependency, templateContext) =>
/** @type {DependencyTemplate & { getInitFragments: (dependency: Dependency, dependencyTemplateContext: DependencyTemplateContext) => InitFragment<GenerateContext>[] }} */
(template).getInitFragments(dependency, templateContext),
"DependencyTemplate.getInitFragment is deprecated (use apply(dep, source, { initFragments }) instead)",
"DEP_WEBPACK_JAVASCRIPT_GENERATOR_GET_INIT_FRAGMENTS"
);
class JavascriptGenerator extends Generator {
/**
* @param {NormalModule} module fresh module
* @returns {SourceTypes} available types (do not mutate)
*/
getTypes(module) {
return JS_TYPES;
}
/**
* @param {NormalModule} module the module
* @param {string=} type source type
* @returns {number} estimate size of the module
*/
getSize(module, type) {
const originalSource = module.originalSource();
if (!originalSource) {
return 39;
}
return originalSource.size();
}
/**
* @param {NormalModule} module module for which the bailout reason should be determined
* @param {ConcatenationBailoutReasonContext} context context
* @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
*/
getConcatenationBailoutReason(module, context) {
// Only harmony modules are valid for optimization
if (
!module.buildMeta ||
module.buildMeta.exportsType !== "namespace" ||
module.presentationalDependencies === undefined ||
!module.presentationalDependencies.some(
d => d instanceof HarmonyCompatibilityDependency
)
) {
return "Module is not an ECMAScript module";
}
// Some expressions are not compatible with module concatenation
// because they may produce unexpected results. The plugin bails out
// if some were detected upfront.
if (module.buildInfo && module.buildInfo.moduleConcatenationBailout) {
return `Module uses ${module.buildInfo.moduleConcatenationBailout}`;
}
}
/**
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generate(module, generateContext) {
const originalSource = module.originalSource();
if (!originalSource) {
return new RawSource("throw new Error('No source available');");
}
const source = new ReplaceSource(originalSource);
/** @type {InitFragment<GenerateContext>[]} */
const initFragments = [];
this.sourceModule(module, initFragments, source, generateContext);
return InitFragment.addToSource(source, initFragments, generateContext);
}
/**
* @param {Error} error the error
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generateError(error, module, generateContext) {
return new RawSource(`throw new Error(${JSON.stringify(error.message)});`);
}
/**
* @param {Module} module the module to generate
* @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments
* @param {ReplaceSource} source the current replace source which can be modified
* @param {GenerateContext} generateContext the generateContext
* @returns {void}
*/
sourceModule(module, initFragments, source, generateContext) {
for (const dependency of module.dependencies) {
this.sourceDependency(
module,
dependency,
initFragments,
source,
generateContext
);
}
if (module.presentationalDependencies !== undefined) {
for (const dependency of module.presentationalDependencies) {
this.sourceDependency(
module,
dependency,
initFragments,
source,
generateContext
);
}
}
for (const childBlock of module.blocks) {
this.sourceBlock(
module,
childBlock,
initFragments,
source,
generateContext
);
}
}
/**
* @param {Module} module the module to generate
* @param {DependenciesBlock} block the dependencies block which will be processed
* @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments
* @param {ReplaceSource} source the current replace source which can be modified
* @param {GenerateContext} generateContext the generateContext
* @returns {void}
*/
sourceBlock(module, block, initFragments, source, generateContext) {
for (const dependency of block.dependencies) {
this.sourceDependency(
module,
dependency,
initFragments,
source,
generateContext
);
}
for (const childBlock of block.blocks) {
this.sourceBlock(
module,
childBlock,
initFragments,
source,
generateContext
);
}
}
/**
* @param {Module} module the current module
* @param {Dependency} dependency the dependency to generate
* @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments
* @param {ReplaceSource} source the current replace source which can be modified
* @param {GenerateContext} generateContext the render context
* @returns {void}
*/
sourceDependency(module, dependency, initFragments, source, generateContext) {
const constructor =
/** @type {new (...args: EXPECTED_ANY[]) => Dependency} */
(dependency.constructor);
const template = generateContext.dependencyTemplates.get(constructor);
if (!template) {
throw new Error(
`No template for dependency: ${dependency.constructor.name}`
);
}
/** @type {InitFragment<GenerateContext>[] | undefined} */
let chunkInitFragments;
/** @type {DependencyTemplateContext} */
const templateContext = {
runtimeTemplate: generateContext.runtimeTemplate,
dependencyTemplates: generateContext.dependencyTemplates,
moduleGraph: generateContext.moduleGraph,
chunkGraph: generateContext.chunkGraph,
module,
runtime: generateContext.runtime,
runtimeRequirements: generateContext.runtimeRequirements,
concatenationScope: generateContext.concatenationScope,
codeGenerationResults:
/** @type {NonNullable<GenerateContext["codeGenerationResults"]>} */
(generateContext.codeGenerationResults),
initFragments,
get chunkInitFragments() {
if (!chunkInitFragments) {
const data =
/** @type {NonNullable<GenerateContext["getData"]>} */
(generateContext.getData)();
chunkInitFragments = data.get("chunkInitFragments");
if (!chunkInitFragments) {
chunkInitFragments = [];
data.set("chunkInitFragments", chunkInitFragments);
}
}
return chunkInitFragments;
}
};
template.apply(dependency, source, templateContext);
// TODO remove in webpack 6
if ("getInitFragments" in template) {
const fragments = deprecatedGetInitFragments(
template,
dependency,
templateContext
);
if (fragments) {
for (const fragment of fragments) {
initFragments.push(fragment);
}
}
}
}
}
module.exports = JavascriptGenerator;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,129 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning");
const ConstDependency = require("../dependencies/ConstDependency");
const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
/** @typedef {import("estree").Expression} Expression */
/** @typedef {import("estree").Node} Node */
/** @typedef {import("estree").SourceLocation} SourceLocation */
/** @typedef {import("./JavascriptParser")} JavascriptParser */
/** @typedef {import("./JavascriptParser").Range} Range */
/**
* @param {JavascriptParser} parser the parser
* @param {string} value the const value
* @param {(string[] | null)=} runtimeRequirements runtime requirements
* @returns {(expression: Expression) => true} plugin function
*/
module.exports.toConstantDependency = (parser, value, runtimeRequirements) =>
function constDependency(expr) {
const dep = new ConstDependency(
value,
/** @type {Range} */
(expr.range),
runtimeRequirements
);
dep.loc = /** @type {SourceLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(dep);
return true;
};
/**
* @param {string} value the string value
* @returns {(expression: Expression) => BasicEvaluatedExpression} plugin function
*/
module.exports.evaluateToString = value =>
function stringExpression(expr) {
return new BasicEvaluatedExpression()
.setString(value)
.setRange(/** @type {Range} */ (expr.range));
};
/**
* @param {number} value the number value
* @returns {(expression: Expression) => BasicEvaluatedExpression} plugin function
*/
module.exports.evaluateToNumber = value =>
function stringExpression(expr) {
return new BasicEvaluatedExpression()
.setNumber(value)
.setRange(/** @type {Range} */ (expr.range));
};
/**
* @param {boolean} value the boolean value
* @returns {(expression: Expression) => BasicEvaluatedExpression} plugin function
*/
module.exports.evaluateToBoolean = value =>
function booleanExpression(expr) {
return new BasicEvaluatedExpression()
.setBoolean(value)
.setRange(/** @type {Range} */ (expr.range));
};
/**
* @param {string} identifier identifier
* @param {string} rootInfo rootInfo
* @param {() => string[]} getMembers getMembers
* @param {boolean | null=} truthy is truthy, null if nullish
* @returns {(expression: Expression) => BasicEvaluatedExpression} callback
*/
module.exports.evaluateToIdentifier = (
identifier,
rootInfo,
getMembers,
truthy
) =>
function identifierExpression(expr) {
const evaluatedExpression = new BasicEvaluatedExpression()
.setIdentifier(identifier, rootInfo, getMembers)
.setSideEffects(false)
.setRange(/** @type {Range} */ (expr.range));
switch (truthy) {
case true:
evaluatedExpression.setTruthy();
break;
case null:
evaluatedExpression.setNullish(true);
break;
case false:
evaluatedExpression.setFalsy();
break;
}
return evaluatedExpression;
};
/**
* @param {JavascriptParser} parser the parser
* @param {string} message the message
* @returns {(expression: Expression) => boolean | undefined} callback to handle unsupported expression
*/
module.exports.expressionIsUnsupported = (parser, message) =>
function unsupportedExpression(expr) {
const dep = new ConstDependency(
"(void 0)",
/** @type {Range} */ (expr.range),
null
);
dep.loc = /** @type {SourceLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(dep);
if (!parser.state.module) return;
parser.state.module.addWarning(
new UnsupportedFeatureWarning(
message,
/** @type {SourceLocation} */ (expr.loc)
)
);
return true;
};
module.exports.skipTraversal = () => true;
module.exports.approve = () => true;

View File

@ -0,0 +1,180 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const { isSubset } = require("../util/SetHelpers");
const { getAllChunks } = require("./ChunkHelpers");
/** @typedef {import("../util/Hash")} Hash */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Chunk").ChunkId} ChunkId */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */
/** @typedef {import("../Entrypoint")} Entrypoint */
/** @typedef {import("../ChunkGraph").EntryModuleWithChunkGroup} EntryModuleWithChunkGroup */
/** @typedef {import("../ChunkGroup")} ChunkGroup */
/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
/** @typedef {(string|number)[]} EntryItem */
const EXPORT_PREFIX = `var ${RuntimeGlobals.exports} = `;
/** @typedef {Set<Chunk>} Chunks */
/** @typedef {ModuleId[]} ModuleIds */
/**
* @param {ChunkGraph} chunkGraph chunkGraph
* @param {RuntimeTemplate} runtimeTemplate runtimeTemplate
* @param {EntryModuleWithChunkGroup[]} entries entries
* @param {Chunk} chunk chunk
* @param {boolean} passive true: passive startup with on chunks loaded
* @returns {string} runtime code
*/
module.exports.generateEntryStartup = (
chunkGraph,
runtimeTemplate,
entries,
chunk,
passive
) => {
/** @type {string[]} */
const runtime = [
`var __webpack_exec__ = ${runtimeTemplate.returningFunction(
`${RuntimeGlobals.require}(${RuntimeGlobals.entryModuleId} = moduleId)`,
"moduleId"
)}`
];
/**
* @param {ModuleId} id id
* @returns {string} fn to execute
*/
const runModule = id => `__webpack_exec__(${JSON.stringify(id)})`;
/**
* @param {Chunks} chunks chunks
* @param {ModuleIds} moduleIds module ids
* @param {boolean=} final true when final, otherwise false
*/
const outputCombination = (chunks, moduleIds, final) => {
if (chunks.size === 0) {
runtime.push(
`${final ? EXPORT_PREFIX : ""}(${moduleIds.map(runModule).join(", ")});`
);
} else {
const fn = runtimeTemplate.returningFunction(
moduleIds.map(runModule).join(", ")
);
runtime.push(
`${final && !passive ? EXPORT_PREFIX : ""}${
passive
? RuntimeGlobals.onChunksLoaded
: RuntimeGlobals.startupEntrypoint
}(0, ${JSON.stringify(Array.from(chunks, c => c.id))}, ${fn});`
);
if (final && passive) {
runtime.push(`${EXPORT_PREFIX}${RuntimeGlobals.onChunksLoaded}();`);
}
}
};
/** @type {Chunks | undefined} */
let currentChunks;
/** @type {ModuleIds | undefined} */
let currentModuleIds;
for (const [module, entrypoint] of entries) {
if (!chunkGraph.getModuleSourceTypes(module).has("javascript")) {
continue;
}
const runtimeChunk =
/** @type {Entrypoint} */
(entrypoint).getRuntimeChunk();
const moduleId = /** @type {ModuleId} */ (chunkGraph.getModuleId(module));
const chunks = getAllChunks(
/** @type {Entrypoint} */
(entrypoint),
chunk,
runtimeChunk
);
if (
currentChunks &&
currentChunks.size === chunks.size &&
isSubset(currentChunks, chunks)
) {
/** @type {ModuleIds} */
(currentModuleIds).push(moduleId);
} else {
if (currentChunks) {
outputCombination(
currentChunks,
/** @type {ModuleIds} */ (currentModuleIds)
);
}
currentChunks = chunks;
currentModuleIds = [moduleId];
}
}
// output current modules with export prefix
if (currentChunks) {
outputCombination(
currentChunks,
/** @type {ModuleIds} */
(currentModuleIds),
true
);
}
runtime.push("");
return Template.asString(runtime);
};
/**
* @param {Hash} hash the hash to update
* @param {ChunkGraph} chunkGraph chunkGraph
* @param {EntryModuleWithChunkGroup[]} entries entries
* @param {Chunk} chunk chunk
* @returns {void}
*/
module.exports.updateHashForEntryStartup = (
hash,
chunkGraph,
entries,
chunk
) => {
for (const [module, entrypoint] of entries) {
const runtimeChunk =
/** @type {Entrypoint} */
(entrypoint).getRuntimeChunk();
const moduleId = chunkGraph.getModuleId(module);
hash.update(`${moduleId}`);
for (const c of getAllChunks(
/** @type {Entrypoint} */ (entrypoint),
chunk,
/** @type {Chunk} */ (runtimeChunk)
)) {
hash.update(`${c.id}`);
}
}
};
/**
* @param {Chunk} chunk the chunk
* @param {ChunkGraph} chunkGraph the chunk graph
* @param {(chunk: Chunk, chunkGraph: ChunkGraph) => boolean} filterFn filter function
* @returns {Set<number | string>} initially fulfilled chunk ids
*/
module.exports.getInitialChunkIds = (chunk, chunkGraph, filterFn) => {
const initialChunkIds = new Set(chunk.ids);
for (const c of chunk.getAllInitialChunks()) {
if (c === chunk || filterFn(c, chunkGraph)) continue;
for (const id of /** @type {ChunkId[]} */ (c.ids)) {
initialChunkIds.add(id);
}
}
return initialChunkIds;
};