diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 11a4dbe2a81..9dfebc27c2a 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -27,7 +27,7 @@ jobs:
node-version: 17.x
cache: "yarn"
- run: yarn --frozen-lockfile
- - uses: actions/cache@v1
+ - uses: actions/cache@v3
with:
path: .eslintcache
key: lint-${{ env.GITHUB_SHA }}
@@ -62,7 +62,7 @@ jobs:
- run: yarn --frozen-lockfile
- run: yarn link --frozen-lockfile || true
- run: yarn link webpack --frozen-lockfile
- - uses: actions/cache@v1
+ - uses: actions/cache@v3
with:
path: .jest-cache
key: jest-unit-${{ env.GITHUB_SHA }}
@@ -101,7 +101,7 @@ jobs:
- run: yarn --frozen-lockfile
- run: yarn link --frozen-lockfile || true
- run: yarn link webpack --frozen-lockfile
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
with:
path: .jest-cache
key: jest-integration-${{ env.GITHUB_SHA }}
diff --git a/README.md b/README.md
index c712d27fd7a..a6549c1c462 100644
--- a/README.md
+++ b/README.md
@@ -158,11 +158,11 @@ or are automatically applied via regex from your webpack configuration.
#### Transpiling
-| Name | Status | Install Size | Description |
-| :--------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------: | :------------: | :------------------------------------------------------------------------------------------------ |
-|
| ![babel-npm] | ![babel-size] | Loads ES2015+ code and transpiles to ES5 using Babel |
-|
| ![type-npm] | ![type-size] | Loads TypeScript like JavaScript |
-|
| ![coffee-npm] | ![coffee-size] | Loads CoffeeScript like JavaScript |
+| Name | Status | Install Size | Description |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------: | :------------: | :------------------------------------------------------------------------------------------------ |
+|
| ![babel-npm] | ![babel-size] | Loads ES2015+ code and transpiles to ES5 using Babel |
+|
| ![type-npm] | ![type-size] | Loads TypeScript like JavaScript |
+|
| ![coffee-npm] | ![coffee-size] | Loads CoffeeScript like JavaScript |
[babel-npm]: https://img.shields.io/npm/v/babel-loader.svg
[babel-size]: https://packagephobia.com/badge?p=babel-loader
@@ -175,7 +175,7 @@ or are automatically applied via regex from your webpack configuration.
| Name | Status | Install Size | Description |
| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------: | :--------------: | :-------------------------------------------------------------------------------------- |
-|
| ![html-npm] | ![html-size] | Exports HTML as string, requires references to static resources |
+|
| ![html-npm] | ![html-size] | Exports HTML as string, requires references to static resources |
|
| ![pug-npm] | ![pug-size] | Loads Pug templates and returns a function |
|
| ![pug3-npm] | ![pug3-size] | Compiles Pug to a function or HTML string, useful for use with Vue, React, Angular |
|
| ![md-npm] | ![md-size] | Compiles Markdown to HTML |
diff --git a/declarations/LoaderContext.d.ts b/declarations/LoaderContext.d.ts
index 3e9341423a7..f93a0890d2d 100644
--- a/declarations/LoaderContext.d.ts
+++ b/declarations/LoaderContext.d.ts
@@ -212,6 +212,12 @@ export interface LoaderRunnerLoaderContext {
* Example: "/abc/resource.js?query#frag"
*/
resource: string;
+
+ /**
+ * Target of compilation.
+ * Example: "web"
+ */
+ target: string;
}
type AdditionalData = {
diff --git a/lib/NormalModule.js b/lib/NormalModule.js
index 4d1264f9b3c..b3fababd63f 100644
--- a/lib/NormalModule.js
+++ b/lib/NormalModule.js
@@ -330,6 +330,8 @@ class NormalModule extends Module {
this._isEvaluatingSideEffects = false;
/** @type {WeakSet | undefined} */
this._addedSideEffectsBailout = undefined;
+ /** @type {Map} */
+ this._codeGeneratorData = new Map();
}
/**
@@ -1188,11 +1190,9 @@ class NormalModule extends Module {
runtimeRequirements.add(RuntimeGlobals.thisAsExports);
}
- /** @type {Map} */
- let data;
+ /** @type {function(): Map} */
const getData = () => {
- if (data === undefined) data = new Map();
- return data;
+ return this._codeGeneratorData;
};
const sources = new Map();
@@ -1223,7 +1223,7 @@ class NormalModule extends Module {
const resultEntry = {
sources,
runtimeRequirements,
- data
+ data: this._codeGeneratorData
};
return resultEntry;
}
@@ -1371,6 +1371,7 @@ class NormalModule extends Module {
write(this.error);
write(this._lastSuccessfulBuildMeta);
write(this._forceBuild);
+ write(this._codeGeneratorData);
super.serialize(context);
}
@@ -1403,6 +1404,7 @@ class NormalModule extends Module {
this.error = read();
this._lastSuccessfulBuildMeta = read();
this._forceBuild = read();
+ this._codeGeneratorData = read();
super.deserialize(context);
}
}
diff --git a/lib/dependencies/ImportParserPlugin.js b/lib/dependencies/ImportParserPlugin.js
index 151ff89adcc..718b0482828 100644
--- a/lib/dependencies/ImportParserPlugin.js
+++ b/lib/dependencies/ImportParserPlugin.js
@@ -137,7 +137,7 @@ class ImportParserPlugin {
if (importOptions.webpackInclude !== undefined) {
if (
!importOptions.webpackInclude ||
- importOptions.webpackInclude.constructor.name !== "RegExp"
+ !(importOptions.webpackInclude instanceof RegExp)
) {
parser.state.module.addWarning(
new UnsupportedFeatureWarning(
@@ -146,13 +146,13 @@ class ImportParserPlugin {
)
);
} else {
- include = new RegExp(importOptions.webpackInclude);
+ include = importOptions.webpackInclude;
}
}
if (importOptions.webpackExclude !== undefined) {
if (
!importOptions.webpackExclude ||
- importOptions.webpackExclude.constructor.name !== "RegExp"
+ !(importOptions.webpackExclude instanceof RegExp)
) {
parser.state.module.addWarning(
new UnsupportedFeatureWarning(
@@ -161,7 +161,7 @@ class ImportParserPlugin {
)
);
} else {
- exclude = new RegExp(importOptions.webpackExclude);
+ exclude = importOptions.webpackExclude;
}
}
if (importOptions.webpackExports !== undefined) {
diff --git a/lib/javascript/JavascriptParser.js b/lib/javascript/JavascriptParser.js
index c10c7b16eaf..58bcc4a64b3 100644
--- a/lib/javascript/JavascriptParser.js
+++ b/lib/javascript/JavascriptParser.js
@@ -3635,17 +3635,27 @@ class JavascriptParser extends Parser {
return EMPTY_COMMENT_OPTIONS;
}
let options = {};
+ /** @type {unknown[]} */
let errors = [];
for (const comment of comments) {
const { value } = comment;
if (value && webpackCommentRegExp.test(value)) {
// try compile only if webpack options comment is present
try {
- const val = vm.runInNewContext(`(function(){return {${value}};})()`);
- Object.assign(options, val);
+ for (let [key, val] of Object.entries(
+ vm.runInNewContext(`(function(){return {${value}};})()`)
+ )) {
+ if (typeof val === "object" && val !== null) {
+ if (val.constructor.name === "RegExp") val = new RegExp(val);
+ else val = JSON.parse(JSON.stringify(val));
+ }
+ options[key] = val;
+ }
} catch (e) {
- e.comment = comment;
- errors.push(e);
+ const newErr = new Error(String(e.message));
+ newErr.stack = String(e.stack);
+ Object.assign(newErr, { comment });
+ errors.push(newErr);
}
}
}
diff --git a/lib/node/NodeTargetPlugin.js b/lib/node/NodeTargetPlugin.js
index 33f785babff..adea6ab7801 100644
--- a/lib/node/NodeTargetPlugin.js
+++ b/lib/node/NodeTargetPlugin.js
@@ -11,6 +11,7 @@ const ExternalsPlugin = require("../ExternalsPlugin");
const builtins = [
"assert",
+ "assert/strict",
"async_hooks",
"buffer",
"child_process",
diff --git a/lib/optimize/RealContentHashPlugin.js b/lib/optimize/RealContentHashPlugin.js
index 39493200c96..9ceb157781d 100644
--- a/lib/optimize/RealContentHashPlugin.js
+++ b/lib/optimize/RealContentHashPlugin.js
@@ -342,7 +342,6 @@ ${referencingAssets
for (const oldHash of hashesInOrder) {
const assets = hashToAssets.get(oldHash);
assets.sort(comparator);
- const hash = createHash(this._hashFunction);
await Promise.all(
assets.map(asset =>
asset.ownHashes.has(oldHash)
@@ -363,6 +362,10 @@ ${referencingAssets
});
let newHash = hooks.updateHash.call(assetsContent, oldHash);
if (!newHash) {
+ const hash = createHash(this._hashFunction);
+ if (compilation.outputOptions.hashSalt) {
+ hash.update(compilation.outputOptions.hashSalt);
+ }
for (const content of assetsContent) {
hash.update(content);
}
diff --git a/package.json b/package.json
index 112ccc561e0..5e3ff54f9ac 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "webpack",
- "version": "5.75.0",
+ "version": "5.76.1",
"author": "Tobias Koppers @sokra",
"description": "Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.",
"license": "MIT",
@@ -76,7 +76,7 @@
"less": "^4.0.0",
"less-loader": "^8.0.0",
"lint-staged": "^11.0.0",
- "loader-utils": "^2.0.0",
+ "loader-utils": "^2.0.3",
"lodash": "^4.17.19",
"lodash-es": "^4.17.15",
"memfs": "^3.2.0",
diff --git a/test/Compiler-filesystem-caching.test.js b/test/Compiler-filesystem-caching.test.js
new file mode 100644
index 00000000000..cad5f679208
--- /dev/null
+++ b/test/Compiler-filesystem-caching.test.js
@@ -0,0 +1,152 @@
+"use strict";
+
+require("./helpers/warmup-webpack");
+
+const path = require("path");
+const fs = require("graceful-fs");
+const rimraf = require("rimraf");
+
+let fixtureCount = 0;
+
+describe("Compiler (filesystem caching)", () => {
+ jest.setTimeout(5000);
+
+ const tempFixturePath = path.join(
+ __dirname,
+ "fixtures",
+ "temp-filesystem-cache-fixture"
+ );
+
+ function compile(entry, onSuccess, onError) {
+ const webpack = require("..");
+ const options = webpack.config.getNormalizedWebpackOptions({});
+ options.cache = {
+ type: "filesystem",
+ cacheDirectory: path.join(tempFixturePath, "cache")
+ };
+ options.entry = entry;
+ options.context = path.join(__dirname, "fixtures");
+ options.output.path = path.join(tempFixturePath, "dist");
+ options.output.filename = "bundle.js";
+ options.output.pathinfo = true;
+ options.module = {
+ rules: [
+ {
+ test: /\.svg$/,
+ type: "asset/resource",
+ use: {
+ loader: require.resolve("./fixtures/empty-svg-loader")
+ }
+ }
+ ]
+ };
+
+ function runCompiler(onSuccess, onError) {
+ const c = webpack(options);
+ c.hooks.compilation.tap(
+ "CompilerCachingTest",
+ compilation => (compilation.bail = true)
+ );
+ c.run((err, stats) => {
+ if (err) throw err;
+ expect(typeof stats).toBe("object");
+ stats = stats.toJson({
+ modules: true,
+ reasons: true
+ });
+ expect(typeof stats).toBe("object");
+ expect(stats).toHaveProperty("errors");
+ expect(Array.isArray(stats.errors)).toBe(true);
+ if (stats.errors.length > 0) {
+ onError(new Error(JSON.stringify(stats.errors, null, 4)));
+ }
+ c.close(() => {
+ onSuccess(stats);
+ });
+ });
+ }
+
+ runCompiler(onSuccess, onError);
+
+ return {
+ runAgain: runCompiler
+ };
+ }
+
+ function cleanup() {
+ rimraf.sync(`${tempFixturePath}*`);
+ }
+
+ beforeAll(cleanup);
+ afterAll(cleanup);
+
+ function createTempFixture() {
+ const fixturePath = `${tempFixturePath}-${fixtureCount}`;
+ const usesAssetFilepath = path.join(fixturePath, "uses-asset.js");
+ const svgFilepath = path.join(fixturePath, "file.svg");
+
+ // Remove previous copy if present
+ rimraf.sync(fixturePath);
+
+ // Copy over file since we"ll be modifying some of them
+ fs.mkdirSync(fixturePath);
+ fs.copyFileSync(
+ path.join(__dirname, "fixtures", "uses-asset.js"),
+ usesAssetFilepath
+ );
+ fs.copyFileSync(path.join(__dirname, "fixtures", "file.svg"), svgFilepath);
+
+ fixtureCount++;
+ return {
+ rootPath: fixturePath,
+ usesAssetFilepath: usesAssetFilepath,
+ svgFilepath: svgFilepath
+ };
+ }
+
+ it("should compile again when cached asset has changed but loader output remains the same", done => {
+ const tempFixture = createTempFixture();
+
+ const onError = error => done(error);
+
+ const helper = compile(
+ tempFixture.usesAssetFilepath,
+ stats => {
+ // Not cached the first time
+ expect(stats.assets[0].name).toBe("bundle.js");
+ expect(stats.assets[0].emitted).toBe(true);
+
+ expect(stats.assets[1].name).toMatch(/\w+\.svg$/);
+ expect(stats.assets[0].emitted).toBe(true);
+
+ helper.runAgain(stats => {
+ // Cached the second run
+ expect(stats.assets[0].name).toBe("bundle.js");
+ expect(stats.assets[0].emitted).toBe(false);
+
+ expect(stats.assets[1].name).toMatch(/\w+\.svg$/);
+ expect(stats.assets[0].emitted).toBe(false);
+
+ const svgContent = fs
+ .readFileSync(tempFixture.svgFilepath)
+ .toString()
+ .replace("icon-square-small", "icon-square-smaller");
+
+ fs.writeFileSync(tempFixture.svgFilepath, svgContent);
+
+ helper.runAgain(stats => {
+ // Still cached after file modification because loader always returns empty
+ expect(stats.assets[0].name).toBe("bundle.js");
+ expect(stats.assets[0].emitted).toBe(false);
+
+ expect(stats.assets[1].name).toMatch(/\w+\.svg$/);
+ expect(stats.assets[0].emitted).toBe(false);
+
+ done();
+ }, onError);
+ }, onError);
+ },
+ onError
+ );
+ });
+});
diff --git a/test/configCases/contenthash/salt/img.jpg b/test/configCases/contenthash/salt/img.jpg
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/test/configCases/contenthash/salt/index.js b/test/configCases/contenthash/salt/index.js
new file mode 100644
index 00000000000..2d2b98703ee
--- /dev/null
+++ b/test/configCases/contenthash/salt/index.js
@@ -0,0 +1,5 @@
+import img from "./img.jpg";
+
+it("should compile", () => {
+ expect(typeof img).toBe("string");
+});
diff --git a/test/configCases/contenthash/salt/test.config.js b/test/configCases/contenthash/salt/test.config.js
new file mode 100644
index 00000000000..530c9147c05
--- /dev/null
+++ b/test/configCases/contenthash/salt/test.config.js
@@ -0,0 +1,24 @@
+const findOutputFiles = require("../../../helpers/findOutputFiles");
+
+const allAssets = new Set();
+const allBundles = new Set();
+
+module.exports = {
+ findBundle: function(i, options) {
+ const bundle = findOutputFiles(options, new RegExp(`^bundle${i}`))[0];
+ allBundles.add(/\.([^.]+)\./.exec(bundle)[1]);
+
+ const assets = findOutputFiles(options, /^img/);
+ for (const asset of assets) {
+ allAssets.add(asset);
+ }
+
+ return `./${bundle}`;
+ },
+ afterExecute: () => {
+ // Since there are exactly 2 unique values of output.hashSalt,
+ // there should be exactly 2 unique output hashes for each file.
+ expect(allBundles.size).toBe(2);
+ expect(allAssets.size).toBe(2);
+ }
+};
diff --git a/test/configCases/contenthash/salt/webpack.config.js b/test/configCases/contenthash/salt/webpack.config.js
new file mode 100644
index 00000000000..1ec1c83b9d9
--- /dev/null
+++ b/test/configCases/contenthash/salt/webpack.config.js
@@ -0,0 +1,48 @@
+/** @type {import("../../../../").Configuration[]} */
+module.exports = [
+ {
+ output: {
+ filename: "bundle0.[contenthash].js",
+ assetModuleFilename: "[name].[contenthash][ext]",
+ hashSalt: "1"
+ },
+ module: {
+ rules: [
+ {
+ test: /\.jpg$/,
+ type: "asset/resource"
+ }
+ ]
+ }
+ },
+ {
+ output: {
+ filename: "bundle1.[contenthash].js",
+ assetModuleFilename: "[name].[contenthash][ext]",
+ hashSalt: "1"
+ },
+ module: {
+ rules: [
+ {
+ test: /\.jpg$/,
+ type: "asset/resource"
+ }
+ ]
+ }
+ },
+ {
+ output: {
+ filename: "bundle2.[contenthash].js",
+ assetModuleFilename: "[name].[contenthash][ext]",
+ hashSalt: "2"
+ },
+ module: {
+ rules: [
+ {
+ test: /\.jpg$/,
+ type: "asset/resource"
+ }
+ ]
+ }
+ }
+];
diff --git a/test/fixtures/empty-svg-loader.js b/test/fixtures/empty-svg-loader.js
new file mode 100644
index 00000000000..0a599e7d5d6
--- /dev/null
+++ b/test/fixtures/empty-svg-loader.js
@@ -0,0 +1 @@
+module.exports = () => "";
diff --git a/test/fixtures/file.svg b/test/fixtures/file.svg
new file mode 100644
index 00000000000..d7b7e40b4f8
--- /dev/null
+++ b/test/fixtures/file.svg
@@ -0,0 +1 @@
+
diff --git a/test/fixtures/uses-asset.js b/test/fixtures/uses-asset.js
new file mode 100644
index 00000000000..b3532c8b7fc
--- /dev/null
+++ b/test/fixtures/uses-asset.js
@@ -0,0 +1 @@
+import SVG from './file.svg';
diff --git a/types.d.ts b/types.d.ts
index 251d0adfd3d..78da415cff2 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -6595,6 +6595,12 @@ declare interface LoaderRunnerLoaderContext {
* Example: "/abc/resource.js?query#frag"
*/
resource: string;
+
+ /**
+ * Target of compilation.
+ * Example: "web"
+ */
+ target: string;
}
declare class LoaderTargetPlugin {
constructor(target: string);
diff --git a/yarn.lock b/yarn.lock
index 951cf8d49d0..14ea915aa37 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4154,10 +4154,10 @@ loader-utils@^1.1.0, loader-utils@^1.4.0:
emojis-list "^3.0.0"
json5 "^1.0.1"
-loader-utils@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
- integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
+loader-utils@^2.0.0, loader-utils@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.3.tgz#d4b15b8504c63d1fc3f2ade52d41bc8459d6ede1"
+ integrity sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"