mirror of https://github.com/webpack/webpack.git
feat: port webpack-manifest-plugin
This commit is contained in:
parent
20abdce9b0
commit
81268133cd
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* This file was automatically generated.
|
||||||
|
* DO NOT MODIFY BY HAND.
|
||||||
|
* Run `yarn fix:special` to update
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ManifestPluginOptions {
|
||||||
|
/**
|
||||||
|
* Specifies the filename of the output file on disk. By default the plugin will emit `manifest.json` inside the 'output.path' directory.
|
||||||
|
*/
|
||||||
|
filename?: string;
|
||||||
|
}
|
|
@ -670,6 +670,9 @@ module.exports = mergeExports(fn, {
|
||||||
get SyncModuleIdsPlugin() {
|
get SyncModuleIdsPlugin() {
|
||||||
return require("./ids/SyncModuleIdsPlugin");
|
return require("./ids/SyncModuleIdsPlugin");
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
get ManifestPlugin() {
|
||||||
|
return require("./stats/ManifestPlugin");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
/*
|
||||||
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||||
|
Author Haijie Xie @hai-x
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const path = require("path");
|
||||||
|
const { RawSource } = require("webpack-sources");
|
||||||
|
const Compilation = require("../Compilation");
|
||||||
|
const HotUpdateChunk = require("../HotUpdateChunk");
|
||||||
|
const createSchemaValidation = require("../util/create-schema-validation");
|
||||||
|
|
||||||
|
/** @typedef {import("../Compiler")} Compiler */
|
||||||
|
/** @typedef {import("../Chunk")} Chunk */
|
||||||
|
/** @typedef {import("../Module")} Module */
|
||||||
|
/** @typedef {import("../NormalModule")} NormalModule */
|
||||||
|
/** @typedef {import("../config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
|
||||||
|
|
||||||
|
/** @typedef {import("../../declarations/plugins/ManifestPlugin").ManifestPluginOptions} ManifestPluginOptions */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} Asset
|
||||||
|
* @property {string} name
|
||||||
|
* @property {string} path
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {{ name: string, stage: number }} TapOptions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {TapOptions} */
|
||||||
|
const TAP_OPTIONS = {
|
||||||
|
name: "ManifestPlugin",
|
||||||
|
stage: Compilation.PROCESS_ASSETS_STAGE_REPORT
|
||||||
|
};
|
||||||
|
|
||||||
|
const validate = createSchemaValidation(
|
||||||
|
require("../../schemas/plugins/ManifestPlugin.check"),
|
||||||
|
() => require("../../schemas/plugins/ManifestPlugin.json"),
|
||||||
|
{
|
||||||
|
name: "ManifestPlugin",
|
||||||
|
baseDataPath: "options"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} filename filename
|
||||||
|
* @returns {string} extname
|
||||||
|
*/
|
||||||
|
const extname = (filename) => {
|
||||||
|
const replaced = filename.replace(/\?.*/, "");
|
||||||
|
const split = replaced.split(".");
|
||||||
|
const last = split.pop();
|
||||||
|
if (!last) return "";
|
||||||
|
return last && /^(gz|map)$/i.test(last) ? `${split.pop()}.${last}` : last;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ManifestPlugin {
|
||||||
|
/**
|
||||||
|
* @param {ManifestPluginOptions} options options
|
||||||
|
*/
|
||||||
|
constructor(options) {
|
||||||
|
validate(options);
|
||||||
|
|
||||||
|
const defaultOptions = {
|
||||||
|
filename: "manifest.json"
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {Required<ManifestPluginOptions>} */
|
||||||
|
this.options = Object.assign(defaultOptions, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the plugin
|
||||||
|
* @param {Compiler} compiler the compiler instance
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
apply(compiler) {
|
||||||
|
/** @type {Map<string,Module>} */
|
||||||
|
const moduleAssets = new Map();
|
||||||
|
|
||||||
|
const outputFilename = path.resolve(
|
||||||
|
/** @type {WebpackOptions} */ (compiler.options).output.path,
|
||||||
|
this.options.filename
|
||||||
|
);
|
||||||
|
const manifestAssetName = path.relative(
|
||||||
|
/** @type {WebpackOptions} */ (compiler.options).output.path,
|
||||||
|
outputFilename
|
||||||
|
);
|
||||||
|
|
||||||
|
compiler.hooks.compilation.tap(TAP_OPTIONS, (compilation) => {
|
||||||
|
compilation.hooks.moduleAsset.tap(TAP_OPTIONS, (module, asset) => {
|
||||||
|
moduleAssets.set(asset, module);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
compiler.hooks.thisCompilation.tap(TAP_OPTIONS, (compilation) => {
|
||||||
|
compilation.hooks.processAssets.tap(TAP_OPTIONS, () => {
|
||||||
|
const stats = compilation.getStats().toJson({
|
||||||
|
all: false,
|
||||||
|
assets: true,
|
||||||
|
cachedAssets: true,
|
||||||
|
assetsSpace: Infinity,
|
||||||
|
ids: true,
|
||||||
|
publicPath: true
|
||||||
|
});
|
||||||
|
|
||||||
|
/** @type {Map<string, Asset>} */
|
||||||
|
const mapByPath = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Asset} asset asset
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
const addToMap = (asset) => {
|
||||||
|
const { path } = asset;
|
||||||
|
mapByPath.set(path, asset);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const chunk of compilation.chunks) {
|
||||||
|
if (chunk instanceof HotUpdateChunk) continue;
|
||||||
|
const chunkName = chunk.name;
|
||||||
|
|
||||||
|
for (const auxiliaryFile of chunk.auxiliaryFiles) {
|
||||||
|
addToMap({
|
||||||
|
name: path.basename(auxiliaryFile),
|
||||||
|
path: auxiliaryFile
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const chunkFilename of chunk.files) {
|
||||||
|
const name = chunkName
|
||||||
|
? `${chunkName}.${extname(chunkFilename)}`
|
||||||
|
: chunkFilename;
|
||||||
|
addToMap({
|
||||||
|
name,
|
||||||
|
path: chunkFilename
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stats.assets) {
|
||||||
|
// module assets are included in `chunk.auxiliaryFiles`, so we add them after chunk assets
|
||||||
|
for (const asset of stats.assets) {
|
||||||
|
let moduleAssetName;
|
||||||
|
|
||||||
|
const module = /** @type {NormalModule} */ (
|
||||||
|
moduleAssets.get(asset.name)
|
||||||
|
);
|
||||||
|
if (module && module.userRequest) {
|
||||||
|
moduleAssetName = path.join(
|
||||||
|
path.dirname(asset.name),
|
||||||
|
path.basename(module.userRequest)
|
||||||
|
);
|
||||||
|
} else if (asset.info.sourceFilename) {
|
||||||
|
moduleAssetName = path.join(
|
||||||
|
path.dirname(asset.name),
|
||||||
|
path.basename(asset.info.sourceFilename)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (moduleAssetName) {
|
||||||
|
addToMap({
|
||||||
|
name: moduleAssetName,
|
||||||
|
path: asset.name
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will handle them later
|
||||||
|
if (
|
||||||
|
(asset.chunks && asset.chunks.length > 0) ||
|
||||||
|
(asset.auxiliaryChunks && asset.auxiliaryChunks.length > 0)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
addToMap({
|
||||||
|
name: asset.name,
|
||||||
|
path: asset.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {Record<string,string>} */
|
||||||
|
const manifest = {};
|
||||||
|
const hashDigestLength = compilation.outputOptions.hashDigestLength;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name name
|
||||||
|
* @returns {string} hash removed name
|
||||||
|
*/
|
||||||
|
const removeHash = (name) => {
|
||||||
|
if (hashDigestLength <= 0) return name;
|
||||||
|
const reg = new RegExp(
|
||||||
|
`(\\.[a-f0-9]{${hashDigestLength}})(?=\\.)?`,
|
||||||
|
"gi"
|
||||||
|
);
|
||||||
|
return name.replace(reg, "");
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [_name, item] of mapByPath) {
|
||||||
|
manifest[removeHash(item.name)] = stats.publicPath
|
||||||
|
? stats.publicPath +
|
||||||
|
(stats.publicPath.endsWith("/")
|
||||||
|
? `${item.path}`
|
||||||
|
: `/${item.path}`)
|
||||||
|
: item.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
compilation.emitAsset(
|
||||||
|
manifestAssetName,
|
||||||
|
new RawSource(JSON.stringify(manifest, null, 2))
|
||||||
|
);
|
||||||
|
|
||||||
|
moduleAssets.clear();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ManifestPlugin;
|
|
@ -0,0 +1,7 @@
|
||||||
|
/*
|
||||||
|
* This file was automatically generated.
|
||||||
|
* DO NOT MODIFY BY HAND.
|
||||||
|
* Run `yarn fix:special` to update
|
||||||
|
*/
|
||||||
|
declare const check: (options: import("../../declarations/plugins/ManifestPlugin").ManifestPluginOptions) => boolean;
|
||||||
|
export = check;
|
|
@ -0,0 +1,6 @@
|
||||||
|
/*
|
||||||
|
* This file was automatically generated.
|
||||||
|
* DO NOT MODIFY BY HAND.
|
||||||
|
* Run `yarn fix:special` to update
|
||||||
|
*/
|
||||||
|
"use strict";function r(e,{instancePath:t="",parentData:a,parentDataProperty:o,rootData:n=e}={}){if(!e||"object"!=typeof e||Array.isArray(e))return r.errors=[{params:{type:"object"}}],!1;{const t=0;for(const t in e)if("filename"!==t)return r.errors=[{params:{additionalProperty:t}}],!1;if(0===t&&void 0!==e.filename&&"string"!=typeof e.filename)return r.errors=[{params:{type:"string"}}],!1}return r.errors=null,!0}module.exports=r,module.exports.default=r;
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"title": "ManifestPluginOptions",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"filename": {
|
||||||
|
"description": "Specifies the filename of the output file on disk. By default the plugin will emit `manifest.json` inside the 'output.path' directory.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import url from "../../asset-modules/_images/file.png";
|
||||||
|
|
||||||
|
import(/* webpackChunkName: 'file' */ "./file.txt?foo");
|
||||||
|
|
||||||
|
it("should emit manifest with expected entries and paths", () => {
|
||||||
|
expect(url).toEqual("/app/file-loader.png");
|
||||||
|
|
||||||
|
const manifest = JSON.parse(
|
||||||
|
fs.readFileSync(path.resolve(__dirname, "test.json"), "utf-8")
|
||||||
|
);
|
||||||
|
|
||||||
|
const keys = Object.keys(manifest).sort();
|
||||||
|
expect(keys).toEqual(
|
||||||
|
[
|
||||||
|
"file.js",
|
||||||
|
"file.txt?foo",
|
||||||
|
"main.js",
|
||||||
|
"third.party.js",
|
||||||
|
"file.png"
|
||||||
|
].sort()
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(manifest["main.js"]).toMatch(/\/app\/bundle0\.js/);
|
||||||
|
expect(manifest["file.js"]).toMatch(/\/app\/file\.[a-f0-9]+\.js/);
|
||||||
|
expect(manifest["file.txt?foo"]).toMatch(/\/app\/file\.[a-f0-9]+\.txt\?foo/);
|
||||||
|
expect(manifest["third.party.js"]).toBe("/app/third.party.js");
|
||||||
|
expect(manifest["file.png"]).toBe("/app/file-loader.png");
|
||||||
|
});
|
|
@ -0,0 +1,56 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const { RawSource } = require("webpack-sources");
|
||||||
|
const webpack = require("../../../../");
|
||||||
|
|
||||||
|
class CopyPlugin {
|
||||||
|
apply(compiler) {
|
||||||
|
const hookOptions = {
|
||||||
|
name: "MockCopyPlugin",
|
||||||
|
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
|
||||||
|
};
|
||||||
|
const emit = (compilation, callback) => {
|
||||||
|
const output = "// some compilation result\n";
|
||||||
|
compilation.emitAsset("third.party.js", new RawSource(output));
|
||||||
|
callback && callback(); // eslint-disable-line no-unused-expressions
|
||||||
|
};
|
||||||
|
|
||||||
|
compiler.hooks.thisCompilation.tap(hookOptions, (compilation) => {
|
||||||
|
compilation.hooks.processAssets.tap(hookOptions, () => emit(compilation));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import("../../../../").Configuration} */
|
||||||
|
module.exports = {
|
||||||
|
node: {
|
||||||
|
__dirname: false,
|
||||||
|
__filename: false
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
publicPath: "/app/",
|
||||||
|
chunkFilename: "[name].[contenthash].js",
|
||||||
|
assetModuleFilename: "[name].[contenthash][ext][query]"
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new CopyPlugin(),
|
||||||
|
new webpack.experiments.ManifestPlugin({
|
||||||
|
filename: "test.json"
|
||||||
|
})
|
||||||
|
],
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.txt$/,
|
||||||
|
type: "asset/resource"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.png$/,
|
||||||
|
loader: "file-loader",
|
||||||
|
options: {
|
||||||
|
name: "file-loader.[ext]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
|
@ -9758,6 +9758,21 @@ declare interface MakeDirectoryOptions {
|
||||||
recursive?: boolean;
|
recursive?: boolean;
|
||||||
mode?: string | number;
|
mode?: string | number;
|
||||||
}
|
}
|
||||||
|
declare class ManifestPlugin {
|
||||||
|
constructor(options: ManifestPluginOptions);
|
||||||
|
options: Required<ManifestPluginOptions>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the plugin
|
||||||
|
*/
|
||||||
|
apply(compiler: Compiler): void;
|
||||||
|
}
|
||||||
|
declare interface ManifestPluginOptions {
|
||||||
|
/**
|
||||||
|
* Specifies the filename of the output file on disk. By default the plugin will emit `manifest.json` inside the 'output.path' directory.
|
||||||
|
*/
|
||||||
|
filename?: string;
|
||||||
|
}
|
||||||
declare interface MapOptions {
|
declare interface MapOptions {
|
||||||
/**
|
/**
|
||||||
* need columns?
|
* need columns?
|
||||||
|
@ -18734,6 +18749,7 @@ declare namespace exports {
|
||||||
export namespace ids {
|
export namespace ids {
|
||||||
export { SyncModuleIdsPlugin };
|
export { SyncModuleIdsPlugin };
|
||||||
}
|
}
|
||||||
|
export { ManifestPlugin };
|
||||||
}
|
}
|
||||||
export type ExternalItemFunctionCallback = (
|
export type ExternalItemFunctionCallback = (
|
||||||
data: ExternalItemFunctionData,
|
data: ExternalItemFunctionData,
|
||||||
|
|
Loading…
Reference in New Issue