add warning when invalid dependencies are reported by loaders/plugins

add automatic workaround for invalid dependencies

#12340
#12283
This commit is contained in:
Tobias Koppers 2021-01-07 13:06:44 +01:00
parent 6b2ca40b63
commit 09862aacf8
9 changed files with 167 additions and 3 deletions

View File

@ -0,0 +1,38 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const WebpackError = require("./WebpackError");
/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
/** @typedef {import("./Module")} Module */
module.exports = class InvalidDependenciesModuleWarning extends WebpackError {
/**
* @param {Module} module module tied to dependency
* @param {Iterable<string>} deps invalid dependencies
*/
constructor(module, deps) {
const orderedDeps = Array.from(deps).sort();
const depsList = orderedDeps.map(dep => ` * ${JSON.stringify(dep)}`);
super(`Invalid dependencies have been reported by plugins or loaders for this module. All reported dependencies need to be absolute paths.
Invalid dependencies may lead to broken watching and caching.
As best effort we try to convert all invalid values to absolute paths and converting globs into context dependencies, but this is deprecated behavior.
Loaders: Pass absolute paths to this.addDependency (existing files), this.addMissingDependency (not existing files), and this.addContextDependency (directories).
Plugins: Pass absolute paths to fileDependencies (existing files), missingDependencies (not existing files), and contextDependencies (directories).
Globs: They are not supported. Pass absolute path to the directory as context dependencies.
The following invalid values have been reported:
${depsList.slice(0, 3).join("\n")}${
depsList.length > 3 ? "\n * and more ..." : ""
}`);
this.name = "InvalidDependenciesModuleWarning";
this.details = depsList.slice(3).join("\n");
this.module = module;
Error.captureStackTrace(this, this.constructor);
}
};

View File

@ -36,8 +36,10 @@ const {
keepOriginalOrder
} = require("./util/comparators");
const createHash = require("./util/createHash");
const { join } = require("./util/fs");
const { contextify } = require("./util/identifier");
const makeSerializable = require("./util/makeSerializable");
const memoize = require("./util/memoize");
/** @typedef {import("source-map").RawSourceMap} SourceMap */
/** @typedef {import("webpack-sources").Source} Source */
@ -61,6 +63,12 @@ const makeSerializable = require("./util/makeSerializable");
/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
const getInvalidDependenciesModuleWarning = memoize(() =>
require("./InvalidDependenciesModuleWarning")
);
const ABSOLUTE_PATH_REGEX = /^([a-zA-Z]:\\|\\\\|\/)/;
/**
* @typedef {Object} LoaderItem
* @property {string} loader
@ -814,6 +822,44 @@ class NormalModule extends Module {
if (!this.buildInfo.cacheable || !snapshotOptions) {
return callback();
}
// add warning for all non-absolute paths in fileDependencies, etc
// This makes it easier to find problems with watching and/or caching
let nonAbsoluteDependencies = undefined;
const checkDependencies = deps => {
for (const dep of deps) {
if (!ABSOLUTE_PATH_REGEX.test(dep)) {
if (nonAbsoluteDependencies === undefined)
nonAbsoluteDependencies = new Set();
nonAbsoluteDependencies.add(dep);
deps.delete(dep);
try {
const depWithoutGlob = dep.replace(/[\\/]?\*.*$/, "");
const absolute = join(
compilation.fileSystemInfo.fs,
this.context,
depWithoutGlob
);
if (absolute !== dep && ABSOLUTE_PATH_REGEX.test(absolute)) {
(depWithoutGlob !== dep
? this.buildInfo.contextDependencies
: deps
).add(absolute);
}
} catch (e) {
// ignore
}
}
}
};
checkDependencies(this.buildInfo.fileDependencies);
checkDependencies(this.buildInfo.missingDependencies);
checkDependencies(this.buildInfo.contextDependencies);
if (nonAbsoluteDependencies !== undefined) {
const InvalidDependenciesModuleWarning = getInvalidDependenciesModuleWarning();
this.addWarning(
new InvalidDependenciesModuleWarning(this, nonAbsoluteDependencies)
);
}
// convert file/context/missingDependencies into filesystem snapshot
compilation.fileSystemInfo.createSnapshot(
startTime,

View File

@ -497,6 +497,9 @@ module.exports = mergeExports(fn, {
},
get cleverMerge() {
return require("./util/cleverMerge").cachedCleverMerge;
},
get LazySet() {
return require("./util/LazySet");
}
},

View File

@ -0,0 +1 @@
it("should compile", () => {});

View File

@ -0,0 +1,11 @@
module.exports = function (source) {
this.addDependency("loader.js");
this.addDependency("../**/dir/*.js");
this.addDependency("../**/file.js");
this.addContextDependency(".");
this.addMissingDependency("./missing1.js");
this.addMissingDependency("missing2.js");
this.addMissingDependency("missing3.js");
this.addMissingDependency("missing4.js");
return source;
};

View File

@ -0,0 +1,12 @@
module.exports = [
[
{ moduleName: /\.\/index\.js/ },
/Invalid dependencies have been reported/,
/"\."/,
/"\.\.\/\*\*\/dir\/\*\.js"/,
{ details: /"\.\/missing1\.js"/ },
{ details: /"loader\.js"/ },
/and more/,
{ details: /"missing3\.js"/ }
]
];

View File

@ -0,0 +1,50 @@
const webpack = require("../../../../");
const path = require("path");
/** @type {import("../../../../").Configuration} */
module.exports = {
module: {
rules: [
{
test: /index\.js$/,
loader: "./loader.js"
}
]
},
plugins: [
compiler => {
compiler.hooks.compilation.tap("Test", compilation => {
compilation.hooks.succeedModule.tap("Test", module => {
const fileDeps = new webpack.util.LazySet();
const contextDeps = new webpack.util.LazySet();
const missingDeps = new webpack.util.LazySet();
const buildDeps = new webpack.util.LazySet();
module.addCacheDependencies(
fileDeps,
contextDeps,
missingDeps,
buildDeps
);
expect(Array.from(fileDeps).sort()).toEqual([
path.join(__dirname, "index.js"),
path.join(__dirname, "loader.js")
]);
expect(Array.from(contextDeps).sort()).toEqual([
path.join(__dirname, ".."),
__dirname
]);
expect(Array.from(missingDeps).sort()).toEqual([
path.join(__dirname, "missing1.js"),
path.join(__dirname, "missing2.js"),
path.join(__dirname, "missing3.js"),
path.join(__dirname, "missing4.js")
]);
expect(Array.from(fileDeps).sort()).toEqual([
path.join(__dirname, "index.js"),
path.join(__dirname, "loader.js")
]);
});
});
}
]
};

View File

@ -45,12 +45,12 @@ module.exports = [
plugins: [
new webpack.DefinePlugin({
VALUE: webpack.DefinePlugin.runtimeValue(() => read("123.txt"), [
"./123.txt"
join(__dirname, "./123.txt")
])
}),
new webpack.DefinePlugin({
VALUE: webpack.DefinePlugin.runtimeValue(() => read("321.txt"), [
"./321.txt"
join(__dirname, "./321.txt")
])
})
]

5
types.d.ts vendored
View File

@ -4757,7 +4757,8 @@ declare interface KnownStatsPrinterContext {
formatTime?: (time: number, boldQuantity?: boolean) => string;
chunkGroupKind?: string;
}
declare abstract class LazySet<T> {
declare class LazySet<T> {
constructor(iterable?: Iterable<T>);
readonly size: number;
add(item: T): LazySet<T>;
addAll(iterable: LazySet<T> | Iterable<T>): LazySet<T>;
@ -4774,6 +4775,7 @@ declare abstract class LazySet<T> {
[Symbol.iterator](): IterableIterator<T>;
readonly [Symbol.toStringTag]: string;
serialize(__0: { write: any }): void;
static deserialize(__0: { read: any }): LazySet<any>;
}
declare interface LibIdentOptions {
/**
@ -10784,6 +10786,7 @@ declare namespace exports {
export { MEASURE_START_OPERATION, MEASURE_END_OPERATION };
}
export const cleverMerge: <T, O>(first: T, second: O) => T | O | (T & O);
export { LazySet };
}
export namespace sources {
export {