diff --git a/declarations/plugins/SourceMapDevToolPlugin.d.ts b/declarations/plugins/SourceMapDevToolPlugin.d.ts index c65fec728..cdea8d468 100644 --- a/declarations/plugins/SourceMapDevToolPlugin.d.ts +++ b/declarations/plugins/SourceMapDevToolPlugin.d.ts @@ -5,11 +5,11 @@ */ /** - * Include source maps for modules based on their extension (defaults to .js and .css). + * One or multiple conditions used to match resource. */ export type Rules = Rule[] | Rule; /** - * Include source maps for modules based on their extension (defaults to .js and .css). + * Condition used to match resource (string, RegExp or Function). */ export type Rule = RegExp | string | ((str: string) => boolean); @@ -47,6 +47,10 @@ export interface SourceMapDevToolPluginOptions { * Defines the output filename of the SourceMap (will be inlined if no value is provided). */ filename?: (false | null) | string; + /** + * Decide whether to ignore source files that match the specified value in the SourceMap. + */ + ignoreList?: Rules; /** * Include source maps for module paths that match the given value. */ diff --git a/lib/EvalSourceMapDevToolPlugin.js b/lib/EvalSourceMapDevToolPlugin.js index 93ff5e805..de7f2115a 100644 --- a/lib/EvalSourceMapDevToolPlugin.js +++ b/lib/EvalSourceMapDevToolPlugin.js @@ -18,6 +18,7 @@ const { makePathsAbsolute } = require("./util/identifier"); /** @typedef {import("webpack-sources").RawSourceMap} RawSourceMap */ /** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("../declarations/plugins/SourceMapDevToolPlugin").SourceMapDevToolPluginOptions} SourceMapDevToolPluginOptions */ +/** @typedef {import("../declarations/plugins/SourceMapDevToolPlugin").Rules} Rules */ /** @typedef {import("./ChunkGraph").ModuleId} ModuleId */ /** @typedef {import("./Compiler")} Compiler */ @@ -163,6 +164,21 @@ class EvalSourceMapDevToolPlugin { } ); sourceMap.sources = moduleFilenames; + sourceMap.ignoreList = options.ignoreList + ? sourceMap.sources.reduce( + /** @type {(acc: number[], sourceName: string, idx: number) => number[]} */ ( + (acc, sourceName, idx) => { + const rule = /** @type {Rules} */ (options.ignoreList); + if (ModuleFilenameHelpers.matchPart(sourceName, rule)) { + acc.push(idx); + } + return acc; + } + ), + [] + ) + : []; + if (options.noSources) { sourceMap.sourcesContent = undefined; } diff --git a/lib/SourceMapDevToolPlugin.js b/lib/SourceMapDevToolPlugin.js index 1b4d8ff46..dfcccc5fe 100644 --- a/lib/SourceMapDevToolPlugin.js +++ b/lib/SourceMapDevToolPlugin.js @@ -20,6 +20,7 @@ const { makePathsAbsolute } = require("./util/identifier"); /** @typedef {import("webpack-sources").MapOptions} MapOptions */ /** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("../declarations/plugins/SourceMapDevToolPlugin").SourceMapDevToolPluginOptions} SourceMapDevToolPluginOptions */ +/** @typedef {import("../declarations/plugins/SourceMapDevToolPlugin").Rules} Rules */ /** @typedef {import("./CacheFacade").ItemCacheFacade} ItemCacheFacade */ /** @typedef {import("./Chunk")} Chunk */ /** @typedef {import("./Compilation").Asset} Asset */ @@ -433,6 +434,25 @@ class SourceMapDevToolPlugin { moduleToSourceNameMapping.get(m) ); sourceMap.sources = /** @type {string[]} */ (moduleFilenames); + sourceMap.ignoreList = options.ignoreList + ? sourceMap.sources.reduce( + /** @type {(acc: number[], sourceName: string, idx: number) => number[]} */ ( + (acc, sourceName, idx) => { + const rule = /** @type {Rules} */ ( + options.ignoreList + ); + if ( + ModuleFilenameHelpers.matchPart(sourceName, rule) + ) { + acc.push(idx); + } + return acc; + } + ), + [] + ) + : []; + if (options.noSources) { sourceMap.sourcesContent = undefined; } diff --git a/schemas/plugins/SourceMapDevToolPlugin.check.js b/schemas/plugins/SourceMapDevToolPlugin.check.js index c3bfb1d51..c6bfc18f4 100644 --- a/schemas/plugins/SourceMapDevToolPlugin.check.js +++ b/schemas/plugins/SourceMapDevToolPlugin.check.js @@ -3,4 +3,4 @@ * DO NOT MODIFY BY HAND. * Run `yarn fix:special` to update */ -const e=/^(?:[A-Za-z]:[\\/]|\\\\|\/)/;module.exports=l,module.exports.default=l;const n={definitions:{rule:{anyOf:[{instanceof:"RegExp"},{type:"string",minLength:1},{instanceof:"Function"}]},rules:{anyOf:[{type:"array",items:{oneOf:[{$ref:"#/definitions/rule"}]}},{$ref:"#/definitions/rule"}]}},type:"object",additionalProperties:!1,properties:{append:{anyOf:[{enum:[!1,null]},{type:"string",minLength:1},{instanceof:"Function"}]},columns:{type:"boolean"},debugIds:{type:"boolean"},exclude:{oneOf:[{$ref:"#/definitions/rules"}]},fallbackModuleFilenameTemplate:{anyOf:[{type:"string",minLength:1},{instanceof:"Function"}]},fileContext:{type:"string"},filename:{anyOf:[{enum:[!1,null]},{type:"string",absolutePath:!1,minLength:1}]},include:{oneOf:[{$ref:"#/definitions/rules"}]},module:{type:"boolean"},moduleFilenameTemplate:{anyOf:[{type:"string",minLength:1},{instanceof:"Function"}]},namespace:{type:"string"},noSources:{type:"boolean"},publicPath:{type:"string"},sourceRoot:{type:"string"},test:{$ref:"#/definitions/rules"}}},t=Object.prototype.hasOwnProperty;function s(e,{instancePath:n="",parentData:t,parentDataProperty:l,rootData:r=e}={}){let o=null,a=0;const i=a;let u=!1;const p=a;if(a===p)if(Array.isArray(e)){const n=e.length;for(let t=0;t { + const source = fs.readFileSync(__filename, "utf-8"); + const match = + /\/\/# sourceMappingURL\s*=\s*data:application\/json;charset=utf-8;base64,(.*)\\n\/\/#/.exec( + source + ); + const mapString = Buffer.from(match[1], "base64").toString("utf-8"); + return JSON.parse(mapString); +}; + +const map = getSourceMap(); + +it("marks matching modules in ignoreList", () => { + const sources = map.sources; + const ignoredIndex = sources.findIndex((source) => + /ignored\.js/.test(source) + ); + expect(ignored).toBe("ignored"); + expect(ignoredIndex).not.toBe(-1); + expect(Array.isArray(map.ignoreList)).toBe(true); + expect(map.ignoreList).toContain(ignoredIndex); +}); + +it("keeps other modules outside ignoreList", () => { + const sources = map.sources; + const usedIndex = sources.findIndex((source) => /used\.js/.test(source)); + expect(used).toBe("used"); + expect(usedIndex).not.toBe(-1); + expect(map.ignoreList).not.toContain(usedIndex); +}); diff --git a/test/configCases/source-map/eval-source-map-ignore-list/used.js b/test/configCases/source-map/eval-source-map-ignore-list/used.js new file mode 100644 index 000000000..ee64d9695 --- /dev/null +++ b/test/configCases/source-map/eval-source-map-ignore-list/used.js @@ -0,0 +1 @@ +export default "used"; diff --git a/test/configCases/source-map/eval-source-map-ignore-list/webpack.config.js b/test/configCases/source-map/eval-source-map-ignore-list/webpack.config.js new file mode 100644 index 000000000..531720169 --- /dev/null +++ b/test/configCases/source-map/eval-source-map-ignore-list/webpack.config.js @@ -0,0 +1,17 @@ +"use strict"; + +const webpack = require("../../../../"); + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + plugins: [ + new webpack.EvalSourceMapDevToolPlugin({ + ignoreList: [/ignored\.js/] + }) + ], + optimization: { + // Ensure the correct `sourceMappingURL` is detected + concatenateModules: true + } +}; diff --git a/test/configCases/source-map/source-map-ignore-list/ignored.js b/test/configCases/source-map/source-map-ignore-list/ignored.js new file mode 100644 index 000000000..e86a68597 --- /dev/null +++ b/test/configCases/source-map/source-map-ignore-list/ignored.js @@ -0,0 +1 @@ +export default "ignored"; diff --git a/test/configCases/source-map/source-map-ignore-list/index.js b/test/configCases/source-map/source-map-ignore-list/index.js new file mode 100644 index 000000000..8ed4f6017 --- /dev/null +++ b/test/configCases/source-map/source-map-ignore-list/index.js @@ -0,0 +1,33 @@ +import used from "./used"; +import ignored from "./ignored"; +import fs from "fs"; +import path from "path"; + +const getSourceMap = () => { + const content = fs.readFileSync( + path.join(__dirname, "bundle0.js.map"), + "utf-8" + ); + return JSON.parse(content); +}; + +const map = getSourceMap(); + +it("marks matching modules in ignoreList", () => { + const sources = map.sources; + const ignoredIndex = sources.findIndex((source) => + /ignored\.js/.test(source) + ); + expect(ignored).toBe("ignored"); + expect(ignoredIndex).not.toBe(-1); + expect(Array.isArray(map.ignoreList)).toBe(true); + expect(map.ignoreList).toContain(ignoredIndex); +}); + +it("keeps other modules outside ignoreList", () => { + const sources = map.sources; + const usedIndex = sources.findIndex((source) => /used\.js/.test(source)); + expect(used).toBe("used"); + expect(usedIndex).not.toBe(-1); + expect(map.ignoreList).not.toContain(usedIndex); +}); diff --git a/test/configCases/source-map/source-map-ignore-list/used.js b/test/configCases/source-map/source-map-ignore-list/used.js new file mode 100644 index 000000000..ee64d9695 --- /dev/null +++ b/test/configCases/source-map/source-map-ignore-list/used.js @@ -0,0 +1 @@ +export default "used"; diff --git a/test/configCases/source-map/source-map-ignore-list/webpack.config.js b/test/configCases/source-map/source-map-ignore-list/webpack.config.js new file mode 100644 index 000000000..3b13a7ea7 --- /dev/null +++ b/test/configCases/source-map/source-map-ignore-list/webpack.config.js @@ -0,0 +1,15 @@ +"use strict"; + +const webpack = require("../../../../"); + +/** @type {import("../../../../").Configuration} */ +module.exports = { + mode: "development", + devtool: false, + plugins: [ + new webpack.SourceMapDevToolPlugin({ + filename: "[file].map", + ignoreList: [/ignored\.js/] + }) + ] +}; diff --git a/types.d.ts b/types.d.ts index e25fb870c..aaf3d15d9 100644 --- a/types.d.ts +++ b/types.d.ts @@ -16833,6 +16833,11 @@ declare interface SourceMapDevToolPluginOptions { */ filename?: null | string | false; + /** + * Decide whether to ignore source files that match the specified value in the SourceMap. + */ + ignoreList?: string | RegExp | Rule[] | ((str: string) => boolean); + /** * Include source maps for module paths that match the given value. */