add experimental SyncModuleIdsPlugin

This commit is contained in:
Tobias Koppers 2022-01-19 14:21:10 +01:00
parent 1489b91a98
commit 8828dfa394
7 changed files with 261 additions and 5 deletions

View File

@ -165,6 +165,11 @@ class Compiler {
/** @type {AsyncSeriesHook<[Compilation]>} */
afterCompile: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<[]>} */
readRecords: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<[]>} */
emitRecords: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<[Compiler]>} */
watchRun: new AsyncSeriesHook(["compiler"]),
/** @type {SyncHook<[Error]>} */
@ -882,8 +887,32 @@ ${other}`);
* @returns {void}
*/
emitRecords(callback) {
if (!this.recordsOutputPath) return callback();
if (this.hooks.emitRecords.isUsed()) {
if (this.recordsOutputPath) {
asyncLib.parallel(
[
cb => this.hooks.emitRecords.callAsync(cb),
this._emitRecords.bind(this)
],
err => callback(err)
);
} else {
this.hooks.emitRecords.callAsync(callback);
}
} else {
if (this.recordsOutputPath) {
this._emitRecords(callback);
} else {
callback();
}
}
}
/**
* @param {Callback<void>} callback signals when the call finishes
* @returns {void}
*/
_emitRecords(callback) {
const writeFile = () => {
this.outputFileSystem.writeFile(
this.recordsOutputPath,
@ -926,6 +955,31 @@ ${other}`);
* @returns {void}
*/
readRecords(callback) {
if (this.hooks.readRecords.isUsed()) {
if (this.recordsInputPath) {
asyncLib.parallel([
cb => this.hooks.readRecords.callAsync(cb),
this._readRecords.bind(this)
]);
} else {
this.records = {};
this.hooks.readRecords.callAsync(callback);
}
} else {
if (this.recordsInputPath) {
this._readRecords(callback);
} else {
this.records = {};
callback();
}
}
}
/**
* @param {Callback<void>} callback signals when the call finishes
* @returns {void}
*/
_readRecords(callback) {
if (!this.recordsInputPath) {
this.records = {};
return callback();

View File

@ -0,0 +1,140 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { WebpackError } = require("..");
const { getUsedModuleIdsAndModules } = require("./IdHelpers");
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Module")} Module */
const plugin = "SyncModuleIdsPlugin";
class SyncModuleIdsPlugin {
/**
* @param {Object} options options
* @param {string} options.path path to file
* @param {string=} options.context context for module names
* @param {function(Module): boolean} options.test selector for modules
* @param {"read" | "create" | "merge" | "update"=} options.mode operation mode (defaults to merge)
*/
constructor({ path, context, test, mode }) {
this._path = path;
this._context = context;
this._test = test || (() => true);
const readAndWrite = !mode || mode === "merge" || mode === "update";
this._read = readAndWrite || mode === "read";
this._write = readAndWrite || mode === "create";
this._prune = mode === "update";
}
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
/** @type {Map<string, string | number>} */
let data;
let dataChanged = false;
if (this._read) {
compiler.hooks.readRecords.tapAsync(plugin, callback => {
const fs = compiler.intermediateFileSystem;
fs.readFile(this._path, (err, buffer) => {
if (err) {
if (err.code !== "ENOENT") {
return callback(err);
}
return callback();
}
const json = JSON.parse(buffer.toString());
data = new Map();
for (const key of Object.keys(json)) {
data.set(key, json[key]);
}
dataChanged = false;
return callback();
});
});
}
if (this._write) {
compiler.hooks.emitRecords.tapAsync(plugin, callback => {
if (!data || !dataChanged) return callback();
const json = {};
const sorted = Array.from(data).sort(([a], [b]) => (a < b ? -1 : 1));
for (const [key, value] of sorted) {
json[key] = value;
}
const fs = compiler.intermediateFileSystem;
fs.writeFile(this._path, JSON.stringify(json), callback);
});
}
compiler.hooks.thisCompilation.tap(plugin, compilation => {
const associatedObjectForCache = compiler.root;
const context = this._context || compiler.context;
if (this._read) {
compilation.hooks.reviveModules.tap(plugin, (_1, _2) => {
if (!data) return;
const { chunkGraph } = compilation;
const [usedIds, modules] = getUsedModuleIdsAndModules(
compilation,
this._test
);
for (const module of modules) {
const name = module.libIdent({
context,
associatedObjectForCache
});
if (!name) continue;
const id = data.get(name);
const idAsString = `${id}`;
if (usedIds.has(idAsString)) {
const err = new WebpackError(
`SyncModuleIdsPlugin: Unable to restore id '${id}' from '${this._path}' as it's already used.`
);
err.module = module;
compilation.errors.push(err);
}
chunkGraph.setModuleId(module, id);
usedIds.add(idAsString);
}
});
}
if (this._write) {
compilation.hooks.recordModules.tap(plugin, modules => {
const { chunkGraph } = compilation;
let oldData = data;
if (!oldData) {
oldData = data = new Map();
} else if (this._prune) {
data = new Map();
}
for (const module of modules) {
if (this._test(module)) {
const name = module.libIdent({
context,
associatedObjectForCache
});
if (!name) continue;
const id = chunkGraph.getModuleId(module);
if (id === null) continue;
const oldId = oldData.get(name);
if (oldId !== id) {
dataChanged = true;
} else if (data === oldData) {
continue;
}
data.set(name, id);
}
}
if (data.size !== oldData.size) dataChanged = true;
});
}
});
}
}
module.exports = SyncModuleIdsPlugin;

View File

@ -564,6 +564,11 @@ module.exports = mergeExports(fn, {
get HttpUriPlugin() {
return require("./schemes/HttpUriPlugin");
}
},
ids: {
get SyncModuleIdsPlugin() {
return require("./ids/SyncModuleIdsPlugin");
}
}
}
});

View File

@ -1 +1,3 @@
module.exports = require("../css-modules/warnings");
for (const item of module.exports.slice(0, module.exports.length / 2))
module.exports.push(item);

View File

@ -1,8 +1,8 @@
const path = require("path");
const webpack = require("../../../../");
/** @type {import("../../../../").Configuration[]} */
module.exports = [
/** @type {function(any, any): import("../../../../").Configuration[]} */
module.exports = (env, { testPath }) => [
{
context: path.join(__dirname, "../css-modules"),
entry: "../css-modules-in-node/index.js",
@ -31,5 +31,24 @@ module.exports = [
test: m => m.type.startsWith("css")
})
]
},
{
context: path.join(__dirname, "../css-modules"),
entry: "../css-modules-in-node/index.js",
target: "node",
mode: "production",
output: {
uniqueName: "my-app"
},
experiments: {
css: true
},
plugins: [
new webpack.experiments.ids.SyncModuleIdsPlugin({
test: m => m.type.startsWith("css"),
path: path.resolve(testPath, "../css-modules/module-ids.json"),
mode: "read"
})
]
}
];

View File

@ -1,7 +1,8 @@
const webpack = require("../../../../");
const path = require("path");
/** @type {import("../../../../").Configuration[]} */
module.exports = [
/** @type {function(any, any): import("../../../../").Configuration[]} */
module.exports = (env, { testPath }) => [
{
target: "web",
mode: "development",
@ -24,6 +25,11 @@ module.exports = [
failOnConflict: true,
fixedLength: true,
test: m => m.type.startsWith("css")
}),
new webpack.experiments.ids.SyncModuleIdsPlugin({
test: m => m.type.startsWith("css"),
path: path.resolve(testPath, "module-ids.json"),
mode: "create"
})
]
}

30
types.d.ts vendored
View File

@ -1900,6 +1900,8 @@ declare class Compiler {
make: AsyncParallelHook<[Compilation]>;
finishMake: AsyncParallelHook<[Compilation]>;
afterCompile: AsyncSeriesHook<[Compilation]>;
readRecords: AsyncSeriesHook<[]>;
emitRecords: AsyncSeriesHook<[]>;
watchRun: AsyncSeriesHook<[Compiler]>;
failed: SyncHook<[Error]>;
invalid: SyncHook<[null | string, number]>;
@ -11599,6 +11601,31 @@ type StatsValue =
| "minimal"
| "normal"
| "detailed";
declare class SyncModuleIdsPlugin {
constructor(__0: {
/**
* path to file
*/
path: string;
/**
* context for module names
*/
context?: string;
/**
* selector for modules
*/
test: (arg0: Module) => boolean;
/**
* operation mode (defaults to merge)
*/
mode?: "read" | "create" | "merge" | "update";
});
/**
* Apply the plugin
*/
apply(compiler: Compiler): void;
}
declare interface SyntheticDependencyLocation {
name: string;
index?: number;
@ -12811,6 +12838,9 @@ declare namespace exports {
export namespace schemes {
export { HttpUriPlugin };
}
export namespace ids {
export { SyncModuleIdsPlugin };
}
}
export type WebpackPluginFunction = (
this: Compiler,