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/precompile-source-processor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"schema-utils": patch
---

Minify a precompiled check with webpack's `SourceProcessor` where webpack has one.
10 changes: 5 additions & 5 deletions package-lock.json

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

29 changes: 22 additions & 7 deletions src/precompile.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,8 @@ async function loadFileSchema(uri) {
* @returns {Promise<string>} code without error objects, minified
*/
async function minify(code, esm) {
/** @type {{ minify: (code: string, options: Record<string, EXPECTED_ANY>) => Promise<{ code?: string }> }} */
let minifier;
/** @type {(code: string, options: Record<string, EXPECTED_ANY>) => Promise<string> | string} */
let minifyWith;

// webpack's own minifier, which writes what terser writes without loading it - the packages
// shipping a check already have webpack. A bundler that bundles `schema-utils` must neither
Expand All @@ -280,9 +280,25 @@ async function minify(code, esm) {
const self = module;

try {
minifier = await (nodeRequire || self.require.bind(self))(
const { syntax } = (nodeRequire || self.require.bind(self))(
"webpack",
).javascript.syntax.printer.load();
).javascript;

if (syntax.SourceProcessor) {
// One `SourceProcessor` run reads, mangles, compresses and prints it.
minifyWith = (source, options) =>
new syntax.SourceProcessor().process(source, {
...options,
sourceType: esm ? "module" : "script",
mode: "minify",
}).code;
} else {
// TODO in the next major release: require the webpack whose `SourceProcessor` minifies and drop `printer.load()`
const minifier = await syntax.printer.load();

minifyWith = async (source, options) =>
/** @type {string} */ ((await minifier.minify(source, options)).code);
}
} catch (err) {
throw new Error(
"Install webpack 5.112.0 or later to minify a precompiled check",
Expand All @@ -296,15 +312,14 @@ async function minify(code, esm) {
/(=\s*\[?)\{instancePath(?::instancePath\b[^{}]*?)?,schemaPath:"(?:[^"\\]|\\.)*",keyword:"\w+",/g,
"$1{",
);
const result = await minifier.minify(stripped, {

return minifyWith(stripped, {
compress: { passes: 3 },
mangle: true,
ecma: 2015,
module: esm,
toplevel: true,
});

return /** @type {string} */ (result.code);
}

/**
Expand Down
44 changes: 44 additions & 0 deletions test/precompile.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,50 @@ describe("precompile", () => {
expect(code).not.toMatch(/require\(|module\.exports/);
});

it("should minify with webpack's SourceProcessor where it has one", async () => {
/** @type {Record<string, EXPECTED_ANY>[]} */
const runs = [];

jest.doMock("webpack", () => ({
javascript: {
syntax: {
SourceProcessor: class {
/**
* @param {string} code code
* @param {Record<string, EXPECTED_ANY>} options options
* @returns {{ code: string }} the code
*/
process(code, options) {
runs.push(options);
return { code: `/* minified */${code.length}` };
}
},
},
},
}));

try {
const code = await precompile(schema, { esm: true, minify: true });

expect(code).toMatch(/\/\* minified \*\/\d+$/);
expect(runs).toEqual([
{
compress: { passes: 3 },
mangle: true,
ecma: 2015,
module: true,
toplevel: true,
sourceType: "module",
mode: "minify",
},
]);
} finally {
jest.dontMock("webpack");
// A mock once required stays in the registry for the next `doMock`.
jest.resetModules();
}
});

it("should ask for webpack when it has no minifier", async () => {
// webpack before 5.112.0
jest.doMock("webpack", () => ({}));
Expand Down
Loading