From 438510365f7b2bcdfc6e06a6ce8aae976403cdf1 Mon Sep 17 00:00:00 2001 From: sheo13666q Date: Sun, 18 Oct 2026 23:08:05 +0000 Subject: [PATCH 1/2] fix: bundle schema-utils without errors or warnings precompile required the ajv helpers it inlines with a request built from their name and webpack with a variable request. A bundler that bundles schema-utils, as webpack's own test cases do, turned the first into a context over ajv/dist/runtime and failed to parse its .d.ts and .map files, and warned about the second. The helpers are now required by name and webpack through an alias of module, so bundling schema-utils reports nothing. The minified output is byte-identical. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QYPkbvwyRWt2SHgXDoYRdo --- .changeset/precompile-bundle.md | 5 ++++ src/precompile.js | 18 ++++++++++---- test/precompile.test.js | 43 +++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 .changeset/precompile-bundle.md diff --git a/.changeset/precompile-bundle.md b/.changeset/precompile-bundle.md new file mode 100644 index 0000000..d509ec7 --- /dev/null +++ b/.changeset/precompile-bundle.md @@ -0,0 +1,5 @@ +--- +"schema-utils": patch +--- + +Bundle `schema-utils` without errors or warnings: `precompile` requires the `ajv` helpers it inlines by name and loads webpack's minifier without a request a bundler parses. diff --git a/src/precompile.js b/src/precompile.js index 73c9947..ac09479 100644 --- a/src/precompile.js +++ b/src/precompile.js @@ -130,6 +130,13 @@ function addKeywords(ajv, hoist) { }); } +// Each helper is required by its own name: a request built from the name would make a bundler +// that bundles `schema-utils` parse every file in `ajv/dist/runtime` +const INLINE_HELPERS = { + equal: () => require("ajv/dist/runtime/equal").default, + ucs2length: () => require("ajv/dist/runtime/ucs2length").default, +}; + /** * @param {string} code generated code * @param {boolean} esm whether the code is an ES module @@ -147,7 +154,7 @@ function useRuntime(code, esm, inline) { // WHY: loading a helper through `schema-utils` costs about 1ms, more than the check itself // takes to run for most options - both functions are small and self-contained, `uri` is not if (inline && name !== "uri") { - return `(${require(`ajv/dist/runtime/${name}`).default.toString()})`; + return `(${INLINE_HELPERS[/** @type {"equal" | "ucs2length"} */ (name)]().toString()})`; } // ajv keeps `require` for its helpers in an ES module too @@ -262,12 +269,13 @@ async function minify(code, esm) { let minifier; // webpack's own minifier, which writes what terser writes without loading it - the packages - // shipping a check already have webpack. Required by name so neither TypeScript nor a bundler - // reaches for it. - const WEBPACK = "webpack"; + // shipping a check already have webpack. Required through an alias of `module` so a bundler + // that bundles `schema-utils` neither bundles webpack nor warns about a request it cannot + // resolve, while Node.js resolves it like any `require` here. + const self = module; try { - minifier = await require(WEBPACK).javascript.syntax.printer.load(); + minifier = await self.require("webpack").javascript.syntax.printer.load(); } catch (err) { throw new Error( "Install webpack 5.112.0 or later to minify a precompiled check", diff --git a/test/precompile.test.js b/test/precompile.test.js index 8e6cb64..9c71cbd 100644 --- a/test/precompile.test.js +++ b/test/precompile.test.js @@ -598,3 +598,46 @@ describe("precompile", () => { }); }); }); + +describe("bundled", () => { + // `schema-utils` is bundled by tools that ship it inside their own code, which reaches + // `precompile` through the `precompile` export even though it only runs at build time + it("should bundle without errors or warnings", (done) => { + const webpack = require("webpack"); + + const outputPath = fs.mkdtempSync(path.join(os.tmpdir(), "schema-utils-")); + + webpack( + { + mode: "none", + target: "node", + context: path.resolve(__dirname, ".."), + entry: "./src/index.js", + output: { path: outputPath }, + }, + (err, stats) => { + remove(outputPath); + + if (err) { + done(err); + + return; + } + + try { + const { errors, warnings } = stats.toJson({ + all: false, + errors: true, + warnings: true, + }); + + expect(errors.map(({ message }) => message)).toEqual([]); + expect(warnings.map(({ message }) => message)).toEqual([]); + done(); + } catch (error) { + done(error); + } + }, + ); + }, 60000); +}); From 64d5ce7fa449476c6823a213957f2990cd479e09 Mon Sep 17 00:00:00 2001 From: sheo13666q Date: Sun, 18 Oct 2026 23:32:10 +0000 Subject: [PATCH 2/2] fix: minify from a webpack bundle with Node.js's own require Inside a webpack bundle, module is webpack's module object, which has no require, so minify reported that webpack was missing. Use the require webpack keeps as __non_webpack_require__ there, and module.require outside a bundle. The bundled test now also minifies a check from the bundle. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QYPkbvwyRWt2SHgXDoYRdo --- .changeset/precompile-bundle.md | 2 +- src/precompile.js | 15 +++++++++++---- test/fixtures/bundled-precompile.js | 11 +++++++++++ test/precompile.test.js | 25 +++++++++++++++++++------ 4 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 test/fixtures/bundled-precompile.js diff --git a/.changeset/precompile-bundle.md b/.changeset/precompile-bundle.md index d509ec7..a8fa678 100644 --- a/.changeset/precompile-bundle.md +++ b/.changeset/precompile-bundle.md @@ -2,4 +2,4 @@ "schema-utils": patch --- -Bundle `schema-utils` without errors or warnings: `precompile` requires the `ajv` helpers it inlines by name and loads webpack's minifier without a request a bundler parses. +Bundle `schema-utils` without errors or warnings: `precompile` requires the `ajv` helpers it inlines by name and loads webpack's minifier with Node.js's own `require`, so `minify` works from a webpack bundle too. diff --git a/src/precompile.js b/src/precompile.js index ac09479..843c970 100644 --- a/src/precompile.js +++ b/src/precompile.js @@ -269,13 +269,20 @@ async function minify(code, esm) { let minifier; // webpack's own minifier, which writes what terser writes without loading it - the packages - // shipping a check already have webpack. Required through an alias of `module` so a bundler - // that bundles `schema-utils` neither bundles webpack nor warns about a request it cannot - // resolve, while Node.js resolves it like any `require` here. + // shipping a check already have webpack. A bundler that bundles `schema-utils` must neither + // bundle webpack nor warn about a request it cannot resolve, so webpack is loaded with Node.js's + // own `require`: the one webpack keeps as `__non_webpack_require__` inside a webpack bundle, and + // `module.require` through an alias of `module` outside one. + /* global __non_webpack_require__ */ + // prettier-ignore + // @ts-expect-error a free variable only a webpack bundle defines + const nodeRequire = typeof __non_webpack_require__ === "function" ? __non_webpack_require__ : undefined; const self = module; try { - minifier = await self.require("webpack").javascript.syntax.printer.load(); + minifier = await (nodeRequire || self.require.bind(self))( + "webpack", + ).javascript.syntax.printer.load(); } catch (err) { throw new Error( "Install webpack 5.112.0 or later to minify a precompiled check", diff --git a/test/fixtures/bundled-precompile.js b/test/fixtures/bundled-precompile.js new file mode 100644 index 0000000..4df00e8 --- /dev/null +++ b/test/fixtures/bundled-precompile.js @@ -0,0 +1,11 @@ +const { precompile } = require("../../src"); + +precompile({ type: "string", minLength: 1 }, { minify: true }).then( + (code) => { + const check = { exports: {} }; + + // eslint-disable-next-line no-new-func + new Function("module", code)(check); + process.stdout.write(JSON.stringify([check.exports(""), check.exports("a")])); + }, +); diff --git a/test/precompile.test.js b/test/precompile.test.js index 9c71cbd..25a1509 100644 --- a/test/precompile.test.js +++ b/test/precompile.test.js @@ -602,23 +602,27 @@ describe("precompile", () => { describe("bundled", () => { // `schema-utils` is bundled by tools that ship it inside their own code, which reaches // `precompile` through the `precompile` export even though it only runs at build time - it("should bundle without errors or warnings", (done) => { + it("should bundle without errors or warnings and minify from the bundle", (done) => { const webpack = require("webpack"); - const outputPath = fs.mkdtempSync(path.join(os.tmpdir(), "schema-utils-")); + // Inside the package, so the bundle finds webpack the way any installed bundle does + const localPath = path.resolve(__dirname, "../local"); + + fs.mkdirSync(localPath, { recursive: true }); + + const outputPath = fs.mkdtempSync(path.join(localPath, "bundle-")); webpack( { mode: "none", target: "node", - context: path.resolve(__dirname, ".."), - entry: "./src/index.js", + context: __dirname, + entry: "./fixtures/bundled-precompile.js", output: { path: outputPath }, }, (err, stats) => { - remove(outputPath); - if (err) { + remove(outputPath); done(err); return; @@ -633,9 +637,18 @@ describe("bundled", () => { expect(errors.map(({ message }) => message)).toEqual([]); expect(warnings.map(({ message }) => message)).toEqual([]); + expect( + JSON.parse( + execFileSync(process.execPath, [ + path.join(outputPath, "main.js"), + ]).toString(), + ), + ).toEqual([false, true]); done(); } catch (error) { done(error); + } finally { + remove(outputPath); } }, );