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
5 changes: 5 additions & 0 deletions .changeset/precompiled-options-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"minimizer-webpack-plugin": patch
---

perf: validate options with a check precompiled by `schema-utils precompile`, so valid options no longer load `ajv` and compile the schema, about 200ms off every production webpack start
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"CHANGELOG.md",
"package.json",
"src/serialize-javascript.js",
"src/options.check.js",
"dist/**",
"**/__snapshots__/**",
"package-lock.json",
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
src/serialize-javascript.js
src/options.check.js
/coverage
/dist
/node_modules
Expand Down
3 changes: 3 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ module.exports = (api) => {
api.cache(true);

return {
// Generated by `schema-utils precompile` for Node.js 10.13.0 already, so it
// is copied as is rather than grown three times by the transforms
ignore: ["src/options.check.js"],
presets: [
[
"@babel/preset-env",
Expand Down
2 changes: 2 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { defineConfig } from "eslint/config";
import configs from "eslint-config-webpack/configs.js";

export default defineConfig([
// Generated by `schema-utils precompile`, `npm run fix:schema` updates it
{ ignores: ["src/options.check.js"] },
{
ignores: ["./src/serialize-javascript.js"],
extends: [configs["recommended-dirty"]],
Expand Down
10 changes: 9 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,15 @@ module.exports = {
// older Node on macOS), where webpack + multiple minimizers per asset
// routinely take longer than that.
testTimeout: 60000,
coveragePathIgnorePatterns: ["src/serialize-javascript.js"],
coveragePathIgnorePatterns: [
"src/serialize-javascript.js",
"src/options.check.js",
],
// Babel ignores the generated check, see `babel.config.js`
transformIgnorePatterns: [
"/node_modules/",
"<rootDir>/src/options\\.check\\.js$",
],
snapshotSerializers: ["<rootDir>/test/helpers/snapshotHashSerializer.js"],
testPathIgnorePatterns,
};
27 changes: 15 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,16 @@
"build": "npm-run-all -p \"build:**\"",
"security": "npm audit --production",
"lint:serialize-javascript": "node ./scripts/copy-serialize-javascript.js --check",
"lint:schema": "schema-utils precompile --check src/options.json",
"lint:prettier": "prettier --list-different .",
"lint:code": "eslint --cache .",
"lint:spelling": "cspell \"**/*.*\"",
"lint:types": "tsc --pretty --noEmit",
"lint": "npm-run-all -l -p \"lint:**\"",
"fix:code": "npm run lint:code -- --fix",
"fix:prettier": "npm run lint:prettier -- --write",
"fix": "npm-run-all -l fix:code fix:prettier",
"fix:schema": "schema-utils precompile src/options.json",
"fix": "npm-run-all -l fix:schema fix:code fix:prettier",
"test:base": "node ./scripts/jest.js",
"test:watch": "npm run test:base -- --watch",
"test:coverage": "npm run test:base -- --collectCoverageFrom=\"src/**/*.js\" --coverage",
Expand All @@ -83,7 +85,7 @@
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.31",
"jest-worker": "^27.4.5",
"schema-utils": "^4.3.3",
"schema-utils": "^4.6.0",
"terser": "^5.51.0"
},
"devDependencies": {
Expand Down
4 changes: 4 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2413,10 +2413,13 @@ class MinimizerPlugin {
*/
validateOptions(compiler) {
if (typeof compiler.validate === "function") {
// The precompiled check validates valid options without `ajv`, webpack
// only compiles the schema to report what is wrong with invalid ones
compiler.validate(
() => /** @type {Schema} */ (require("./options.json")),
this.rawOptions,
VALIDATION_CONFIGURATION,
require("./options.check.js"),
);
this.validateMinimizers();
this.validateGenerators();
Expand All @@ -2432,6 +2435,7 @@ class MinimizerPlugin {
/** @type {Schema} */ (require("./options.json")),
this.rawOptions,
VALIDATION_CONFIGURATION,
require("./options.check.js"),
);
this.validateMinimizers();
this.validateGenerators();
Expand Down
7 changes: 7 additions & 0 deletions src/options.check.js

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions test/__snapshots__/validate-options.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -127,22 +127,22 @@ exports[`validation validate 8`] = `
exports[`validation validate 9`] = `
"Invalid options object. Minimizer Plugin has been initialized using an options object that does not match the API schema.
- options.minify should be one of these:
function | non-empty string | object { path, export? } | [function | non-empty string | object { path, export? } | object { implementation, options?, filter? }, ...] | object { implementation, options?, filter? }
function | non-empty string | object { path: non-empty string, export?: non-empty string } | [function | non-empty string | object { path: non-empty string, export?: non-empty string } | object { implementation, options?, filter?: function }, ...] | object { implementation, options?, filter?: function }
-> Allows you to override default minify function. Written as an object it states how to run one minimizer, options included. An empty array is no minimizers at all, for an instance whose whole job is its \`generate\`. A string or \`{ path, export }\` loads the minimizer by module path in workers (like sass-loader \`implementation\`).
-> Read more at https://github.com/webpack/minimizer-webpack-plugin#number
Details:
* options.minify should be one of these:
function | non-empty string | object { path, export? }
function | non-empty string | object { path: non-empty string, export?: non-empty string }
-> The minimizer itself: a function, a module path string (worker \`require\`s it), or \`{ path, export }\` for a named export.
Details:
* options.minify should be an instance of function.
* options.minify should be a non-empty string.
* options.minify should be an object:
object { path, export? }
object { path: non-empty string, export?: non-empty string }
* options.minify should be an array:
[function | non-empty string | object { path, export? } | object { implementation, options?, filter? }, ...]
[function | non-empty string | object { path: non-empty string, export?: non-empty string } | object { implementation, options?, filter?: function }, ...]
* options.minify should be an object:
object { implementation, options?, filter? }"
object { implementation, options?, filter?: function }"
`;

exports[`validation validate 10`] = `
Expand Down Expand Up @@ -247,20 +247,20 @@ exports[`validation validate 18`] = `
exports[`validation validate 19`] = `
"Invalid options object. Minimizer Plugin has been initialized using an options object that does not match the API schema.
- options.minify should be one of these:
function | non-empty string | object { path, export? } | [function | non-empty string | object { path, export? } | object { implementation, options?, filter? }, ...] | object { implementation, options?, filter? }
function | non-empty string | object { path: non-empty string, export?: non-empty string } | [function | non-empty string | object { path: non-empty string, export?: non-empty string } | object { implementation, options?, filter?: function }, ...] | object { implementation, options?, filter?: function }
-> Allows you to override default minify function. Written as an object it states how to run one minimizer, options included. An empty array is no minimizers at all, for an instance whose whole job is its \`generate\`. A string or \`{ path, export }\` loads the minimizer by module path in workers (like sass-loader \`implementation\`).
-> Read more at https://github.com/webpack/minimizer-webpack-plugin#number
Details:
* options.minify should be one of these:
function | non-empty string | object { path, export? }
function | non-empty string | object { path: non-empty string, export?: non-empty string }
-> The minimizer itself: a function, a module path string (worker \`require\`s it), or \`{ path, export }\` for a named export.
Details:
* options.minify should be an instance of function.
* options.minify should be a non-empty string.
* options.minify should be an object:
object { path, export? }
object { path: non-empty string, export?: non-empty string }
* options.minify should be an array:
[function | non-empty string | object { path, export? } | object { implementation, options?, filter? }, ...]
[function | non-empty string | object { path: non-empty string, export?: non-empty string } | object { implementation, options?, filter?: function }, ...]
* options.minify should be an object:
object { implementation, options?, filter? }"
object { implementation, options?, filter?: function }"
`;
24 changes: 20 additions & 4 deletions test/embedded-source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ const htmlMinify = require(WEBPACK_HTML_MINIFY);

const fixture = (name) => path.resolve(__dirname, "./fixtures/embedded", name);

/**
* A one-rule stylesheet with its declarations sorted: webpack 5.112's CSS
* minimizer emits them in a different order than 5.110 and 5.111 did.
* @param {string} css minified stylesheet
* @returns {string} the stylesheet with sorted declarations
*/
const sortDeclarations = (css) => {
const [, selector, body] = /^([^{]+)\{(.*)\}$/.exec(css);

return `${selector}{${body.split(";").sort().join(";")}}`;
};

/**
* The three minify functions webpack's own `optimization.minimize` wires into
* one plugin instance, which is what makes this work out of the box.
Expand Down Expand Up @@ -642,8 +654,10 @@ describe("embedded source", () => {
const stats = await compile(compiler);

expect(getErrors(stats)).toEqual([]);
expect(readAsset("sheet.css", compiler, stats)).toBe(
".a{background:url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'> <rect /> </svg>\");color:red}",
expect(sortDeclarations(readAsset("sheet.css", compiler, stats))).toBe(
sortDeclarations(
".a{background:url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'> <rect /> </svg>\");color:red}",
),
);
});

Expand Down Expand Up @@ -739,8 +753,10 @@ describe("embedded source", () => {
const stats = await compile(compiler);

expect(getErrors(stats)).toEqual([]);
expect(readAsset("sheet.css", compiler, stats)).toBe(
".a{background:url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'> <rect /> </svg>\");color:red}",
expect(sortDeclarations(readAsset("sheet.css", compiler, stats))).toBe(
sortDeclarations(
".a{background:url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'> <rect /> </svg>\");color:red}",
),
);
});
});
Expand Down
28 changes: 26 additions & 2 deletions test/validate-options.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,37 @@
import { validate } from "schema-utils";

import MinimizerPlugin from "../src";
import check from "../src/options.check";
import schema from "../src/options.json";

import { getCompiler } from "./helpers";

/**
* Whether the full validation accepts the options.
* @param {EXPECTED_ANY} options plugin options
* @returns {boolean} true when valid
*/
const isValid = (options) => {
try {
validate(/** @type {EXPECTED_ANY} */ (schema), options);
} catch (_err) {
return false;
}

return true;
};

/**
* @param {EXPECTED_ANY} options plugin options
* @returns {import("webpack").Compiler} compiler
*/
const createCompiler = (options) =>
getCompiler({ plugins: [new MinimizerPlugin(options)] });
const createCompiler = (options) => {
// The precompiled check has to agree with the schema it was compiled from,
// a valid value is never validated against the schema itself
expect(check(options === undefined ? {} : options)).toBe(isValid(options));

return getCompiler({ plugins: [new MinimizerPlugin(options)] });
};

describe("validation", () => {
it("validate", () => {
Expand Down
16 changes: 16 additions & 0 deletions types/options.check.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export = validate11;
declare function validate11(
data: any,
{
instancePath,
parentData,
parentDataProperty,
rootData,
}?: {
instancePath?: string | undefined;
rootData?: any;
},
): boolean;
declare namespace validate11 {
export { validate11 as default };
}
Loading