Skip to content

Commit b0e0e4c

Browse files
authored
feat: support array values for devtool option (#20191)
1 parent 3ae1004 commit b0e0e4c

23 files changed

Lines changed: 557 additions & 88 deletions

.changeset/thirty-ears-join.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack": minor
3+
---
4+
5+
Added support for array values ​​to the `devtool` option.

declarations/WebpackOptions.d.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,22 @@ export type DevServer =
4646
/**
4747
* A developer tool to enhance debugging (false | eval | [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map).
4848
*/
49-
export type DevTool = (false | "eval") | string;
49+
export type DevTool =
50+
| {
51+
/**
52+
* Which asset type should receive this devtool value.
53+
*/
54+
type: "all" | "javascript" | "css";
55+
/**
56+
* A developer tool to enhance debugging (false | eval | [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map).
57+
*/
58+
use: RawDevTool;
59+
}[]
60+
| RawDevTool;
61+
/**
62+
* A developer tool to enhance debugging (false | eval | [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map).
63+
*/
64+
export type RawDevTool = (false | "eval") | string;
5065
/**
5166
* Enable and configure the Dotenv plugin to load environment variables from .env files.
5267
*/

lib/OptionsApply.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@
66
"use strict";
77

88
/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
9+
/** @typedef {import("./config/normalization").WebpackOptionsInterception} WebpackOptionsInterception */
910
/** @typedef {import("./Compiler")} Compiler */
1011

1112
class OptionsApply {
1213
/**
1314
* @param {WebpackOptions} options options object
1415
* @param {Compiler} compiler compiler object
16+
* @param {WebpackOptionsInterception=} interception intercepted options
1517
* @returns {WebpackOptions} options object
1618
*/
17-
process(options, compiler) {
19+
process(options, compiler, interception) {
1820
return options;
1921
}
2022
}

lib/WebpackOptionsApply.js

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ const { cleverMerge } = require("./util/cleverMerge");
7676

7777
/** @typedef {import("../declarations/WebpackOptions").WebpackPluginFunction} WebpackPluginFunction */
7878
/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
79+
/** @typedef {import("./config/normalization").WebpackOptionsInterception} WebpackOptionsInterception */
7980
/** @typedef {import("./Compiler")} Compiler */
8081
/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
8182
/** @typedef {import("./util/fs").IntermediateFileSystem} IntermediateFileSystem */
@@ -90,9 +91,10 @@ class WebpackOptionsApply extends OptionsApply {
9091
/**
9192
* @param {WebpackOptions} options options object
9293
* @param {Compiler} compiler compiler object
94+
* @param {WebpackOptionsInterception=} interception intercepted options
9395
* @returns {WebpackOptions} options object
9496
*/
95-
process(options, compiler) {
97+
process(options, compiler, interception) {
9698
compiler.outputPath = options.output.path;
9799
compiler.recordsInputPath = options.recordsInputPath || null;
98100
compiler.recordsOutputPath = options.recordsOutputPath || null;
@@ -315,37 +317,59 @@ class WebpackOptionsApply extends OptionsApply {
315317
).apply(compiler);
316318
}
317319

318-
if (options.devtool) {
319-
if (options.devtool.includes("source-map")) {
320-
const hidden = options.devtool.includes("hidden");
321-
const inline = options.devtool.includes("inline");
322-
const evalWrapped = options.devtool.includes("eval");
323-
const cheap = options.devtool.includes("cheap");
324-
const moduleMaps = options.devtool.includes("module");
325-
const noSources = options.devtool.includes("nosources");
326-
const debugIds = options.devtool.includes("debugids");
327-
const Plugin = evalWrapped
328-
? require("./EvalSourceMapDevToolPlugin")
329-
: require("./SourceMapDevToolPlugin");
330-
new Plugin({
331-
filename: inline ? null : options.output.sourceMapFilename,
332-
moduleFilenameTemplate: options.output.devtoolModuleFilenameTemplate,
333-
fallbackModuleFilenameTemplate:
334-
options.output.devtoolFallbackModuleFilenameTemplate,
335-
append: hidden ? false : undefined,
336-
module: moduleMaps ? true : !cheap,
337-
columns: !cheap,
338-
noSources,
339-
namespace: options.output.devtoolNamespace,
340-
debugIds
341-
}).apply(compiler);
342-
} else if (options.devtool.includes("eval")) {
343-
const EvalDevToolModulePlugin = require("./EvalDevToolModulePlugin");
320+
let devtool =
321+
interception === undefined ? options.devtool : interception.devtool;
322+
devtool = Array.isArray(devtool)
323+
? devtool
324+
: typeof devtool === "string"
325+
? [{ type: "all", use: devtool }]
326+
: [];
327+
328+
for (const item of devtool) {
329+
const { type, use } = item;
330+
331+
if (use) {
332+
if (use.includes("source-map")) {
333+
const hidden = use.includes("hidden");
334+
const inline = use.includes("inline");
335+
const evalWrapped = use.includes("eval");
336+
const cheap = use.includes("cheap");
337+
const moduleMaps = use.includes("module");
338+
const noSources = use.includes("nosources");
339+
const debugIds = use.includes("debugids");
340+
const Plugin = evalWrapped
341+
? require("./EvalSourceMapDevToolPlugin")
342+
: require("./SourceMapDevToolPlugin");
343+
const assetExt =
344+
type === "javascript"
345+
? /\.((c|m)?js)($|\?)/i
346+
: type === "css"
347+
? /\.(css)($|\?)/i
348+
: /\.((c|m)?js|css)($|\?)/i;
349+
350+
new Plugin({
351+
test: evalWrapped ? undefined : assetExt,
352+
filename: inline ? null : options.output.sourceMapFilename,
353+
moduleFilenameTemplate:
354+
options.output.devtoolModuleFilenameTemplate,
355+
fallbackModuleFilenameTemplate:
356+
options.output.devtoolFallbackModuleFilenameTemplate,
357+
append: hidden ? false : undefined,
358+
module: moduleMaps ? true : !cheap,
359+
columns: !cheap,
360+
noSources,
361+
namespace: options.output.devtoolNamespace,
362+
debugIds
363+
}).apply(compiler);
364+
} else if (use.includes("eval")) {
365+
const EvalDevToolModulePlugin = require("./EvalDevToolModulePlugin");
344366

345-
new EvalDevToolModulePlugin({
346-
moduleFilenameTemplate: options.output.devtoolModuleFilenameTemplate,
347-
namespace: options.output.devtoolNamespace
348-
}).apply(compiler);
367+
new EvalDevToolModulePlugin({
368+
moduleFilenameTemplate:
369+
options.output.devtoolModuleFilenameTemplate,
370+
namespace: options.output.devtoolNamespace
371+
}).apply(compiler);
372+
}
349373
}
350374
}
351375

lib/config/defaults.js

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const {
3939

4040
/** @typedef {import("../../declarations/WebpackOptions").CacheOptionsNormalized} CacheOptionsNormalized */
4141
/** @typedef {import("../../declarations/WebpackOptions").Context} Context */
42+
/** @typedef {import("../../declarations/WebpackOptions").DevTool} Devtool */
4243
/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorOptions} CssGeneratorOptions */
4344
/** @typedef {import("../../declarations/WebpackOptions").EntryDescription} EntryDescription */
4445
/** @typedef {import("../../declarations/WebpackOptions").EntryNormalized} Entry */
@@ -342,7 +343,28 @@ const applyWebpackOptionsDefaults = (options, compilerIndex) => {
342343
}
343344
}
344345

345-
F(options, "devtool", () => (development ? "eval" : false));
346+
F(
347+
options,
348+
"devtool",
349+
() =>
350+
/** @type {Devtool} */ (
351+
development
352+
? [
353+
options.experiments.css
354+
? {
355+
type: "css",
356+
use: "source-map"
357+
}
358+
: undefined,
359+
{
360+
type: "javascript",
361+
use: "eval"
362+
}
363+
].filter(Boolean)
364+
: false
365+
)
366+
);
367+
346368
D(options, "watch", false);
347369
D(options, "profile", false);
348370
D(options, "parallelism", 100);
@@ -1443,9 +1465,7 @@ const applyOutputDefaults = (
14431465
throw new Error(
14441466
"For the selected environment is no default ESM chunk format available:\n" +
14451467
"ESM exports can be chosen when 'import()' is available.\n" +
1446-
`JSONP Array push can be chosen when 'document' is available.\n${
1447-
helpMessage
1448-
}`
1468+
`JSONP Array push can be chosen when 'document' is available.\n${helpMessage}`
14491469
);
14501470
} else {
14511471
if (tp.document) return "array-push";
@@ -1460,9 +1480,7 @@ const applyOutputDefaults = (
14601480
: ""
14611481
}\n` +
14621482
"JSONP Array push ('array-push') can be chosen when 'document' or 'importScripts' is available.\n" +
1463-
`CommonJs exports ('commonjs') can be chosen when 'require' or node builtins are available.\n${
1464-
helpMessage
1465-
}`
1483+
`CommonJs exports ('commonjs') can be chosen when 'require' or node builtins are available.\n${helpMessage}`
14661484
);
14671485
}
14681486
}

lib/config/normalization.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ const util = require("util");
2424
/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptionsNormalized */
2525
/** @typedef {import("../WebpackError")} WebpackError */
2626

27+
/**
28+
* @typedef {object} WebpackOptionsInterception
29+
* @property {WebpackOptionsNormalized["devtool"]=} devtool
30+
*/
31+
2732
const handledDeprecatedNoEmitOnErrors = util.deprecate(
2833
/**
2934
* @param {boolean} noEmitOnErrors no emit on errors
@@ -582,4 +587,95 @@ const getNormalizedOptimizationRuntimeChunk = (runtimeChunk) => {
582587
};
583588
};
584589

590+
/**
591+
* @param {WebpackOptionsNormalized} options options to be intercepted
592+
* @returns {{ options: WebpackOptionsNormalized, interception?: WebpackOptionsInterception }} options and interception
593+
*/
594+
const applyWebpackOptionsInterception = (options) => {
595+
// Return origin options when backCompat is disabled
596+
if (options.experiments.futureDefaults) {
597+
return {
598+
options
599+
};
600+
}
601+
602+
// TODO webpack6 - remove compatibility logic and move `devtools` fully into `devtool` with multi-type support
603+
let _devtool = options.devtool;
604+
/** @type {WebpackOptionsNormalized["devtool"]} */
605+
let cached;
606+
607+
const devtoolBackCompat = () => {
608+
if (Array.isArray(_devtool)) {
609+
if (cached) return cached;
610+
// Prefer `all`, then `javascript`, then `css`
611+
const match = ["all", "javascript", "css"]
612+
.map((type) =>
613+
/** @type {Extract<WebpackOptionsNormalized["devtool"], Array<EXPECTED_ANY>>} */ (
614+
_devtool
615+
).find((item) => item.type === type)
616+
)
617+
.find(Boolean);
618+
619+
// If `devtool: []` is specified, return `false` here
620+
return (cached = match ? match.use : false);
621+
}
622+
return _devtool;
623+
};
624+
625+
/** @type {ProxyHandler<WebpackOptionsNormalized>} */
626+
const handler = Object.create(null);
627+
handler.get = (target, prop, receiver) => {
628+
if (prop === "devtool") {
629+
return devtoolBackCompat();
630+
}
631+
return Reflect.get(target, prop, receiver);
632+
};
633+
handler.set = (target, prop, value, receiver) => {
634+
if (prop === "devtool") {
635+
_devtool = value;
636+
cached = undefined;
637+
return true;
638+
}
639+
return Reflect.set(target, prop, value, receiver);
640+
};
641+
handler.deleteProperty = (target, prop) => {
642+
if (prop === "devtool") {
643+
_devtool = undefined;
644+
cached = undefined;
645+
return true;
646+
}
647+
return Reflect.deleteProperty(target, prop);
648+
};
649+
handler.defineProperty = (target, prop, descriptor) => {
650+
if (prop === "devtool") {
651+
_devtool = descriptor.value;
652+
cached = undefined;
653+
return true;
654+
}
655+
return Reflect.defineProperty(target, prop, descriptor);
656+
};
657+
handler.getOwnPropertyDescriptor = (target, prop) => {
658+
if (prop === "devtool") {
659+
return {
660+
configurable: true,
661+
enumerable: true,
662+
value: devtoolBackCompat(),
663+
writable: true
664+
};
665+
}
666+
return Reflect.getOwnPropertyDescriptor(target, prop);
667+
};
668+
669+
return {
670+
options: new Proxy(options, handler),
671+
interception: {
672+
get devtool() {
673+
return _devtool;
674+
}
675+
}
676+
};
677+
};
678+
679+
module.exports.applyWebpackOptionsInterception =
680+
applyWebpackOptionsInterception;
585681
module.exports.getNormalizedWebpackOptions = getNormalizedWebpackOptions;

lib/webpack.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@ const {
1515
applyWebpackOptionsBaseDefaults,
1616
applyWebpackOptionsDefaults
1717
} = require("./config/defaults");
18-
const { getNormalizedWebpackOptions } = require("./config/normalization");
18+
const {
19+
applyWebpackOptionsInterception,
20+
getNormalizedWebpackOptions
21+
} = require("./config/normalization");
1922
const NodeEnvironmentPlugin = require("./node/NodeEnvironmentPlugin");
2023
const memoize = require("./util/memoize");
2124

2225
/** @typedef {import("../declarations/WebpackOptions").WebpackOptions} WebpackOptions */
2326
/** @typedef {import("../declarations/WebpackOptions").WebpackPluginFunction} WebpackPluginFunction */
2427
/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptionsNormalizedWithDefaults */
28+
/** @typedef {import("./config/normalization").WebpackOptionsInterception} WebpackOptionsInterception */
2529
/** @typedef {import("./Compiler").WatchOptions} WatchOptions */
2630
/** @typedef {import("./MultiCompiler").MultiCompilerOptions} MultiCompilerOptions */
2731
/** @typedef {import("./MultiCompiler").MultiWebpackOptions} MultiWebpackOptions */
@@ -68,8 +72,13 @@ const createMultiCompiler = (childOptions, options) => {
6872
* @returns {Compiler} a compiler
6973
*/
7074
const createCompiler = (rawOptions, compilerIndex) => {
71-
const options = getNormalizedWebpackOptions(rawOptions);
75+
let options = getNormalizedWebpackOptions(rawOptions);
7276
applyWebpackOptionsBaseDefaults(options);
77+
78+
/** @type {WebpackOptionsInterception=} */
79+
let interception;
80+
({ options, interception } = applyWebpackOptionsInterception(options));
81+
7382
const compiler = new Compiler(
7483
/** @type {string} */ (options.context),
7584
options
@@ -99,7 +108,8 @@ const createCompiler = (rawOptions, compilerIndex) => {
99108
new WebpackOptionsApply().process(
100109
/** @type {WebpackOptionsNormalizedWithDefaults} */
101110
(options),
102-
compiler
111+
compiler,
112+
interception
103113
);
104114
compiler.hooks.initialize.call();
105115
return compiler;

schemas/WebpackOptions.check.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)