feat: added ability to generate custom error content for generators (#19385)
Github Actions / lint (push) Waiting to run Details
Github Actions / basic (push) Waiting to run Details
Github Actions / validate-legacy-node (push) Waiting to run Details
Github Actions / unit (push) Waiting to run Details
Github Actions / integration (10.x, macos-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (10.x, macos-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (10.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (10.x, ubuntu-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (10.x, windows-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (10.x, windows-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (12.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (14.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (16.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (18.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (18.x, ubuntu-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (20.x, macos-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (20.x, macos-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (20.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (20.x, ubuntu-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (20.x, windows-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (20.x, windows-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (22.x, macos-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (22.x, macos-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (22.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (22.x, ubuntu-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (22.x, windows-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (22.x, windows-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (23.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (23.x, ubuntu-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (lts/*, ubuntu-latest, a, 1) (push) Blocked by required conditions Details
Github Actions / integration (lts/*, ubuntu-latest, b, 1) (push) Blocked by required conditions Details

This commit is contained in:
Alexander Akait 2025-04-05 17:37:35 +03:00 committed by GitHub
parent 880cf0804d
commit e5c3f95b84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 526 additions and 4 deletions

View File

@ -35,6 +35,14 @@
* @property {() => Map<string, TODO>=} getData get access to the code generation data
*/
/**
* @callback GenerateErrorFn
* @param {Error} error the error
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
/**
* @typedef {object} UpdateHashContext
* @property {NormalModule} module the module
@ -108,6 +116,24 @@ class Generator {
}
}
/**
* @this {ByTypeGenerator}
* @type {GenerateErrorFn}
*/
function generateError(error, module, generateContext) {
const type = generateContext.type;
const generator =
/** @type {Generator & { generateError?: GenerateErrorFn }} */
(this.map[type]);
if (!generator) {
throw new Error(`Generator.byType: no generator specified for ${type}`);
}
if (typeof generator.generateError === "undefined") {
return null;
}
return generator.generateError(error, module, generateContext);
}
class ByTypeGenerator extends Generator {
/**
* @param {Record<string, Generator>} map map of types
@ -116,6 +142,8 @@ class ByTypeGenerator extends Generator {
super();
this.map = map;
this._types = new Set(Object.keys(map));
/** @type {GenerateErrorFn | undefined} */
this.generateError = generateError.bind(this);
}
/**

View File

@ -205,6 +205,7 @@ class Module extends DependenciesBlock {
this.useSimpleSourceMap = false;
// Is in hot context, i.e. HotModuleReplacementPlugin.js enabled
// TODO do we need hot here?
/** @type {boolean} */
this.hot = false;
// Info from Build

View File

@ -62,6 +62,7 @@ const memoize = require("./util/memoize");
/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */
/** @typedef {import("./DependencyTemplates")} DependencyTemplates */
/** @typedef {import("./Generator")} Generator */
/** @typedef {import("./Generator").GenerateErrorFn} GenerateErrorFn */
/** @typedef {import("./Module").BuildInfo} BuildInfo */
/** @typedef {import("./Module").BuildMeta} BuildMeta */
/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */
@ -1450,11 +1451,28 @@ class NormalModule extends Module {
const sources = new Map();
for (const type of sourceTypes || chunkGraph.getModuleSourceTypes(this)) {
// TODO webpack@6 make generateError required
const generator =
/** @type {Generator & { generateError?: GenerateErrorFn }} */
(this.generator);
const source = this.error
? new RawSource(
? generator.generateError
? generator.generateError(this.error, this, {
dependencyTemplates,
runtimeTemplate,
moduleGraph,
chunkGraph,
runtimeRequirements,
runtime,
concatenationScope,
codeGenerationResults,
getData,
type
})
: new RawSource(
`throw new Error(${JSON.stringify(this.error.message)});`
)
: /** @type {Generator} */ (this.generator).generate(this, {
: generator.generate(this, {
dependencyTemplates,
runtimeTemplate,
moduleGraph,

View File

@ -244,6 +244,10 @@ class AssetGenerator extends Generator {
hash.update(source.buffer());
}
if (module.error) {
hash.update(module.error.toString());
}
const fullContentHash = /** @type {string} */ (
hash.digest(runtimeTemplate.outputOptions.hashDigest)
);
@ -623,6 +627,27 @@ class AssetGenerator extends Generator {
return /** @type {Source} */ (module.originalSource());
}
/**
* @param {Error} error the error
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generateError(error, module, generateContext) {
switch (generateContext.type) {
case "asset": {
return new RawSource(error.message);
}
case "javascript": {
return new RawSource(
`throw new Error(${JSON.stringify(error.message)});`
);
}
default:
return null;
}
}
/**
* @param {NormalModule} module fresh module
* @returns {SourceTypes} available types (do not mutate)
@ -708,6 +733,7 @@ class AssetGenerator extends Generator {
*/
updateHash(hash, updateHashContext) {
const { module } = updateHashContext;
if (
/** @type {BuildInfo} */
(module.buildInfo).dataUrl

View File

@ -90,6 +90,24 @@ class AssetSourceGenerator extends Generator {
}
}
/**
* @param {Error} error the error
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generateError(error, module, generateContext) {
switch (generateContext.type) {
case "javascript": {
return new RawSource(
`throw new Error(${JSON.stringify(error.message)});`
);
}
default:
return null;
}
}
/**
* @param {NormalModule} module module for which the bailout reason should be determined
* @param {ConcatenationBailoutReasonContext} context context

View File

@ -210,6 +210,27 @@ class CssGenerator extends Generator {
}
}
/**
* @param {Error} error the error
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generateError(error, module, generateContext) {
switch (generateContext.type) {
case "javascript": {
return new RawSource(
`throw new Error(${JSON.stringify(error.message)});`
);
}
case "css": {
return new RawSource(`/**\n ${error.message} \n**/`);
}
default:
return null;
}
}
/**
* @param {NormalModule} module fresh module
* @returns {SourceTypes} available types (do not mutate)

View File

@ -110,6 +110,16 @@ class JavascriptGenerator extends Generator {
return InitFragment.addToSource(source, initFragments, generateContext);
}
/**
* @param {Error} error the error
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generateError(error, module, generateContext) {
return new RawSource(`throw new Error(${JSON.stringify(error.message)});`);
}
/**
* @param {Module} module the module to generate
* @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments

View File

@ -216,6 +216,16 @@ class JsonGenerator extends Generator {
}
return new RawSource(content);
}
/**
* @param {Error} error the error
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generateError(error, module, generateContext) {
return new RawSource(`throw new Error(${JSON.stringify(error.message)});`);
}
}
module.exports = JsonGenerator;

View File

@ -5,6 +5,7 @@
"use strict";
const { RawSource } = require("webpack-sources");
const Generator = require("../Generator");
const { WEBASSEMBLY_TYPES } = require("../ModuleSourceTypesConstants");
@ -56,6 +57,16 @@ class AsyncWebAssemblyGenerator extends Generator {
generate(module, generateContext) {
return /** @type {Source} */ (module.originalSource());
}
/**
* @param {Error} error the error
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generateError(error, module, generateContext) {
return new RawSource(error.message);
}
}
module.exports = AsyncWebAssemblyGenerator;

View File

@ -201,6 +201,16 @@ class AsyncWebAssemblyJavascriptGenerator extends Generator {
return InitFragment.addToSource(source, initFragments, generateContext);
}
/**
* @param {Error} error the error
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generateError(error, module, generateContext) {
return new RawSource(`throw new Error(${JSON.stringify(error.message)});`);
}
}
module.exports = AsyncWebAssemblyJavascriptGenerator;

View File

@ -521,6 +521,16 @@ class WebAssemblyGenerator extends Generator {
return new RawSource(newBuf);
}
/**
* @param {Error} error the error
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generateError(error, module, generateContext) {
return new RawSource(error.message);
}
}
module.exports = WebAssemblyGenerator;

View File

@ -212,6 +212,16 @@ class WebAssemblyJavascriptGenerator extends Generator {
);
return InitFragment.addToSource(source, initFragments, generateContext);
}
/**
* @param {Error} error the error
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generateError(error, module, generateContext) {
return new RawSource(`throw new Error(${JSON.stringify(error.message)});`);
}
}
module.exports = WebAssemblyJavascriptGenerator;

View File

@ -0,0 +1,10 @@
(module
(type $t0 (func (param i32 i32) (result i32)))
(type $t1 (func (result i32)))
(func $add (export "add") (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
(i32.add
(get_local $p0)
(get_local $p1)))
(func $getNumber (export "getNumber") (type $t1) (result i32)
(i32.const 40)))

View File

@ -0,0 +1,14 @@
module.exports = [
/javascript\/auto error message/,
/asset\/inline error message/,
/asset\/resource error message/,
/asset\/resource other error message/,
/asset\/resource in css error message/,
/asset\/source error message/,
/asset\/source in css error message/,
/css\/auto error message/,
/css error message/,
/json error message/,
/json other error message/,
/webassembly\/async error message/
];

View File

@ -0,0 +1,3 @@
{
"test": "test"
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600"><title>icon-square-small</title><path fill="#FFF" d="M300 .1L565 150v299.9L300 599.8 35 449.9V150z"/><path fill="#8ED6FB" d="M517.7 439.5L308.8 557.8v-92L439 394.1l78.7 45.4zm14.3-12.9V179.4l-76.4 44.1v159l76.4 44.1zM81.5 439.5l208.9 118.2v-92l-130.2-71.6-78.7 45.4zm-14.3-12.9V179.4l76.4 44.1v159l-76.4 44.1zm8.9-263.2L290.4 42.2v89l-137.3 75.5-1.1.6-75.9-43.9zm446.9 0L308.8 42.2v89L446 206.8l1.1.6 75.9-44z"/><path fill="#1C78C0" d="M290.4 444.8L162 374.1V234.2l128.4 74.1v136.5zm18.4 0l128.4-70.6v-140l-128.4 74.1v136.5zM299.6 303zm-129-85l129-70.9L428.5 218l-128.9 74.4-129-74.4z"/></svg>

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,118 @@
it("should generate a custom error content", async () => {
expect(__STATS__.modules.filter(m => m.moduleType !== "runtime").length).toEqual(14);
expect(__STATS__.assets.length).toEqual(19);
expect(__STATS__.chunks.length).toEqual(12);
let errored;
let json;
try {
json = await import("./file.json");
} catch (error) {
errored = error;
}
expect(errored.toString()).toMatch(/json error message/);
let otherJson;
try {
otherJson = await import("./other.json");
} catch (error) {
errored = error;
}
expect(errored.toString()).toMatch(/json other error message/);
let source;
try {
source = await import("./source.txt");
} catch (error) {
errored = error;
}
expect(errored.toString()).toMatch(/asset\/source error message/);
let resource;
try {
resource = await import("./file.svg");
} catch (error) {
errored = error;
}
expect(errored.toString()).toMatch(/asset\/resource error message/);
let otherResource;
try {
otherResource = await import("./other.svg");
} catch (error) {
errored = error;
}
expect(errored.toString()).toMatch(/asset\/resource other error message/);
let inline;
try {
inline = await import("./inline.txt");
} catch (error) {
errored = error;
}
expect(errored.toString()).toMatch(/asset\/inline error message/);
let style;
try {
style = await import("./style.css");
} catch (error) {
errored = error;
}
expect(errored.toString()).toMatch(/css error message/);
let js;
try {
js = await import("./module.js");
} catch (error) {
errored = error;
}
expect(errored.toString()).toMatch(/javascript\/auto error message/);
let otherStyle;
errored = undefined;
try {
otherStyle = await import("./style-other.css");
} catch (error) {
errored = error;
}
expect(errored).toBeUndefined();
let cssModules;
try {
cssModules = await import("./style.modules.css");
} catch (error) {
errored = error;
}
expect(errored.toString()).toMatch(/css\/auto error message/);
let asyncWasm;
try {
asyncWasm = await import("./async-wasm.wat");
} catch (error) {
errored = error;
}
expect(errored.toString()).toMatch(/webassembly\/async error message/);
});

View File

@ -0,0 +1,7 @@
module.exports = options => {
if (options.cache && options.cache.type === "filesystem") {
return [/Pack got invalid because of write to/];
}
return [];
};

View File

@ -0,0 +1 @@
inline

View File

@ -0,0 +1,7 @@
/** @type {import("../../../../").LoaderDefinition<{ message: string }>} */
module.exports = function() {
const callback = this.async();
const options = this.getOptions();
callback(new Error(options.message || 'Message'));
};

View File

@ -0,0 +1 @@
export default "test";

View File

@ -0,0 +1,3 @@
{
"test": "test"
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600"><title>icon-square-small</title><path fill="#FFF" d="M300 .1L565 150v299.9L300 599.8 35 449.9V150z"/><path fill="#8ED6FB" d="M517.7 439.5L308.8 557.8v-92L439 394.1l78.7 45.4zm14.3-12.9V179.4l-76.4 44.1v159l76.4 44.1zM81.5 439.5l208.9 118.2v-92l-130.2-71.6-78.7 45.4zm-14.3-12.9V179.4l76.4 44.1v159l-76.4 44.1zm8.9-263.2L290.4 42.2v89l-137.3 75.5-1.1.6-75.9-43.9zm446.9 0L308.8 42.2v89L446 206.8l1.1.6 75.9-44z"/><path fill="#1C78C0" d="M290.4 444.8L162 374.1V234.2l128.4 74.1v136.5zm18.4 0l128.4-70.6v-140l-128.4 74.1v136.5zM299.6 303zm-129-85l129-70.9L428.5 218l-128.9 74.4-129-74.4z"/></svg>

After

Width:  |  Height:  |  Size: 656 B

View File

@ -0,0 +1 @@
test

View File

@ -0,0 +1,4 @@
div {
background: url("./in-style.png");
background: url("./in-style-source.png");
}

View File

@ -0,0 +1,3 @@
a {
color: red;
}

View File

@ -0,0 +1,3 @@
.class {
color: red;
}

View File

@ -0,0 +1,10 @@
(module
(type $t0 (func (param i32 i32) (result i32)))
(type $t1 (func (result i32)))
(func $add (export "add") (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
(i32.add
(get_local $p0)
(get_local $p1)))
(func $getNumber (export "getNumber") (type $t1) (result i32)
(i32.const 40)))

View File

@ -0,0 +1,9 @@
const findOutputFiles = require("../../../helpers/findOutputFiles");
module.exports = {
findBundle: function (i, options) {
const files = findOutputFiles(options, new RegExp(/\.js$/));
return files.sort((a, b) => (a.startsWith("main") ? 1 : 0));
}
};

View File

@ -0,0 +1,5 @@
const supportsWebAssembly = require("../../../helpers/supportsWebAssembly");
module.exports = function (config) {
return supportsWebAssembly();
};

View File

@ -0,0 +1,143 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
target: "web",
output: {
filename: "[name].[chunkhash:8].[contenthash:8].js",
chunkFilename: "[name].[chunkhash:8].[contenthash:8].js"
},
optimization: {
chunkIds: "named",
emitOnErrors: true
},
experiments: {
css: true,
asyncWebAssembly: true
},
module: {
rules: [
{
type: "asset/source",
test: /source\.txt$/,
use: {
loader: "./loader.js",
options: {
message: "asset/source error message"
}
}
},
{
type: "asset/resource",
test: /file\.svg$/,
use: {
loader: "./loader.js",
options: {
message: "asset/resource error message"
}
}
},
{
type: "asset/resource",
test: /other\.svg$/,
use: {
loader: "./loader.js",
options: {
message: "asset/resource other error message"
}
}
},
{
type: "asset/inline",
test: /inline\.txt$/,
use: {
loader: "./loader.js",
options: {
message: "asset/inline error message"
}
}
},
{
type: "css",
test: /style\.css$/,
use: {
loader: "./loader.js",
options: {
message: "css error message"
}
}
},
{
type: "asset/resource",
test: /in-style\.png$/,
use: {
loader: "./loader.js",
options: {
message: "asset/resource in css error message"
}
}
},
{
type: "asset/source",
test: /in-style-source\.png$/,
use: {
loader: "./loader.js",
options: {
message: "asset/source in css error message"
}
}
},
{
type: "javascript/auto",
test: /module\.js$/,
use: {
loader: "./loader.js",
options: {
message: "javascript/auto error message"
}
}
},
{
type: "json",
test: /file\.json$/,
use: {
loader: "./loader.js",
options: {
message: "json error message"
}
}
},
{
type: "json",
test: /other\.json$/,
use: {
loader: "./loader.js",
options: {
message: "json other error message"
}
}
},
{
type: "css/auto",
generator: {
exportsOnly: true
},
test: /style\.modules\.css$/,
use: {
loader: "./loader.js",
options: {
message: "css/auto error message"
}
}
},
{
type: "webassembly/async",
test: /async-wasm\.wat$/,
use: {
loader: "./loader.js",
options: {
message: "webassembly/async error message"
}
}
}
]
}
};

5
types.d.ts vendored
View File

@ -888,6 +888,11 @@ type BuildInfo = KnownBuildInfo & Record<string, any>;
type BuildMeta = KnownBuildMeta & Record<string, any>;
declare abstract class ByTypeGenerator extends Generator {
map: Record<string, Generator>;
generateError?: (
error: Error,
module: NormalModule,
generateContext: GenerateContext
) => null | Source;
}
declare const CIRCULAR_CONNECTION: unique symbol;
declare class Cache {