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
19 changes: 13 additions & 6 deletions lib/ChunkGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,23 @@ class ChunkGroup {
*/
addOptions(options) {
for (const _key of Object.keys(options)) {
const key = /** @type {keyof ChunkGroupOptions} */ (_key);
const key =
/** @type {keyof ChunkGroupOptions} */
(_key);
if (this.options[key] === undefined) {
/** @type {TODO} */
/** @type {EXPECTED_ANY} */
(this.options)[key] = options[key];
} else if (this.options[key] !== options[key]) {
if (key.endsWith("Order")) {
/** @type {TODO} */
(this.options)[key] = Math.max(
/** @type {number} */ (this.options[key]),
/** @type {number} */ (options[key])
const orderKey =
/** @type {Exclude<keyof ChunkGroupOptions, "name" | "fetchPriority">} */
(key);

this.options[orderKey] = Math.max(
/** @type {number} */
(this.options[orderKey]),
/** @type {number} */
(options[orderKey])
);
} else {
throw new Error(
Expand Down
1 change: 1 addition & 0 deletions lib/Compilation.js
Original file line number Diff line number Diff line change
Expand Up @@ -2354,6 +2354,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
*/
_addEntryItem(context, entry, target, options, callback) {
const { name } = options;
/** @type {EntryData | undefined} */
let entryData =
name !== undefined ? this.entries.get(name) : this.globalEntry;
if (entryData === undefined) {
Expand Down
104 changes: 61 additions & 43 deletions lib/ConstPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,53 +179,21 @@ class ConstPlugin {
? statement.alternate
: statement.consequent;
if (branchToRemove) {
// Before removing the dead branch, the hoisted declarations
// must be collected.
//
// Given the following code:
//
// if (true) f() else g()
// if (false) {
// function f() {}
// const g = function g() {}
// if (someTest) {
// let a = 1
// var x, {y, z} = obj
// }
// } else {
// …
// }
//
// the generated code is:
//
// if (true) f() else {}
// if (false) {
// var f, x, y, z; (in loose mode)
// var x, y, z; (in strict mode)
// } else {
// …
// }
//
// NOTE: When code runs in strict mode, `var` declarations
// are hoisted but `function` declarations don't.
//
const declarations = parser.scope.isStrict
? getHoistedDeclarations(branchToRemove, false)
: getHoistedDeclarations(branchToRemove, true);
const replacement =
declarations.length > 0
? `{ var ${declarations.join(", ")}; }`
: "{}";
const dep = new ConstDependency(
replacement,
/** @type {Range} */ (branchToRemove.range)
);
dep.loc = /** @type {SourceLocation} */ (branchToRemove.loc);
parser.state.module.addPresentationalDependency(dep);
this.eliminateUnusedStatement(parser, branchToRemove);
}
return bool;
}
});
parser.hooks.unusedStatement.tap(PLUGIN_NAME, statement => {
if (
parser.scope.isAsmJs ||
// Check top level scope here again
parser.scope.topLevelScope === true
)
return;
this.eliminateUnusedStatement(parser, statement);
return true;
});
parser.hooks.expressionConditionalOperator.tap(
PLUGIN_NAME,
expression => {
Expand Down Expand Up @@ -534,6 +502,56 @@ class ConstPlugin {
}
);
}

/**
* Eliminate an unused statement.
* @param {JavascriptParser} parser the parser
* @param {Statement} statement the statement to remove
* @returns {void}
*/
eliminateUnusedStatement(parser, statement) {
// Before removing the unused branch, the hoisted declarations
// must be collected.
//
// Given the following code:
//
// if (true) f() else g()
// if (false) {
// function f() {}
// const g = function g() {}
// if (someTest) {
// let a = 1
// var x, {y, z} = obj
// }
// } else {
// …
// }
//
// the generated code is:
//
// if (true) f() else {}
// if (false) {
// var f, x, y, z; (in loose mode)
// var x, y, z; (in strict mode)
// } else {
// …
// }
//
// NOTE: When code runs in strict mode, `var` declarations
// are hoisted but `function` declarations don't.
//
const declarations = parser.scope.isStrict
? getHoistedDeclarations(statement, false)
: getHoistedDeclarations(statement, true);
const replacement =
declarations.length > 0 ? `{ var ${declarations.join(", ")}; }` : "{}";
const dep = new ConstDependency(
`// removed by dead control flow\n${replacement}`,
/** @type {Range} */ (statement.range)
);
dep.loc = /** @type {SourceLocation} */ (statement.loc);
parser.state.module.addPresentationalDependency(dep);
}
}

module.exports = ConstPlugin;
11 changes: 5 additions & 6 deletions lib/asset/AssetGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,7 @@ class AssetGenerator extends Generator {
* @returns {SourceTypes} available types (do not mutate)
*/
getTypes(module) {
/** @type {Set<string>} */
const sourceTypes = new Set();
const connections = this._moduleGraph.getIncomingConnections(module);

Expand All @@ -669,27 +670,25 @@ class AssetGenerator extends Generator {
}

if ((module.buildInfo && module.buildInfo.dataUrl) || this.emit === false) {
if (sourceTypes) {
if (sourceTypes.size > 0) {
if (sourceTypes.has("javascript") && sourceTypes.has("css")) {
return JS_AND_CSS_URL_TYPES;
} else if (sourceTypes.has("javascript")) {
return JS_TYPES;
} else if (sourceTypes.has("css")) {
return CSS_URL_TYPES;
}
return JS_TYPES;
}

return NO_TYPES;
}

if (sourceTypes) {
if (sourceTypes.size > 0) {
if (sourceTypes.has("javascript") && sourceTypes.has("css")) {
return ASSET_AND_JS_AND_CSS_URL_TYPES;
} else if (sourceTypes.has("javascript")) {
return ASSET_AND_JS_TYPES;
} else if (sourceTypes.has("css")) {
return ASSET_AND_CSS_URL_TYPES;
}
return ASSET_AND_JS_TYPES;
}

return ASSET_TYPES;
Expand Down
12 changes: 7 additions & 5 deletions lib/asset/AssetSourceGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ class AssetSourceGenerator extends Generator {
* @returns {SourceTypes} available types (do not mutate)
*/
getTypes(module) {
/** @type {Set<string>} */
const sourceTypes = new Set();
const connections = this._moduleGraph.getIncomingConnections(module);

Expand All @@ -133,12 +134,13 @@ class AssetSourceGenerator extends Generator {
sourceTypes.add(connection.originModule.type.split("/")[0]);
}

if (sourceTypes.has("javascript") && sourceTypes.has("css")) {
return JS_AND_CSS_URL_TYPES;
} else if (sourceTypes.has("javascript")) {
if (sourceTypes.size > 0) {
if (sourceTypes.has("javascript") && sourceTypes.has("css")) {
return JS_AND_CSS_URL_TYPES;
} else if (sourceTypes.has("css")) {
return CSS_URL_TYPES;
}
return JS_TYPES;
} else if (sourceTypes.has("css")) {
return CSS_URL_TYPES;
}

return NO_TYPES;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ module.exports = class RequireEnsureDependenciesBlockParserPlugin {
}
if (successExpression) {
if (successExpression.fn.body.type === "BlockStatement") {
// Opt-out of Dead Control Flow detection for this block
const oldTerminated = parser.scope.terminated;
parser.walkStatement(successExpression.fn.body);
parser.scope.terminated = oldTerminated;
} else {
parser.walkExpression(successExpression.fn.body);
}
Expand Down
11 changes: 9 additions & 2 deletions lib/javascript/JavascriptParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,9 @@ class JavascriptParser extends Parser {
/** @type {SyncBailHook<[ThrowStatement | ReturnStatement], boolean | void>} */
terminate: new SyncBailHook(["statement"]),
/** @type {SyncBailHook<[Program, Comment[]], boolean | void>} */
finish: new SyncBailHook(["ast", "comments"])
finish: new SyncBailHook(["ast", "comments"]),
/** @type {SyncBailHook<[Statement], boolean | void>} */
unusedStatement: new SyncBailHook(["statement"])
});
this.sourceType = sourceType;
/** @type {ScopeInfo} */
Expand Down Expand Up @@ -1939,8 +1941,13 @@ class JavascriptParser extends Parser {
for (let index = 0, len = statements.length; index < len; index++) {
const statement = statements[index];

if (onlyFunctionDeclaration && statement.type !== "FunctionDeclaration")
if (
onlyFunctionDeclaration &&
statement.type !== "FunctionDeclaration" &&
this.hooks.unusedStatement.call(/** @type {Statement} */ (statement))
) {
continue;
}

this.walkStatement(statement);

Expand Down
26 changes: 13 additions & 13 deletions lib/stats/DefaultStatsPrinterPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -1454,7 +1454,12 @@ const AVAILABLE_COLORS = {
magenta: "\u001B[1m\u001B[35m"
};

/** @typedef {Required<{ [Key in keyof KnownStatsPrinterFormatters]: (value: Parameters<NonNullable<KnownStatsPrinterFormatters[Key]>>[0], options: Required<KnownStatsPrinterColorFunctions> & StatsPrinterContext, ...args: TODO[]) => string }>} AvailableFormats */
/**
* @template T
* @typedef {T extends [infer Head, ...infer Tail] ? Tail : undefined} Tail
*/

/** @typedef {Required<{ [Key in keyof KnownStatsPrinterFormatters]: (value: Parameters<NonNullable<KnownStatsPrinterFormatters[Key]>>[0], options: Required<KnownStatsPrinterColorFunctions> & StatsPrinterContextWithExtra, ...args: Tail<Parameters<NonNullable<KnownStatsPrinterFormatters[Key]>>>) => string }>} AvailableFormats */

/** @type {AvailableFormats} */
const AVAILABLE_FORMATS = {
Expand Down Expand Up @@ -1629,21 +1634,16 @@ class DefaultStatsPrinterPlugin {
context[color] = str => str;
}
}
for (const format of Object.keys(AVAILABLE_FORMATS)) {
for (const _format of Object.keys(AVAILABLE_FORMATS)) {
const format =
/** @type {keyof KnownStatsPrinterFormatters} */
(_format);

context[format] =
/**
* @param {string | number} content content
* @param {...TODO} args args
* @returns {string} result
*/
/** @type {(content: Parameters<NonNullable<KnownStatsPrinterFormatters[keyof KnownStatsPrinterFormatters]>>[0], ...args: Tail<Parameters<NonNullable<KnownStatsPrinterFormatters[keyof KnownStatsPrinterFormatters]>>>) => string} */
(content, ...args) =>
/** @type {TODO} */
(
AVAILABLE_FORMATS[
/** @type {keyof AvailableFormats} */
(format)
]
)(
(AVAILABLE_FORMATS)[format](
content,
/** @type {StatsPrinterContext & Required<KnownStatsPrinterColorFunctions>} */
(context),
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "webpack",
"version": "5.99.8",
"version": "5.99.9",
"author": "Tobias Koppers @sokra",
"description": "Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.",
"license": "MIT",
Expand Down
5 changes: 3 additions & 2 deletions test/BenchmarkTestCases.benchmark.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -321,9 +321,10 @@ const baseOutputPath = path.join(__dirname, "js", "benchmark");

const bench = withCodSpeed(
new Bench({
warmup: true,
now: hrtimeNow,
throws: true
throws: true,
warmup: true,
time: 30000
})
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "foo"
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
it("should compile and work", done => {
require.ensure(
["./foo"],
() => {
throw new Error("error");
},
() => {
import("./foo").then(m => {
expect(m.default).toBe("foo");
done();
});
}
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
optimization: {
minimize: false
}
};
18 changes: 18 additions & 0 deletions test/configCases/parsing/dead-code-elimination/esm1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
it("Should eliminate hoisted function in ESM because of default strict mode", () => {
expect(() => {
fnDecl;
}).toThrow();
try {
throw new Error();
} catch (e) {
return;
}
{
function fnDecl() {
expect(true).toBe(true);
}
}
expect(true).toBe(false);
});

export default "esm1";
1 change: 1 addition & 0 deletions test/configCases/parsing/dead-code-elimination/esm2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "esm2";
Loading
Loading