add manifest option

This commit is contained in:
Ivan Kopeykin 2021-10-22 18:01:28 +03:00
parent 7ec1187eb3
commit a5e113f289
17 changed files with 352 additions and 1 deletions

View File

@ -231,6 +231,10 @@ export type FilterTypes = FilterItemTypes[] | FilterItemTypes;
* Filtering value, regexp or function.
*/
export type FilterItemTypes = RegExp | string | ((value: string) => boolean);
/**
* Options for manifest.
*/
export type Manifest = false | string;
/**
* Enable production optimizations or development hints.
*/
@ -817,6 +821,10 @@ export interface WebpackOptions {
* Custom values available in the loader context.
*/
loader?: Loader;
/**
* Options for manifest.
*/
manifest?: Manifest;
/**
* Enable production optimizations or development hints.
*/
@ -3247,6 +3255,10 @@ export interface WebpackOptionsNormalized {
* Custom values available in the loader context.
*/
loader?: Loader;
/**
* Options for manifest.
*/
manifest?: Manifest;
/**
* Enable production optimizations or development hints.
*/
@ -3336,6 +3348,10 @@ export interface ExperimentsExtra {
* Compile entrypoints and import()s only when they are accessed.
*/
lazyCompilation?: boolean | LazyCompilationOptions;
/**
* Allow to output manifest, request child manifest in compilation.
*/
manifest?: boolean;
}
/**
* Enables/Disables experiments (experimental features with relax SemVer compatibility).
@ -3349,6 +3365,10 @@ export interface ExperimentsNormalizedExtra {
* Compile entrypoints and import()s only when they are accessed.
*/
lazyCompilation?: LazyCompilationOptions;
/**
* Allow to output manifest, request child manifest in compilation.
*/
manifest?: boolean;
}
/**
* If an dependency matches exactly a property of the object, the property value is used as dependency.

View File

@ -11,6 +11,7 @@ const { SyncHook, MultiHook } = require("tapable");
const ConcurrentCompilationError = require("./ConcurrentCompilationError");
const MultiStats = require("./MultiStats");
const MultiWatching = require("./MultiWatching");
const ManifestConsumerPlugin = require("./stats/ManifestConsumerPlugin");
const ArrayQueue = require("./util/ArrayQueue");
/** @template T @typedef {import("tapable").AsyncSeriesHook<T>} AsyncSeriesHook<T> */
@ -193,6 +194,17 @@ module.exports = class MultiCompiler {
this.dependencies.set(compiler, dependencies);
}
/**
* @param {Compiler} compiler compiler
* @param {Compiler[]} dependencies parents of child compiler
* @returns {void}
*/
applyDependencies(compiler, dependencies) {
if (compiler.options.experiments.manifest) {
new ManifestConsumerPlugin().apply(compiler, dependencies);
}
}
/**
* @param {Callback<MultiStats>} callback signals when the validation is complete
* @returns {boolean} true if the dependencies are valid
@ -358,7 +370,15 @@ module.exports = class MultiCompiler {
node.state = "queued";
queue.enqueue(node);
}
// we should apply dependencies even for empty list
// this allows to pass correct compilation errors
this.applyDependencies(
node.compiler,
node.parents.map(({ compiler }) => compiler)
);
}
let errored = false;
let running = 0;
const parallelism = this._options.parallelism;

View File

@ -508,6 +508,11 @@ class WebpackOptionsApply extends OptionsApply {
}
}
if (options.manifest) {
const ManifestPlugin = require("./stats/ManifestPlugin");
new ManifestPlugin(options.manifest).apply(compiler);
}
if (options.performance) {
const SizeLimitsPlugin = require("./performance/SizeLimitsPlugin");
new SizeLimitsPlugin(options.performance).apply(compiler);

View File

@ -154,6 +154,7 @@ const applyWebpackOptionsDefaults = options => {
}
F(options, "devtool", () => (development ? "eval" : false));
D(options, "manifest", false);
D(options, "watch", false);
D(options, "profile", false);
D(options, "parallelism", 100);
@ -272,6 +273,7 @@ const applyExperimentsDefaults = (experiments, { production, development }) => {
D(experiments, "asset", false);
D(experiments, "layers", false);
D(experiments, "lazyCompilation", undefined);
D(experiments, "manifest", false);
D(experiments, "buildHttp", undefined);
D(experiments, "futureDefaults", false);
D(experiments, "cacheUnaffected", experiments.futureDefaults);

View File

@ -212,6 +212,7 @@ const getNormalizedWebpackOptions = config => {
: undefined,
infrastructureLogging: cloneObject(config.infrastructureLogging),
loader: cloneObject(config.loader),
manifest: config.manifest,
mode: config.mode,
module: nestedConfig(config.module, module => ({
noParse: module.noParse,

View File

@ -0,0 +1,96 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
const NormalModule = require("../NormalModule");
const { extractManifest } = require("./ManifestPlugin");
/** @typedef {import("../Compiler")} Compiler */
const STAGE = 101;
function getCompilerName(request) {
return request.slice(/* 'webpack-manifest:'.length */ 17);
}
class ManifestConsumerPlugin {
/**
* Every stats provider compiler should have name
* @param {Compiler} statsConsumerCompiler compiler that consumes stats
* @param {Compiler[]} statsProviderCompilers compiler that provides stats
*/
apply(statsConsumerCompiler, statsProviderCompilers) {
if (!statsConsumerCompiler.options.experiments.manifest) {
throw new Error(
"'manifest' is only allowed when 'experiments.manifest' is enabled"
);
}
const statsByCompilerName = new Map();
const manifestByCompilerName = new Map();
const names = new Set();
function getRawManifest(rawRequest) {
const name = getCompilerName(rawRequest);
let manifest = manifestByCompilerName.get(name);
if (!manifest) {
const stats = statsByCompilerName.get(name);
if (!stats) return;
manifest = extractManifest(stats);
statsByCompilerName.delete(name);
manifestByCompilerName.set(name, manifest);
}
return JSON.stringify(manifest);
}
for (const compiler of statsProviderCompilers) {
const name = compiler.name;
names.add(name);
compiler.hooks.done.tap(
{
stage: STAGE,
name: "ManifestConsumerPlugin"
},
stats => {
statsByCompilerName.set(name, stats);
}
);
}
statsConsumerCompiler.hooks.compilation.tap(
"ManifestConsumerPlugin",
(compilation, { normalModuleFactory }) => {
normalModuleFactory.hooks.resolveForScheme
.for("webpack-manifest")
.tapAsync(
"ManifestConsumerPlugin",
(resourceData, resolveData, callback) => {
const compilerName = getCompilerName(resourceData.resource);
if (!names.has(compilerName)) {
return callback(
new Error(
`Compiler ${JSON.stringify(compilerName)} not found.`
)
);
}
resourceData.data.name = compilerName;
resourceData.data.mimetype = "application/json";
callback(null, true);
}
);
NormalModule.getCompilationHooks(compilation)
.readResourceForScheme.for("webpack-manifest")
.tap("ManifestConsumerPlugin", getRawManifest);
}
);
}
}
module.exports = ManifestConsumerPlugin;

View File

@ -0,0 +1,64 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Stats")} Stats */
const STAGE = 100;
/**
* @param {Stats} stats stats
* @returns {object} json
*/
function extractManifest(stats) {
return stats.toJson({ preset: "manifest" });
}
class ManifestPlugin {
/**
* @param {string} path path
*/
constructor(path) {
/** @type {string} */
this.path = path;
}
/**
* @param {Compiler} compiler compiler that provides stats
*/
apply(compiler) {
if (!compiler.options.experiments.manifest) {
throw new Error(
"'manifest' is only allowed when 'experiments.manifest' is enabled"
);
}
compiler.hooks.done.tapAsync(
{
stage: STAGE,
name: "ManifestPlugin"
},
(stats, cb) => {
const manifest = extractManifest(stats);
compiler.outputFileSystem.writeFile(
this.path,
JSON.stringify(manifest),
err => {
if (!err) return cb();
err.message = `Error during writing manifest. ${err.message}`;
cb(err);
}
);
}
);
}
}
ManifestPlugin.extractManifest = extractManifest;
module.exports = ManifestPlugin;

File diff suppressed because one or more lines are too long

View File

@ -723,6 +723,10 @@
}
]
},
"manifest": {
"description": "Allow to output manifest, request child manifest in compilation.",
"type": "boolean"
},
"outputModule": {
"description": "Allow output javascript files as module source type.",
"type": "boolean"
@ -818,6 +822,10 @@
}
]
},
"manifest": {
"description": "Allow to output manifest, request child manifest in compilation.",
"type": "boolean"
},
"outputModule": {
"description": "Allow output javascript files as module source type.",
"type": "boolean"
@ -1817,6 +1825,17 @@
"description": "Custom values available in the loader context.",
"type": "object"
},
"Manifest": {
"description": "Options for manifest.",
"anyOf": [
{
"enum": [false]
},
{
"type": "string"
}
]
},
"MemoryCacheOptions": {
"description": "Options object for in-memory caching.",
"type": "object",
@ -4887,6 +4906,9 @@
"loader": {
"$ref": "#/definitions/Loader"
},
"manifest": {
"$ref": "#/definitions/Manifest"
},
"mode": {
"$ref": "#/definitions/Mode"
},
@ -5033,6 +5055,9 @@
"loader": {
"$ref": "#/definitions/Loader"
},
"manifest": {
"$ref": "#/definitions/Manifest"
},
"mode": {
"$ref": "#/definitions/Mode"
},

View File

@ -98,6 +98,7 @@ Object {
"futureDefaults": false,
"layers": false,
"lazyCompilation": undefined,
"manifest": false,
"outputModule": false,
"syncWebAssembly": false,
"topLevelAwait": false,
@ -118,6 +119,7 @@ Object {
"loader": Object {
"target": "web",
},
"manifest": false,
"mode": "none",
"module": Object {
"defaultRules": Array [

View File

@ -691,6 +691,19 @@ Object {
"multiple": false,
"simpleType": "string",
},
"experiments-manifest": Object {
"configs": Array [
Object {
"description": "Allow to output manifest, request child manifest in compilation.",
"multiple": false,
"path": "experiments.manifest",
"type": "boolean",
},
],
"description": "Allow to output manifest, request child manifest in compilation.",
"multiple": false,
"simpleType": "boolean",
},
"experiments-output-module": Object {
"configs": Array [
Object {
@ -1051,6 +1064,28 @@ Object {
"multiple": false,
"simpleType": "string",
},
"manifest": Object {
"configs": Array [
Object {
"description": "Options for manifest.",
"multiple": false,
"path": "manifest",
"type": "enum",
"values": Array [
false,
],
},
Object {
"description": "Options for manifest.",
"multiple": false,
"path": "manifest",
"type": "string",
},
],
"description": "Options for manifest.",
"multiple": false,
"simpleType": "string",
},
"mode": Object {
"configs": Array [
Object {
@ -7267,6 +7302,7 @@ Object {
"summary",
"errors-only",
"errors-warnings",
"manifest",
"minimal",
"normal",
"detailed",

View File

@ -0,0 +1,11 @@
import manifest1 from "webpack-manifest:web";
import manifest2 from "webpack-manifest:web2";
it("should provide correct entrypoints", () => {
expect(Object.keys(manifest1.entrypoints)).toEqual(
expect.arrayContaining(["page1", "page2"])
);
expect(Object.keys(manifest2.entrypoints)).toEqual(
expect.arrayContaining(["main"])
);
});

View File

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

View File

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

View File

@ -0,0 +1,12 @@
module.exports = {
findBundle: function(i, options) {
switch (i) {
case 0:
return ["./page1.js", "./page2.js"];
case 1:
return "./web2.js";
case 2:
return "./main.js";
}
}
};

View File

@ -0,0 +1,34 @@
/** @type {import("../../../../").Configuration} */
module.exports = [
{
name: "web",
target: "web",
entry: {
page1: "./page1.js",
page2: "./page2.js"
},
output: {
filename: "[name].js"
}
},
{
name: "web2",
target: "web",
entry: "./page1.js",
output: {
filename: "web2.js"
}
},
{
name: "node",
target: "node",
entry: "./node.js",
experiments: {
manifest: true
},
output: {
filename: "main.js"
},
dependencies: ["web", "web2"]
}
];

21
types.d.ts vendored
View File

@ -2131,6 +2131,11 @@ declare interface Configuration {
*/
loader?: Loader;
/**
* Options for manifest.
*/
manifest?: string | false;
/**
* Enable production optimizations or development hints.
*/
@ -3351,6 +3356,11 @@ declare interface ExperimentsExtra {
* Compile entrypoints and import()s only when they are accessed.
*/
lazyCompilation?: boolean | LazyCompilationOptions;
/**
* Allow to output manifest, request child manifest in compilation.
*/
manifest?: boolean;
}
type ExperimentsNormalized = ExperimentsCommon & ExperimentsNormalizedExtra;
@ -3367,6 +3377,11 @@ declare interface ExperimentsNormalizedExtra {
* Compile entrypoints and import()s only when they are accessed.
*/
lazyCompilation?: LazyCompilationOptions;
/**
* Allow to output manifest, request child manifest in compilation.
*/
manifest?: boolean;
}
declare abstract class ExportInfo {
name: string;
@ -7069,6 +7084,7 @@ declare class MultiCompiler {
intermediateFileSystem: IntermediateFileSystem;
getInfrastructureLogger(name?: any): WebpackLogger;
setDependencies(compiler: Compiler, dependencies: string[]): void;
applyDependencies(compiler: Compiler, dependencies: Compiler[]): void;
validateDependencies(callback: CallbackFunction<MultiStats>): boolean;
runWithDependencies(
compilers: Compiler[],
@ -11924,6 +11940,11 @@ declare interface WebpackOptionsNormalized {
*/
loader?: Loader;
/**
* Options for manifest.
*/
manifest?: string | false;
/**
* Enable production optimizations or development hints.
*/