Skip to content

Commit b404817

Browse files
authored
feat: add validate option to enable/disable validation (#20275)
1 parent f11ef01 commit b404817

57 files changed

Lines changed: 1238 additions & 728 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/quiet-spies-develop.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack": feat
3+
---
4+
5+
Added the `validate` option to enable/disable validation in webpack/plugins/loaders, also implemented API to make it inside plugins.

declarations/WebpackOptions.d.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,10 @@ export type WarningFilterItemTypes =
733733
* Environment to build for. An array of environments to build for all of them when possible.
734734
*/
735735
export type Target = string[] | false | string;
736+
/**
737+
* Enable validation of webpack configuration. Defaults to true in development mode. In production mode, defaults to true unless futureDefaults is enabled, then defaults to false.
738+
*/
739+
export type Validate = boolean;
736740
/**
737741
* Enter watch mode, which rebuilds on file change.
738742
*/
@@ -1030,6 +1034,10 @@ export interface WebpackOptions {
10301034
* Environment to build for. An array of environments to build for all of them when possible.
10311035
*/
10321036
target?: Target;
1037+
/**
1038+
* Enable validation of webpack configuration. Defaults to true in development mode. In production mode, defaults to true unless futureDefaults is enabled, then defaults to false.
1039+
*/
1040+
validate?: Validate;
10331041
/**
10341042
* Enter watch mode, which rebuilds on file change.
10351043
*/
@@ -3957,6 +3965,10 @@ export interface WebpackOptionsNormalized {
39573965
* Environment to build for. An array of environments to build for all of them when possible.
39583966
*/
39593967
target?: Target;
3968+
/**
3969+
* Enable validation of webpack configuration. Defaults to true in development mode. In production mode, defaults to true unless futureDefaults is enabled, then defaults to false.
3970+
*/
3971+
validate?: Validate;
39603972
/**
39613973
* Enter watch mode, which rebuilds on file change.
39623974
*/

lib/BannerPlugin.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,17 @@ const { ConcatSource } = require("webpack-sources");
99
const Compilation = require("./Compilation");
1010
const ModuleFilenameHelpers = require("./ModuleFilenameHelpers");
1111
const Template = require("./Template");
12-
const createSchemaValidation = require("./util/create-schema-validation");
1312

1413
/** @typedef {import("webpack-sources").Source} Source */
1514
/** @typedef {import("../declarations/plugins/BannerPlugin").BannerPluginArgument} BannerPluginArgument */
15+
/** @typedef {import("../declarations/plugins/BannerPlugin").BannerPluginOptions} BannerPluginOptions */
1616
/** @typedef {import("./Compilation").PathData} PathData */
1717
/** @typedef {import("./Compiler")} Compiler */
1818
/** @typedef {import("./Chunk")} Chunk */
1919
/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath */
2020

2121
/** @typedef {(data: { hash?: string, chunk: Chunk, filename: string }) => string} BannerFunction */
2222

23-
const validate = createSchemaValidation(
24-
/** @type {((value: typeof import("../schemas/plugins/BannerPlugin.json")) => boolean)} */
25-
(require("../schemas/plugins/BannerPlugin.check")),
26-
() => require("../schemas/plugins/BannerPlugin.json"),
27-
{
28-
name: "Banner Plugin",
29-
baseDataPath: "options"
30-
}
31-
);
32-
3323
/**
3424
* @param {string} str string to wrap
3525
* @returns {string} wrapped string
@@ -59,8 +49,7 @@ class BannerPlugin {
5949
};
6050
}
6151

62-
validate(options);
63-
52+
/** @type {BannerPluginOptions} */
6453
this.options = options;
6554

6655
const bannerOption = options.banner;
@@ -85,6 +74,17 @@ class BannerPlugin {
8574
* @returns {void}
8675
*/
8776
apply(compiler) {
77+
compiler.hooks.validate.tap(PLUGIN_NAME, () => {
78+
compiler.validate(
79+
() => require("../schemas/plugins/BannerPlugin.json"),
80+
this.options,
81+
{
82+
name: "Banner Plugin",
83+
baseDataPath: "options"
84+
},
85+
(options) => require("../schemas/plugins/BannerPlugin.check")(options)
86+
);
87+
});
8888
const options = this.options;
8989
const banner = this.banner;
9090
const matchObject = ModuleFilenameHelpers.matchObject.bind(

lib/CleanPlugin.js

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ const path = require("path");
99
const asyncLib = require("neo-async");
1010
const { SyncBailHook } = require("tapable");
1111
const Compilation = require("./Compilation");
12-
const createSchemaValidation = require("./util/create-schema-validation");
1312
const { join } = require("./util/fs");
1413
const processAsyncTree = require("./util/processAsyncTree");
1514

@@ -33,21 +32,6 @@ const processAsyncTree = require("./util/processAsyncTree");
3332
* @returns {boolean | undefined} true, if the path should be kept
3433
*/
3534

36-
const validate = createSchemaValidation(
37-
undefined,
38-
() => {
39-
const { definitions } = require("../schemas/WebpackOptions.json");
40-
41-
return {
42-
definitions,
43-
oneOf: [{ $ref: "#/definitions/CleanOptions" }]
44-
};
45-
},
46-
{
47-
name: "Clean Plugin",
48-
baseDataPath: "options"
49-
}
50-
);
5135
const _10sec = 10 * 1000;
5236

5337
/**
@@ -368,9 +352,8 @@ class CleanPlugin {
368352

369353
/** @param {CleanOptions} options options */
370354
constructor(options = {}) {
371-
validate(options);
372-
/** @type {CleanOptions & { dry: boolean }} */
373-
this.options = { dry: false, ...options };
355+
/** @type {CleanOptions} */
356+
this.options = options;
374357
}
375358

376359
/**
@@ -379,8 +362,28 @@ class CleanPlugin {
379362
* @returns {void}
380363
*/
381364
apply(compiler) {
382-
const { dry, keep } = this.options;
365+
compiler.hooks.validate.tap(PLUGIN_NAME, () => {
366+
compiler.validate(
367+
() => {
368+
const { definitions } = require("../schemas/WebpackOptions.json");
369+
370+
return {
371+
definitions,
372+
oneOf: [{ $ref: "#/definitions/CleanOptions" }]
373+
};
374+
},
375+
this.options,
376+
{
377+
name: "Clean Plugin",
378+
baseDataPath: "options"
379+
}
380+
);
381+
});
382+
383+
const { keep } = this.options;
383384

385+
/** @type {boolean} */
386+
const dry = this.options.dry || false;
384387
/** @type {KeepFn} */
385388
const keepFn =
386389
typeof keep === "function"

lib/Compiler.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const WebpackError = require("./WebpackError");
3030
const { Logger } = require("./logging/Logger");
3131
const { dirname, join, mkdirp } = require("./util/fs");
3232
const { makePathsRelative } = require("./util/identifier");
33+
const memoize = require("./util/memoize");
3334
const { isSourceEqual } = require("./util/source");
3435
const webpack = require(".");
3536

@@ -61,6 +62,9 @@ const webpack = require(".");
6162
/** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */
6263
/** @typedef {import("./util/fs").TimeInfoEntries} TimeInfoEntries */
6364
/** @typedef {import("./util/fs").WatchFileSystem} WatchFileSystem */
65+
/** @typedef {import("schema-utils").validate} Validate */
66+
/** @typedef {import("schema-utils").Schema} Schema */
67+
/** @typedef {import("schema-utils").ValidationErrorConfiguration} ValidationErrorConfiguration */
6468

6569
/**
6670
* @typedef {object} CompilationParams
@@ -155,6 +159,8 @@ const includesHash = (filename, hashes) => {
155159
return filename.includes(hashes);
156160
};
157161

162+
const getValidate = memoize(() => require("schema-utils").validate);
163+
158164
class Compiler {
159165
/**
160166
* @param {string} context the compilation path
@@ -226,6 +232,8 @@ class Compiler {
226232
// TODO the following hooks are weirdly located here
227233
// TODO move them for webpack 5
228234
/** @type {SyncHook<[]>} */
235+
validate: new SyncHook([]),
236+
/** @type {SyncHook<[]>} */
229237
environment: new SyncHook([]),
230238
/** @type {SyncHook<[]>} */
231239
afterEnvironment: new SyncHook([]),
@@ -1408,6 +1416,57 @@ ${other}`);
14081416
this.cache.shutdown(callback);
14091417
});
14101418
}
1419+
1420+
/**
1421+
* Schema validation function with optional pre-compiled check
1422+
* @template {EXPECTED_OBJECT | EXPECTED_OBJECT[]} [T=EXPECTED_OBJECT]
1423+
* @param {Schema | (() => Schema)} schema schema
1424+
* @param {T} value value
1425+
* @param {ValidationErrorConfiguration=} options options
1426+
* @param {((value: T) => boolean)=} check options
1427+
*/
1428+
validate(schema, value, options, check) {
1429+
// Avoid validation at all when disabled
1430+
if (this.options.validate === false) {
1431+
return;
1432+
}
1433+
1434+
/**
1435+
* @returns {Schema} schema
1436+
*/
1437+
const getSchema = () => {
1438+
if (typeof schema === "function") {
1439+
return schema();
1440+
}
1441+
1442+
return schema;
1443+
};
1444+
1445+
// // If we have precompiled schema let's use it
1446+
if (check) {
1447+
if (!check(value)) {
1448+
getValidate()(
1449+
getSchema(),
1450+
/** @type {EXPECTED_OBJECT | EXPECTED_OBJECT[]} */
1451+
(value),
1452+
options
1453+
);
1454+
require("util").deprecate(
1455+
() => {},
1456+
"webpack bug: Pre-compiled schema reports error while real schema is happy. This has performance drawbacks.",
1457+
"DEP_WEBPACK_PRE_COMPILED_SCHEMA_INVALID"
1458+
)();
1459+
}
1460+
return;
1461+
}
1462+
1463+
// Otherwise let's standard validation
1464+
getValidate()(
1465+
getSchema(),
1466+
/** @type {EXPECTED_OBJECT | EXPECTED_OBJECT[]} */ (value),
1467+
options
1468+
);
1469+
}
14111470
}
14121471

14131472
module.exports = Compiler;

lib/DllPlugin.js

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,21 @@
88
const DllEntryPlugin = require("./DllEntryPlugin");
99
const FlagAllModulesAsUsedPlugin = require("./FlagAllModulesAsUsedPlugin");
1010
const LibManifestPlugin = require("./LibManifestPlugin");
11-
const createSchemaValidation = require("./util/create-schema-validation");
1211

1312
/** @typedef {import("../declarations/plugins/DllPlugin").DllPluginOptions} DllPluginOptions */
1413
/** @typedef {import("./Compiler")} Compiler */
1514
/** @typedef {import("./DllEntryPlugin").Entries} Entries */
1615
/** @typedef {import("./DllEntryPlugin").Options} Options */
1716

18-
const validate = createSchemaValidation(
19-
require("../schemas/plugins/DllPlugin.check"),
20-
() => require("../schemas/plugins/DllPlugin.json"),
21-
{
22-
name: "Dll Plugin",
23-
baseDataPath: "options"
24-
}
25-
);
26-
2717
const PLUGIN_NAME = "DllPlugin";
2818

2919
class DllPlugin {
3020
/**
3121
* @param {DllPluginOptions} options options object
3222
*/
3323
constructor(options) {
34-
validate(options);
35-
this.options = {
36-
...options,
37-
entryOnly: options.entryOnly !== false
38-
};
24+
/** @type {DllPluginOptions} */
25+
this.options = options;
3926
}
4027

4128
/**
@@ -44,6 +31,19 @@ class DllPlugin {
4431
* @returns {void}
4532
*/
4633
apply(compiler) {
34+
compiler.hooks.validate.tap(PLUGIN_NAME, () => {
35+
compiler.validate(
36+
() => require("../schemas/plugins/DllPlugin.json"),
37+
this.options,
38+
{
39+
name: "Dll Plugin",
40+
baseDataPath: "options"
41+
},
42+
(options) => require("../schemas/plugins/DllPlugin.check")(options)
43+
);
44+
});
45+
46+
const entryOnly = this.options.entryOnly !== false;
4747
compiler.hooks.entryOption.tap(PLUGIN_NAME, (context, entry) => {
4848
if (typeof entry !== "function") {
4949
for (const name of Object.keys(entry)) {
@@ -63,8 +63,8 @@ class DllPlugin {
6363
}
6464
return true;
6565
});
66-
new LibManifestPlugin(this.options).apply(compiler);
67-
if (!this.options.entryOnly) {
66+
new LibManifestPlugin({ ...this.options, entryOnly }).apply(compiler);
67+
if (!entryOnly) {
6868
new FlagAllModulesAsUsedPlugin(PLUGIN_NAME).apply(compiler);
6969
}
7070
}

0 commit comments

Comments
 (0)