mirror of https://github.com/webpack/webpack.git
refactor: manifest plugin
Github Actions / lint (push) Waiting to run
Details
Github Actions / validate-legacy-node (push) Waiting to run
Details
Github Actions / benchmark (1/4) (push) Waiting to run
Details
Github Actions / benchmark (2/4) (push) Waiting to run
Details
Github Actions / benchmark (3/4) (push) Waiting to run
Details
Github Actions / benchmark (4/4) (push) Waiting to run
Details
Github Actions / basic (push) Waiting to run
Details
Github Actions / unit (push) Waiting to run
Details
Github Actions / integration (10.x, macos-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (10.x, macos-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (10.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (10.x, ubuntu-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (10.x, windows-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (10.x, windows-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (12.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (14.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (16.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (18.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (20.x, macos-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (20.x, macos-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (20.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (20.x, ubuntu-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (20.x, windows-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (20.x, windows-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (22.x, macos-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (22.x, macos-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (22.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (22.x, ubuntu-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (22.x, windows-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (22.x, windows-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (24.x, macos-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (24.x, macos-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (24.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (24.x, ubuntu-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (24.x, windows-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (24.x, windows-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (25.x, macos-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (25.x, macos-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (25.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (25.x, ubuntu-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (25.x, windows-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (25.x, windows-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (lts/*, ubuntu-latest, a, 1) (push) Blocked by required conditions
Details
Github Actions / integration (lts/*, ubuntu-latest, b, 1) (push) Blocked by required conditions
Details
Github Actions / lint (push) Waiting to run
Details
Github Actions / validate-legacy-node (push) Waiting to run
Details
Github Actions / benchmark (1/4) (push) Waiting to run
Details
Github Actions / benchmark (2/4) (push) Waiting to run
Details
Github Actions / benchmark (3/4) (push) Waiting to run
Details
Github Actions / benchmark (4/4) (push) Waiting to run
Details
Github Actions / basic (push) Waiting to run
Details
Github Actions / unit (push) Waiting to run
Details
Github Actions / integration (10.x, macos-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (10.x, macos-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (10.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (10.x, ubuntu-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (10.x, windows-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (10.x, windows-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (12.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (14.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (16.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (18.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (20.x, macos-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (20.x, macos-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (20.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (20.x, ubuntu-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (20.x, windows-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (20.x, windows-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (22.x, macos-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (22.x, macos-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (22.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (22.x, ubuntu-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (22.x, windows-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (22.x, windows-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (24.x, macos-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (24.x, macos-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (24.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (24.x, ubuntu-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (24.x, windows-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (24.x, windows-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (25.x, macos-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (25.x, macos-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (25.x, ubuntu-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (25.x, ubuntu-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (25.x, windows-latest, a) (push) Blocked by required conditions
Details
Github Actions / integration (25.x, windows-latest, b) (push) Blocked by required conditions
Details
Github Actions / integration (lts/*, ubuntu-latest, a, 1) (push) Blocked by required conditions
Details
Github Actions / integration (lts/*, ubuntu-latest, b, 1) (push) Blocked by required conditions
Details
This commit is contained in:
parent
3a73cc69d7
commit
57d55e492b
|
|
@ -315,7 +315,8 @@
|
|||
"spacek",
|
||||
"thelarkinn",
|
||||
"behaviour",
|
||||
"WHATWG"
|
||||
"WHATWG",
|
||||
"publicpath"
|
||||
],
|
||||
"ignoreRegExpList": [
|
||||
"/Author.+/",
|
||||
|
|
|
|||
|
|
@ -4,35 +4,69 @@
|
|||
* Run `yarn fix:special` to update
|
||||
*/
|
||||
|
||||
/**
|
||||
* A function that receives the manifest object and returns the manifest string.
|
||||
*/
|
||||
export type HandlerFunction = (manifest: ManifestObject) => string;
|
||||
/**
|
||||
* Maps asset identifiers to their manifest entries.
|
||||
*/
|
||||
export type ManifestObject = Record<string, ManifestItem>;
|
||||
|
||||
export interface ManifestPluginOptions {
|
||||
/**
|
||||
* Enables/disables generation of the entrypoints manifest section.
|
||||
*/
|
||||
entrypoints?: boolean;
|
||||
/**
|
||||
* Specifies the filename of the output file on disk. By default the plugin will emit `manifest.json` inside the 'output.path' directory.
|
||||
*/
|
||||
filename?: string;
|
||||
/**
|
||||
* Allows filtering the files which make up the manifest.
|
||||
*/
|
||||
filter?: (item: ManifestItem) => boolean;
|
||||
/**
|
||||
* A function that receives the manifest object, modifies it, and returns the modified manifest.
|
||||
*/
|
||||
generate?: (manifest: ManifestObject) => ManifestObject;
|
||||
/**
|
||||
* Specifies a path prefix for all keys in the manifest.
|
||||
*/
|
||||
prefix?: string;
|
||||
/**
|
||||
* A function that receives the manifest object and returns the manifest string.
|
||||
*/
|
||||
handler?: HandlerFunction;
|
||||
serialize?: (manifest: ManifestObject) => string;
|
||||
}
|
||||
/**
|
||||
* Describes a manifest entry that links the emitted path to the producing asset.
|
||||
* Describes a manifest entrypoint.
|
||||
*/
|
||||
export interface ManifestEntrypoint {
|
||||
/**
|
||||
* Contains the names of entrypoints.
|
||||
*/
|
||||
imports: string[];
|
||||
/**
|
||||
* Contains the names of parent entrypoints.
|
||||
*/
|
||||
parents?: string[];
|
||||
}
|
||||
/**
|
||||
* Describes a manifest asset that links the emitted path to the producing asset.
|
||||
*/
|
||||
export interface ManifestItem {
|
||||
/**
|
||||
* The compilation asset that produced this manifest entry.
|
||||
* The path absolute URL (this indicates that the path is absolute from the server's root directory) to file.
|
||||
*/
|
||||
asset?: import("../../lib/Compilation").Asset;
|
||||
file: string;
|
||||
/**
|
||||
* The public path recorded in the manifest for this asset.
|
||||
* The source path relative to the context.
|
||||
*/
|
||||
filePath: string;
|
||||
src?: string;
|
||||
}
|
||||
/**
|
||||
* The manifest object.
|
||||
*/
|
||||
export interface ManifestObject {
|
||||
/**
|
||||
* Contains the names of assets.
|
||||
*/
|
||||
assets: Record<string, ManifestItem>;
|
||||
/**
|
||||
* Contains the names of entrypoints.
|
||||
*/
|
||||
entrypoints: Record<string, ManifestEntrypoint>;
|
||||
[k: string]: any;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,19 @@
|
|||
This example demonstrates how to use webpack internal ManifestPlugin.
|
||||
This example demonstrates how to use ManifestPlugin.
|
||||
|
||||
# example.js
|
||||
|
||||
```js
|
||||
import("./baz");
|
||||
```
|
||||
import fooURL from "./foo.txt";
|
||||
|
||||
# foo.txt
|
||||
const barURL = new URL("./bar.txt", import.meta.url);
|
||||
|
||||
```js
|
||||
foo
|
||||
```
|
||||
async function loadAsync() {
|
||||
return import("./async.js");
|
||||
}
|
||||
|
||||
# bar.txt
|
||||
await loadAsync();
|
||||
|
||||
```js
|
||||
bar
|
||||
```
|
||||
|
||||
# baz.js
|
||||
|
||||
```js
|
||||
import foo from "./foo.txt";
|
||||
import bar from "./bar.txt";
|
||||
|
||||
export default foo + bar;
|
||||
export default [fooURL, barURL];
|
||||
```
|
||||
|
||||
# webpack.config.js
|
||||
|
|
@ -32,19 +21,22 @@ export default foo + bar;
|
|||
```javascript
|
||||
"use strict";
|
||||
|
||||
const YAML = require("yamljs");
|
||||
const webpack = require("../../");
|
||||
|
||||
/** @type {webpack.Configuration} */
|
||||
module.exports = {
|
||||
devtool: "source-map",
|
||||
output: {
|
||||
chunkFilename: "[name].[contenthash].js"
|
||||
},
|
||||
optimization: {
|
||||
chunkIds: "named" // To keep filename consistent between different modes (for example building only)
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /foo.txt/,
|
||||
type: "asset/resource"
|
||||
},
|
||||
{
|
||||
test: /bar.txt/,
|
||||
use: require.resolve("file-loader")
|
||||
}
|
||||
]
|
||||
|
|
@ -55,13 +47,21 @@ module.exports = {
|
|||
}),
|
||||
new webpack.ManifestPlugin({
|
||||
filename: "manifest.yml",
|
||||
handler(manifest) {
|
||||
let _manifest = "";
|
||||
for (const key in manifest) {
|
||||
if (key === "manifest.json") continue;
|
||||
_manifest += `- ${key}: '${manifest[key].filePath}'\n`;
|
||||
prefix: "/nested/[publicpath]",
|
||||
filter(item) {
|
||||
if (/.map$/.test(item.file)) {
|
||||
return false;
|
||||
}
|
||||
return _manifest;
|
||||
|
||||
return true;
|
||||
},
|
||||
generate(manifest) {
|
||||
delete manifest.assets["manifest.json"];
|
||||
manifest.custom = "value";
|
||||
return manifest;
|
||||
},
|
||||
serialize(manifest) {
|
||||
return YAML.stringify(manifest, 4);
|
||||
}
|
||||
})
|
||||
]
|
||||
|
|
@ -72,24 +72,105 @@ module.exports = {
|
|||
|
||||
```json
|
||||
{
|
||||
"output.js.map": "dist/output.js.map",
|
||||
"main.js": "dist/output.js",
|
||||
"bar.txt": "dist/a0145fafc7fab801e574631452de554b.txt",
|
||||
"foo.txt": "dist/3ee037f347c64cc372ad.txt",
|
||||
"1.output.js.map": "dist/1.output.js.map",
|
||||
"1.output.js": "dist/1.output.js"
|
||||
"entrypoints": {
|
||||
"main": {
|
||||
"imports": [
|
||||
"main.js"
|
||||
]
|
||||
}
|
||||
},
|
||||
"assets": {
|
||||
"foo.txt": {
|
||||
"file": "dist/3ee037f347c64cc372ad18857b0db91f.txt",
|
||||
"src": "foo.txt"
|
||||
},
|
||||
"bar.txt": {
|
||||
"file": "dist/a0145fafc7fab801e574.txt",
|
||||
"src": "bar.txt"
|
||||
},
|
||||
"output.js.map": {
|
||||
"file": "dist/output.js.map"
|
||||
},
|
||||
"main.js": {
|
||||
"file": "dist/output.js"
|
||||
},
|
||||
"async_js.js.map": {
|
||||
"file": "dist/async_js.d6fc644e617b14425795.js.map"
|
||||
},
|
||||
"async_js.js": {
|
||||
"file": "dist/async_js.d6fc644e617b14425795.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# dist/manifest.yml
|
||||
|
||||
```yml
|
||||
- output.js.map: 'dist/output.js.map'
|
||||
- main.js: 'dist/output.js'
|
||||
- bar.txt: 'dist/a0145fafc7fab801e574631452de554b.txt'
|
||||
- foo.txt: 'dist/3ee037f347c64cc372ad.txt'
|
||||
- 1.output.js.map: 'dist/1.output.js.map'
|
||||
- 1.output.js: 'dist/1.output.js'
|
||||
entrypoints:
|
||||
main:
|
||||
imports:
|
||||
- main.js
|
||||
assets:
|
||||
foo.txt:
|
||||
file: /nested/dist/3ee037f347c64cc372ad18857b0db91f.txt
|
||||
src: foo.txt
|
||||
bar.txt:
|
||||
file: /nested/dist/a0145fafc7fab801e574.txt
|
||||
src: bar.txt
|
||||
main.js:
|
||||
file: /nested/dist/output.js
|
||||
async_js.js:
|
||||
file: /nested/dist/async_js.d6fc644e617b14425795.js
|
||||
custom: value
|
||||
```
|
||||
|
||||
# Collecting all initial scripts and styles
|
||||
|
||||
Here is a function to be able to get all initial scripts and styles:
|
||||
|
||||
```js
|
||||
const fs = require("fs");
|
||||
|
||||
function importEntrypoints(manifest, name) {
|
||||
const seen = new Set();
|
||||
|
||||
function getImported(entrypoint) {
|
||||
const scripts = [];
|
||||
const styles = [];
|
||||
|
||||
for (const item of entrypoint.imports) {
|
||||
const importer = manifest.assets[item];
|
||||
|
||||
if (seen.has(item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
seen.add(item);
|
||||
|
||||
for (const parent of entrypoint.parents || []) {
|
||||
const [parentStyles, parentScripts] = getImported(manifest.entrypoints[parent])
|
||||
styles.push(...parentStyles);
|
||||
scripts.push(...parentScripts);
|
||||
}
|
||||
|
||||
if (/\.css$/.test(importer.file)) {
|
||||
styles.push(importer.file);
|
||||
} else {
|
||||
scripts.push(importer.file);
|
||||
}
|
||||
}
|
||||
|
||||
return [styles, scripts];
|
||||
}
|
||||
|
||||
return getImported(manifest.entrypoints[name]);
|
||||
}
|
||||
|
||||
const manifest = JSON.parser(fs.readFilsSync("./manifest.json", "utf8"));
|
||||
|
||||
// Get all styles and scripts by entry name
|
||||
const [styles, scripts] = importEntrypoints(manifest, "main");
|
||||
```
|
||||
|
||||
# Info
|
||||
|
|
@ -97,52 +178,51 @@ module.exports = {
|
|||
## Unoptimized
|
||||
|
||||
```
|
||||
assets by path *.js 11.9 KiB
|
||||
asset output.js 9.61 KiB [emitted] (name: main) 1 related asset
|
||||
asset 1.output.js 2.3 KiB [emitted] 1 related asset
|
||||
assets by path *.txt 8 bytes
|
||||
asset 3ee037f347c64cc372ad.txt 4 bytes [emitted] [immutable] [from: foo.txt]
|
||||
asset a0145fafc7fab801e574631452de554b.txt 4 bytes [emitted] [immutable] [from: bar.txt]
|
||||
asset manifest.json 260 bytes [emitted]
|
||||
asset manifest.yml 240 bytes [emitted]
|
||||
chunk (runtime: main) output.js (main) 17 bytes (javascript) 5.48 KiB (runtime) [entry] [rendered]
|
||||
> ./example.js main
|
||||
runtime modules 5.48 KiB 8 modules
|
||||
./example.js 17 bytes [built] [code generated]
|
||||
[used exports unknown]
|
||||
entry ./example.js main
|
||||
chunk (runtime: main) 1.output.js 207 bytes (javascript) 4 bytes (asset) [rendered]
|
||||
> ./baz ./example.js 1:0-15
|
||||
dependent modules 122 bytes (javascript) 4 bytes (asset) [dependent] 2 modules
|
||||
./baz.js 85 bytes [built] [code generated]
|
||||
assets by info 893 bytes [immutable]
|
||||
asset async_js.d6fc644e617b14425795.js 885 bytes [emitted] [immutable] 1 related asset
|
||||
asset 3ee037f347c64cc372ad18857b0db91f.txt 4 bytes [emitted] [immutable] [from: foo.txt] (auxiliary name: main)
|
||||
asset a0145fafc7fab801e574.txt 4 bytes [emitted] [immutable] [from: bar.txt] (auxiliary name: main)
|
||||
asset output.js 15.2 KiB [emitted] (name: main) 1 related asset
|
||||
asset manifest.json 601 bytes [emitted]
|
||||
asset manifest.yml 395 bytes [emitted]
|
||||
chunk (runtime: main) async_js.d6fc644e617b14425795.js 24 bytes [rendered]
|
||||
> ./async.js ./example.js 6:8-28
|
||||
./async.js 24 bytes [built] [code generated]
|
||||
[exports: default]
|
||||
[used exports unknown]
|
||||
import() ./baz ./example.js 1:0-15
|
||||
import() ./async.js ./example.js 6:8-28
|
||||
chunk (runtime: main) output.js (main) 325 bytes (javascript) 4 bytes (asset) 7.67 KiB (runtime) [entry] [rendered]
|
||||
> ./example.js main
|
||||
runtime modules 7.67 KiB 9 modules
|
||||
dependent modules 4 bytes (asset) 122 bytes (javascript) [dependent] 2 modules
|
||||
./example.js 203 bytes [built] [code generated]
|
||||
[exports: default]
|
||||
[used exports unknown]
|
||||
entry ./example.js main
|
||||
webpack X.X.X compiled successfully
|
||||
```
|
||||
|
||||
## Production mode
|
||||
|
||||
```
|
||||
assets by path *.js 2.17 KiB
|
||||
asset output.js 1.94 KiB [emitted] [minimized] (name: main) 1 related asset
|
||||
asset 293.output.js 237 bytes [emitted] [minimized] 1 related asset
|
||||
assets by path *.txt 8 bytes
|
||||
asset 3ee037f347c64cc372ad.txt 4 bytes [emitted] [immutable] [from: foo.txt]
|
||||
asset a0145fafc7fab801e574631452de554b.txt 4 bytes [emitted] [immutable] [from: bar.txt]
|
||||
asset manifest.json 268 bytes [emitted]
|
||||
asset manifest.yml 248 bytes [emitted]
|
||||
chunk (runtime: main) 293.output.js 4 bytes (asset) 249 bytes (javascript) [rendered]
|
||||
> ./baz ./example.js 1:0-15
|
||||
./baz.js + 2 modules 207 bytes [built] [code generated]
|
||||
assets by info 205 bytes [immutable]
|
||||
asset async_js.e737f493d3cb4089ee2e.js 197 bytes [emitted] [immutable] [minimized] 1 related asset
|
||||
asset 3ee037f347c64cc372ad18857b0db91f.txt 4 bytes [emitted] [immutable] [from: foo.txt] (auxiliary name: main)
|
||||
asset a0145fafc7fab801e574.txt 4 bytes [emitted] [immutable] [from: bar.txt] (auxiliary name: main)
|
||||
asset output.js 3.18 KiB [emitted] [minimized] (name: main) 1 related asset
|
||||
asset manifest.json 601 bytes [emitted]
|
||||
asset manifest.yml 395 bytes [emitted]
|
||||
chunk (runtime: main) async_js.e737f493d3cb4089ee2e.js 24 bytes [rendered]
|
||||
> ./async.js ./example.js 6:8-28
|
||||
./async.js 24 bytes [built] [code generated]
|
||||
[exports: default]
|
||||
import() ./baz ./example.js 1:0-15
|
||||
./foo.txt 4 bytes (asset) 42 bytes (javascript) [built] [code generated]
|
||||
[no exports]
|
||||
chunk (runtime: main) output.js (main) 17 bytes (javascript) 5.48 KiB (runtime) [entry] [rendered]
|
||||
import() ./async.js ./example.js 6:8-28
|
||||
chunk (runtime: main) output.js (main) 325 bytes (javascript) 4 bytes (asset) 7.67 KiB (runtime) [entry] [rendered]
|
||||
> ./example.js main
|
||||
runtime modules 5.48 KiB 8 modules
|
||||
./example.js 17 bytes [built] [code generated]
|
||||
runtime modules 7.67 KiB 9 modules
|
||||
dependent modules 4 bytes (asset) 122 bytes (javascript) [dependent] 2 modules
|
||||
./example.js 203 bytes [built] [code generated]
|
||||
[exports: default]
|
||||
[no exports used]
|
||||
entry ./example.js main
|
||||
webpack X.X.X compiled successfully
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export default "value";
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import foo from "./foo.txt";
|
||||
import bar from "./bar.txt";
|
||||
|
||||
export default foo + bar;
|
||||
|
|
@ -1 +1,11 @@
|
|||
import("./baz");
|
||||
import fooURL from "./foo.txt";
|
||||
|
||||
const barURL = new URL("./bar.txt", import.meta.url);
|
||||
|
||||
async function loadAsync() {
|
||||
return import("./async.js");
|
||||
}
|
||||
|
||||
await loadAsync();
|
||||
|
||||
export default [fooURL, barURL];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
This example demonstrates how to use webpack internal ManifestPlugin.
|
||||
This example demonstrates how to use ManifestPlugin.
|
||||
|
||||
# example.js
|
||||
|
||||
|
|
@ -6,24 +6,6 @@ This example demonstrates how to use webpack internal ManifestPlugin.
|
|||
_{{example.js}}_
|
||||
```
|
||||
|
||||
# foo.txt
|
||||
|
||||
```js
|
||||
_{{foo.txt}}_
|
||||
```
|
||||
|
||||
# bar.txt
|
||||
|
||||
```js
|
||||
_{{bar.txt}}_
|
||||
```
|
||||
|
||||
# baz.js
|
||||
|
||||
```js
|
||||
_{{baz.js}}_
|
||||
```
|
||||
|
||||
# webpack.config.js
|
||||
|
||||
```javascript
|
||||
|
|
@ -42,6 +24,54 @@ _{{dist/manifest.json}}_
|
|||
_{{dist/manifest.yml}}_
|
||||
```
|
||||
|
||||
# Collecting all initial scripts and styles
|
||||
|
||||
Here is a function to be able to get all initial scripts and styles:
|
||||
|
||||
```js
|
||||
const fs = require("fs");
|
||||
|
||||
function importEntrypoints(manifest, name) {
|
||||
const seen = new Set();
|
||||
|
||||
function getImported(entrypoint) {
|
||||
const scripts = [];
|
||||
const styles = [];
|
||||
|
||||
for (const item of entrypoint.imports) {
|
||||
const importer = manifest.assets[item];
|
||||
|
||||
if (seen.has(item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
seen.add(item);
|
||||
|
||||
for (const parent of entrypoint.parents || []) {
|
||||
const [parentStyles, parentScripts] = getImported(manifest.entrypoints[parent])
|
||||
styles.push(...parentStyles);
|
||||
scripts.push(...parentScripts);
|
||||
}
|
||||
|
||||
if (/\.css$/.test(importer.file)) {
|
||||
styles.push(importer.file);
|
||||
} else {
|
||||
scripts.push(importer.file);
|
||||
}
|
||||
}
|
||||
|
||||
return [styles, scripts];
|
||||
}
|
||||
|
||||
return getImported(manifest.entrypoints[name]);
|
||||
}
|
||||
|
||||
const manifest = JSON.parser(fs.readFilsSync("./manifest.json", "utf8"));
|
||||
|
||||
// Get all styles and scripts by entry name
|
||||
const [styles, scripts] = importEntrypoints(manifest, "main");
|
||||
```
|
||||
|
||||
# Info
|
||||
|
||||
## Unoptimized
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
"use strict";
|
||||
|
||||
const YAML = require("yamljs");
|
||||
const webpack = require("../../");
|
||||
|
||||
/** @type {webpack.Configuration} */
|
||||
module.exports = {
|
||||
devtool: "source-map",
|
||||
output: {
|
||||
chunkFilename: "[name].[contenthash].js"
|
||||
},
|
||||
optimization: {
|
||||
chunkIds: "named" // To keep filename consistent between different modes (for example building only)
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /foo.txt/,
|
||||
type: "asset/resource"
|
||||
},
|
||||
{
|
||||
test: /bar.txt/,
|
||||
use: require.resolve("file-loader")
|
||||
}
|
||||
]
|
||||
|
|
@ -23,13 +26,21 @@ module.exports = {
|
|||
}),
|
||||
new webpack.ManifestPlugin({
|
||||
filename: "manifest.yml",
|
||||
handler(manifest) {
|
||||
let _manifest = "";
|
||||
for (const key in manifest) {
|
||||
if (key === "manifest.json") continue;
|
||||
_manifest += `- ${key}: '${manifest[key].filePath}'\n`;
|
||||
prefix: "/nested/[publicpath]",
|
||||
filter(item) {
|
||||
if (/.map$/.test(item.file)) {
|
||||
return false;
|
||||
}
|
||||
return _manifest;
|
||||
|
||||
return true;
|
||||
},
|
||||
generate(manifest) {
|
||||
delete manifest.assets["manifest.json"];
|
||||
manifest.custom = "value";
|
||||
return manifest;
|
||||
},
|
||||
serialize(manifest) {
|
||||
return YAML.stringify(manifest, 4);
|
||||
}
|
||||
})
|
||||
]
|
||||
|
|
|
|||
|
|
@ -296,6 +296,7 @@ const { isSourceEqual } = require("./util/source");
|
|||
* @property {boolean=} development true, when asset is only used for development and doesn't count towards user-facing assets
|
||||
* @property {boolean=} hotModuleReplacement true, when asset ships data for updating an existing application (HMR)
|
||||
* @property {boolean=} javascriptModule true, when asset is javascript and an ESM
|
||||
* @property {boolean=} manifest true, when file is a manifest
|
||||
* @property {Record<string, null | string | string[]>=} related object of pointers to other assets, keyed by type of relation (only points from parent to child)
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -5,22 +5,21 @@
|
|||
|
||||
"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("..").StatsCompilation} StatsCompilation */
|
||||
/** @typedef {import("./Chunk")} Chunk */
|
||||
/** @typedef {import("./Chunk").ChunkName} ChunkName */
|
||||
/** @typedef {import("./Chunk").ChunkId} ChunkId */
|
||||
/** @typedef {import("./Compilation").Asset} Asset */
|
||||
/** @typedef {import("./Module")} Module */
|
||||
/** @typedef {import("./NormalModule")} NormalModule */
|
||||
/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
|
||||
/** @typedef {import("./Compilation").AssetInfo} AssetInfo */
|
||||
|
||||
/** @typedef {import("../declarations/plugins/ManifestPlugin").ManifestPluginOptions} ManifestPluginOptions */
|
||||
/** @typedef {import("../declarations/plugins/ManifestPlugin").ManifestObject} ManifestObject */
|
||||
/** @typedef {import("../declarations/plugins/ManifestPlugin").ManifestEntrypoint} ManifestEntrypoint */
|
||||
/** @typedef {import("../declarations/plugins/ManifestPlugin").ManifestItem} ManifestItem */
|
||||
|
||||
const PLUGIN_NAME = "ManifestPlugin";
|
||||
|
|
@ -53,29 +52,16 @@ class ManifestPlugin {
|
|||
constructor(options) {
|
||||
validate(options);
|
||||
|
||||
/** @type {Required<ManifestPluginOptions>} */
|
||||
/** @type {ManifestPluginOptions & Required<Omit<ManifestPluginOptions, "filter" | "generate">>} */
|
||||
this.options = {
|
||||
filename: "manifest.json",
|
||||
handler: (manifest) => this._handleManifest(manifest),
|
||||
prefix: "[publicpath]",
|
||||
entrypoints: true,
|
||||
serialize: (manifest) => JSON.stringify(manifest, null, 2),
|
||||
...options
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ManifestObject} manifest manifest object
|
||||
* @returns {string} manifest content
|
||||
*/
|
||||
_handleManifest(manifest) {
|
||||
return JSON.stringify(
|
||||
Object.keys(manifest).reduce((acc, cur) => {
|
||||
acc[cur] = manifest[cur].filePath;
|
||||
return acc;
|
||||
}, /** @type {Record<string, string>} */ ({})),
|
||||
null,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the plugin
|
||||
* @param {Compiler} compiler the compiler instance
|
||||
|
|
@ -89,83 +75,156 @@ class ManifestPlugin {
|
|||
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE
|
||||
},
|
||||
() => {
|
||||
const assets = compilation.getAssets();
|
||||
const hashDigestLength = compilation.outputOptions.hashDigestLength;
|
||||
const publicPath = compilation.getPath(
|
||||
compilation.outputOptions.publicPath
|
||||
);
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const added = new Set();
|
||||
/** @type {ManifestObject} */
|
||||
const manifest = {};
|
||||
/**
|
||||
* @param {string | string[]} value value
|
||||
* @returns {RegExp} regexp to remove hash
|
||||
*/
|
||||
const createHashRegExp = (value) =>
|
||||
new RegExp(
|
||||
`(?:\\.${Array.isArray(value) ? `(${value.join("|")})` : value})(?=\\.)`,
|
||||
"gi"
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {string} name name
|
||||
* @param {AssetInfo | null} info asset info
|
||||
* @returns {string} hash removed name
|
||||
*/
|
||||
const removeHash = (name) => {
|
||||
const removeHash = (name, info) => {
|
||||
// Handles hashes that match configured `hashDigestLength`
|
||||
// i.e. index.XXXX.html -> index.html (html-webpack-plugin)
|
||||
if (hashDigestLength <= 0) return name;
|
||||
const reg = new RegExp(
|
||||
`(\\.[a-f0-9]{${hashDigestLength},32})(?=\\.)`,
|
||||
"gi"
|
||||
);
|
||||
const reg = createHashRegExp(`[a-f0-9]{${hashDigestLength},32}`);
|
||||
return name.replace(reg, "");
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Chunk} chunk chunk
|
||||
* @returns {ChunkName | ChunkId} chunk name or chunk id
|
||||
*/
|
||||
const getName = (chunk) => {
|
||||
if (chunk.name) return chunk.name;
|
||||
|
||||
return chunk.id;
|
||||
};
|
||||
|
||||
/** @type {ManifestObject} */
|
||||
let manifest = {};
|
||||
|
||||
if (this.options.entrypoints) {
|
||||
/** @type {ManifestObject["entrypoints"]} */
|
||||
const entrypoints = {};
|
||||
|
||||
for (const [name, entrypoint] of compilation.entrypoints) {
|
||||
const imports = [];
|
||||
|
||||
for (const chunk of entrypoint.chunks) {
|
||||
for (const file of chunk.files) {
|
||||
const name = getName(chunk);
|
||||
|
||||
imports.push(name ? `${name}.${extname(file)}` : file);
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {ManifestEntrypoint} */
|
||||
const item = { imports };
|
||||
const parents = entrypoint
|
||||
.getParents()
|
||||
.map((item) => /** @type {string} */ (item.name));
|
||||
|
||||
if (parents.length > 0) {
|
||||
item.parents = parents;
|
||||
}
|
||||
|
||||
entrypoints[name] = item;
|
||||
}
|
||||
|
||||
manifest.entrypoints = entrypoints;
|
||||
}
|
||||
|
||||
/** @type {ManifestObject["assets"]} */
|
||||
const assets = {};
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const added = new Set();
|
||||
|
||||
/**
|
||||
* @param {string} file file
|
||||
* @param {((file: string) => string)=} namer namer
|
||||
* @param {string=} usedName usedName
|
||||
* @returns {void}
|
||||
*/
|
||||
const handleFile = (file, namer) => {
|
||||
const handleFile = (file, usedName) => {
|
||||
if (added.has(file)) return;
|
||||
added.add(file);
|
||||
|
||||
let name = namer ? namer(file) : file;
|
||||
const asset = compilation.getAsset(file);
|
||||
if (asset && asset.info.sourceFilename) {
|
||||
name = path.join(
|
||||
path.dirname(file),
|
||||
path.basename(asset.info.sourceFilename)
|
||||
);
|
||||
if (!asset) return;
|
||||
const sourceFilename = asset.info.sourceFilename;
|
||||
const name =
|
||||
usedName ||
|
||||
sourceFilename ||
|
||||
// Fallback for unofficial plugins, just remove hash from filename
|
||||
removeHash(file, asset.info);
|
||||
|
||||
const prefix = this.options.prefix.replace(
|
||||
/\[publicpath\]/gi,
|
||||
() => (publicPath === "auto" ? "/" : publicPath)
|
||||
);
|
||||
/** @type {ManifestItem} */
|
||||
const item = { file: prefix + file };
|
||||
|
||||
if (sourceFilename) {
|
||||
item.src = sourceFilename;
|
||||
}
|
||||
manifest[removeHash(name)] = {
|
||||
filePath: publicPath
|
||||
? publicPath +
|
||||
(publicPath.endsWith("/") ? `${file}` : `/${file}`)
|
||||
: file,
|
||||
asset
|
||||
};
|
||||
|
||||
if (this.options.filter) {
|
||||
const needKeep = this.options.filter(item);
|
||||
|
||||
if (!needKeep) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
assets[name] = item;
|
||||
};
|
||||
|
||||
for (const chunk of compilation.chunks) {
|
||||
if (chunk instanceof HotUpdateChunk) continue;
|
||||
|
||||
const chunkName = chunk.name;
|
||||
for (const auxiliaryFile of chunk.auxiliaryFiles) {
|
||||
handleFile(auxiliaryFile, (file) => path.basename(file));
|
||||
handleFile(auxiliaryFile);
|
||||
}
|
||||
|
||||
const name = getName(chunk);
|
||||
|
||||
for (const file of chunk.files) {
|
||||
handleFile(file, (file) => {
|
||||
if (chunkName) return `${chunkName}.${extname(file)}`;
|
||||
return file;
|
||||
});
|
||||
handleFile(file, name ? `${name}.${extname(file)}` : file);
|
||||
}
|
||||
}
|
||||
|
||||
for (const asset of assets) {
|
||||
for (const asset of compilation.getAssets()) {
|
||||
if (asset.info.hotModuleReplacement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
handleFile(asset.name);
|
||||
}
|
||||
|
||||
manifest.assets = assets;
|
||||
|
||||
if (this.options.generate) {
|
||||
manifest = this.options.generate(manifest);
|
||||
}
|
||||
|
||||
compilation.emitAsset(
|
||||
this.options.filename,
|
||||
new RawSource(this.options.handler(manifest))
|
||||
new RawSource(this.options.serialize(manifest)),
|
||||
{ manifest: true }
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@
|
|||
* DO NOT MODIFY BY HAND.
|
||||
* Run `yarn fix:special` to update
|
||||
*/
|
||||
const r=/^(?:[A-Za-z]:[\\/]|\\\\|\/)/;function e(t,{instancePath:n="",parentData:a,parentDataProperty:s,rootData:o=t}={}){let i=null,l=0;if(0===l){if(!t||"object"!=typeof t||Array.isArray(t))return e.errors=[{params:{type:"object"}}],!1;{const n=l;for(const r in t)if("filename"!==r&&"handler"!==r)return e.errors=[{params:{additionalProperty:r}}],!1;if(n===l){if(void 0!==t.filename){let n=t.filename;const a=l;if(l===a){if("string"!=typeof n)return e.errors=[{params:{type:"string"}}],!1;if(n.includes("!")||!1!==r.test(n))return e.errors=[{params:{}}],!1;if(n.length<1)return e.errors=[{params:{}}],!1}var f=a===l}else f=!0;if(f)if(void 0!==t.handler){const r=l,n=l;let a=!1,s=null;const o=l;if(!(t.handler instanceof Function)){const r={params:{}};null===i?i=[r]:i.push(r),l++}if(o===l&&(a=!0,s=0),!a){const r={params:{passingSchemas:s}};return null===i?i=[r]:i.push(r),l++,e.errors=i,!1}l=n,null!==i&&(n?i.length=n:i=null),f=r===l}else f=!0}}}return e.errors=i,0===l}module.exports=e,module.exports.default=e;
|
||||
const r=/^(?:[A-Za-z]:[\\/]|\\\\|\/)/;function e(t,{instancePath:i="",parentData:n,parentDataProperty:o,rootData:s=t}={}){if(!t||"object"!=typeof t||Array.isArray(t))return e.errors=[{params:{type:"object"}}],!1;{const i=0;for(const r in t)if("entrypoints"!==r&&"filename"!==r&&"filter"!==r&&"generate"!==r&&"prefix"!==r&&"serialize"!==r)return e.errors=[{params:{additionalProperty:r}}],!1;if(0===i){if(void 0!==t.entrypoints){const r=0;if("boolean"!=typeof t.entrypoints)return e.errors=[{params:{type:"boolean"}}],!1;var a=0===r}else a=!0;if(a){if(void 0!==t.filename){let i=t.filename;const n=0;if(0===n){if("string"!=typeof i)return e.errors=[{params:{type:"string"}}],!1;if(i.includes("!")||!1!==r.test(i))return e.errors=[{params:{}}],!1;if(i.length<1)return e.errors=[{params:{}}],!1}a=0===n}else a=!0;if(a){if(void 0!==t.filter){const r=0;if(!(t.filter instanceof Function))return e.errors=[{params:{}}],!1;a=0===r}else a=!0;if(a){if(void 0!==t.generate){const r=0;if(!(t.generate instanceof Function))return e.errors=[{params:{}}],!1;a=0===r}else a=!0;if(a){if(void 0!==t.prefix){const r=0;if("string"!=typeof t.prefix)return e.errors=[{params:{type:"string"}}],!1;a=0===r}else a=!0;if(a)if(void 0!==t.serialize){const r=0;if(!(t.serialize instanceof Function))return e.errors=[{params:{}}],!1;a=0===r}else a=!0}}}}}}return e.errors=null,!0}module.exports=e,module.exports.default=e;
|
||||
|
|
@ -1,51 +1,98 @@
|
|||
{
|
||||
"definitions": {
|
||||
"HandlerFunction": {
|
||||
"description": "A function that receives the manifest object and returns the manifest string.",
|
||||
"instanceof": "Function",
|
||||
"tsType": "(manifest: ManifestObject) => string"
|
||||
},
|
||||
"ManifestItem": {
|
||||
"description": "Describes a manifest entry that links the emitted path to the producing asset.",
|
||||
"ManifestEntrypoint": {
|
||||
"description": "Describes a manifest entrypoint.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"asset": {
|
||||
"description": "The compilation asset that produced this manifest entry.",
|
||||
"tsType": "import('../../lib/Compilation').Asset"
|
||||
"imports": {
|
||||
"description": "Contains the names of entrypoints.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"description": "The name of file.",
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"filePath": {
|
||||
"description": "The public path recorded in the manifest for this asset.",
|
||||
"parents": {
|
||||
"description": "Contains the names of parent entrypoints.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"description": "The entrypoint name.",
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["imports"]
|
||||
},
|
||||
"ManifestItem": {
|
||||
"description": "Describes a manifest asset that links the emitted path to the producing asset.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"file": {
|
||||
"description": "The path absolute URL (this indicates that the path is absolute from the server's root directory) to file.",
|
||||
"type": "string"
|
||||
},
|
||||
"src": {
|
||||
"description": "The source path relative to the context.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["filePath"]
|
||||
"required": ["file"]
|
||||
},
|
||||
"ManifestObject": {
|
||||
"description": "Maps asset identifiers to their manifest entries.",
|
||||
"description": "The manifest object.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/ManifestItem"
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"assets": {
|
||||
"description": "Contains the names of assets.",
|
||||
"type": "object",
|
||||
"tsType": "Record<string, ManifestItem>"
|
||||
},
|
||||
"entrypoints": {
|
||||
"description": "Contains the names of entrypoints.",
|
||||
"type": "object",
|
||||
"tsType": "Record<string, ManifestEntrypoint>"
|
||||
}
|
||||
},
|
||||
"tsType": "Record<string, ManifestItem>"
|
||||
"required": ["assets", "entrypoints"]
|
||||
}
|
||||
},
|
||||
"title": "ManifestPluginOptions",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"entrypoints": {
|
||||
"description": "Enables/disables generation of the entrypoints manifest section.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"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",
|
||||
"absolutePath": false,
|
||||
"minLength": 1
|
||||
},
|
||||
"handler": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/HandlerFunction"
|
||||
}
|
||||
]
|
||||
"filter": {
|
||||
"description": "Allows filtering the files which make up the manifest.",
|
||||
"instanceof": "Function",
|
||||
"tsType": "(item: ManifestItem) => boolean"
|
||||
},
|
||||
"generate": {
|
||||
"description": "A function that receives the manifest object, modifies it, and returns the modified manifest.",
|
||||
"instanceof": "Function",
|
||||
"tsType": "(manifest: ManifestObject) => ManifestObject"
|
||||
},
|
||||
"prefix": {
|
||||
"description": "Specifies a path prefix for all keys in the manifest.",
|
||||
"type": "string"
|
||||
},
|
||||
"serialize": {
|
||||
"description": "A function that receives the manifest object and returns the manifest string.",
|
||||
"instanceof": "Function",
|
||||
"tsType": "(manifest: ManifestObject) => string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
function doSomething() {}
|
||||
|
||||
doSomething();
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import fs from "fs";
|
||||
import path from "path";
|
||||
import url from "../../asset-modules/_images/file.png";
|
||||
|
||||
it("should emit manifest with expected entries and paths with string publicPath", async () => {
|
||||
await import(/* webpackChunkName: 'file' */ "./file.txt?foo");
|
||||
await import("./module.js");
|
||||
|
||||
const manifest = JSON.parse(
|
||||
fs.readFileSync(path.resolve(__dirname, "foo.json"), "utf-8")
|
||||
);
|
||||
const { entrypoints, assets } = manifest;
|
||||
|
||||
expect(entrypoints).toStrictEqual({ main: { imports: ["main.js"] } });
|
||||
expect(Object.keys(assets).sort()).toEqual(
|
||||
[
|
||||
"main.js",
|
||||
"module_js.js",
|
||||
"file.js",
|
||||
"file.txt?foo",
|
||||
"third.party.js",
|
||||
"../../asset-modules/_images/file.png"
|
||||
].sort()
|
||||
);
|
||||
expect(assets["main.js"].file).toBe("/app/bundle0.js");
|
||||
expect(assets["module_js.js"].file).toMatch(/\/app\/module_js\.[a-f0-9]+\.js/);
|
||||
expect(assets["third.party.js"].file).toBe("/app/third.party.js");
|
||||
expect(assets["../../asset-modules/_images/file.png"].file).toBe("/app/file-loader.png");
|
||||
expect(assets["../../asset-modules/_images/file.png"].src).toBeDefined();
|
||||
expect(assets["file.txt?foo"].file).toMatch(/\/app\/file\.[a-f0-9]+\.txt\?foo/);
|
||||
expect(assets["file.txt?foo"].src).toBeDefined();
|
||||
});
|
||||
|
|
@ -1,30 +1,115 @@
|
|||
import fs from "fs";
|
||||
import path from "path";
|
||||
import "./style.css";
|
||||
import "./dependency.js";
|
||||
import url from "../../asset-modules/_images/file.png";
|
||||
|
||||
import(/* webpackChunkName: 'file' */ "./file.txt?foo");
|
||||
|
||||
it("should emit manifest with expected entries and paths with function publicPath", () => {
|
||||
expect(url).toEqual("/dist/file-loader.png");
|
||||
new URL("./file.txt", import.meta.url);
|
||||
new URL("./public/other.txt", import.meta.url);
|
||||
|
||||
const manifest = JSON.parse(
|
||||
fs.readFileSync(path.resolve(__dirname, "bar.json"), "utf-8")
|
||||
);
|
||||
function importEntrypoints(manifest, name) {
|
||||
const seen = new Set();
|
||||
|
||||
const keys = Object.keys(manifest).sort();
|
||||
expect(keys).toEqual(
|
||||
function getImportedChunks(entrypoint) {
|
||||
const scripts = [];
|
||||
const styles = [];
|
||||
|
||||
for (const item of entrypoint.imports) {
|
||||
const importee = manifest.assets[item];
|
||||
|
||||
if (seen.has(item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
seen.add(item);
|
||||
|
||||
for (const parent of entrypoint.parents || []) {
|
||||
const [parentStyles, parentScripts] = getImportedChunks(manifest.entrypoints[parent])
|
||||
styles.push(...parentStyles);
|
||||
scripts.push(...parentScripts);
|
||||
}
|
||||
|
||||
if (/\.css$/.test(importee.file)) {
|
||||
styles.push(importee.file);
|
||||
} else {
|
||||
scripts.push(importee.file);
|
||||
}
|
||||
}
|
||||
|
||||
return [styles, scripts];
|
||||
}
|
||||
|
||||
return getImportedChunks(manifest.entrypoints[name]);
|
||||
}
|
||||
|
||||
it("should emit manifest with expected entries and paths with function publicPath", async () => {
|
||||
// await import(/* webpackName: "my-name" */ "./nested/module.js?foo=bar#hash");
|
||||
|
||||
const manifest = __non_webpack_require__("./other.json");
|
||||
const { custom, entrypoints, assets } = manifest;
|
||||
|
||||
expect(custom).toBe("value");
|
||||
expect(entrypoints).toEqual({
|
||||
"nested-shared": {
|
||||
"imports": [
|
||||
"runtime~nested-shared.js",
|
||||
"nested-shared.js"
|
||||
]
|
||||
},
|
||||
"shared": {
|
||||
"imports": [
|
||||
"shared.js",
|
||||
"shared.css"
|
||||
],
|
||||
"parents": [
|
||||
"nested-shared"
|
||||
]
|
||||
},
|
||||
"foo": {
|
||||
"imports": [
|
||||
"commons-dependency_js.js",
|
||||
"foo.js",
|
||||
"foo.css"
|
||||
],
|
||||
"parents": [
|
||||
"shared"
|
||||
]
|
||||
}
|
||||
});
|
||||
expect(importEntrypoints(manifest, "foo")).toEqual([
|
||||
["/nested/shared.css", "/nested/foo.css"],
|
||||
[
|
||||
"/nested/runtime~nested-shared.js",
|
||||
"/nested/nested-shared.js",
|
||||
"/nested/shared.js",
|
||||
"/nested/commons-dependency_js.js",
|
||||
"/nested/foo.js"
|
||||
],
|
||||
]);
|
||||
expect(Object.keys(assets).sort()).toEqual(
|
||||
[
|
||||
"commons-dependency_js.js",
|
||||
"commons-dependency_js.js.map",
|
||||
"file.txt",
|
||||
"foo.js",
|
||||
"foo.js.map",
|
||||
"file.js",
|
||||
"file.txt?foo",
|
||||
"main.js",
|
||||
"third.party.js",
|
||||
"file.png"
|
||||
"nested-shared.js",
|
||||
"nested-shared.js.map",
|
||||
"runtime~nested-shared.js",
|
||||
"runtime~nested-shared.js.map",
|
||||
"shared.css",
|
||||
"shared.css.map",
|
||||
"foo.css",
|
||||
"foo.css.map",
|
||||
"shared.js",
|
||||
"shared.js.map",
|
||||
"public/other.txt",
|
||||
"third.party.js"
|
||||
].sort()
|
||||
);
|
||||
|
||||
expect(manifest["main.js"]).toMatch(/\/dist\/bundle1\.js/);
|
||||
expect(manifest["file.js"]).toMatch(/\/dist\/file\.[a-f0-9]+\.js/);
|
||||
expect(manifest["file.txt?foo"]).toMatch(/\/dist\/file\.[a-f0-9]+\.txt\?foo/);
|
||||
expect(manifest["third.party.js"]).toBe("/dist/third.party.js");
|
||||
expect(manifest["file.png"]).toBe("/dist/file-loader.png");
|
||||
expect(assets["foo.js"].file).toBe("/nested/foo.js");
|
||||
expect(assets["file.txt?foo"].file).toMatch(/\/nested\/file\.[a-f0-9]+\.txt\?foo/);
|
||||
expect(assets["file.txt?foo"].src).toBeDefined();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
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 with string publicPath", () => {
|
||||
expect(url).toEqual("/app/file-loader.png");
|
||||
|
||||
const manifest = JSON.parse(
|
||||
fs.readFileSync(path.resolve(__dirname, "foo.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 @@
|
|||
export default "dyn";
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
function doSomething() {}
|
||||
|
||||
doSomething();
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
function doNothing() {}
|
||||
|
||||
doNothing();
|
||||
|
|
@ -0,0 +1 @@
|
|||
file
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
body {
|
||||
color: red;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import "./shared.css";
|
||||
|
||||
function doSomething() {}
|
||||
|
||||
doSomething();
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
body {
|
||||
background: red;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
findBundle(i) {
|
||||
if (i === 0) {
|
||||
return ["bundle0.js"];
|
||||
}
|
||||
|
||||
return [
|
||||
"runtime~nested-shared.js",
|
||||
"nested-shared.js",
|
||||
"shared.js",
|
||||
"commons-dependency_js.js",
|
||||
"foo.js"
|
||||
];
|
||||
}
|
||||
};
|
||||
|
|
@ -29,15 +29,15 @@ class CopyPlugin {
|
|||
/** @type {import("../../../../").Configuration[]} */
|
||||
module.exports = [
|
||||
{
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false
|
||||
},
|
||||
entry: "./index-1.js",
|
||||
output: {
|
||||
publicPath: "/app/",
|
||||
publicPath: () => "/app/",
|
||||
chunkFilename: "[name].[contenthash].js",
|
||||
assetModuleFilename: "[name].[contenthash][ext][query]"
|
||||
},
|
||||
optimization: {
|
||||
chunkIds: "named"
|
||||
},
|
||||
plugins: [
|
||||
new CopyPlugin(),
|
||||
new webpack.ManifestPlugin({
|
||||
|
|
@ -61,22 +61,28 @@ module.exports = [
|
|||
}
|
||||
},
|
||||
{
|
||||
entry: "./index-2.js",
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false
|
||||
target: "web",
|
||||
entry: {
|
||||
"nested-shared": {
|
||||
import: "./nested-shared.js"
|
||||
},
|
||||
shared: {
|
||||
dependOn: "nested-shared",
|
||||
import: "./shared.js?foo=bar"
|
||||
},
|
||||
foo: {
|
||||
dependOn: "shared",
|
||||
import: "./index-2.js"
|
||||
}
|
||||
},
|
||||
output: {
|
||||
publicPath: (_data) => "/dist/",
|
||||
publicPath: "auto",
|
||||
filename: "[name].js",
|
||||
chunkFilename: "[name].[contenthash].js",
|
||||
cssChunkFilename: "[name].[contenthash].css",
|
||||
assetModuleFilename: "[name].[contenthash][ext][query]"
|
||||
},
|
||||
plugins: [
|
||||
new CopyPlugin(),
|
||||
new webpack.ManifestPlugin({
|
||||
filename: "bar.json"
|
||||
})
|
||||
],
|
||||
devtool: "source-map",
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
|
|
@ -91,6 +97,41 @@ module.exports = [
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new CopyPlugin(),
|
||||
new webpack.ManifestPlugin({
|
||||
filename: "other.json",
|
||||
filter(item) {
|
||||
if (/file-loader.png/.test(item.file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
generate: (manifest) => {
|
||||
manifest.custom = "value";
|
||||
return manifest;
|
||||
},
|
||||
prefix: "/nested[publicpath]",
|
||||
serialize: (manifest) => JSON.stringify(manifest)
|
||||
})
|
||||
],
|
||||
experiments: {
|
||||
css: true
|
||||
},
|
||||
optimization: {
|
||||
chunkIds: "named",
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
commons: {
|
||||
enforce: true,
|
||||
test: /dependency\.js$/,
|
||||
chunks: "initial"
|
||||
}
|
||||
}
|
||||
},
|
||||
runtimeChunk: { name: (entrypoint) => `runtime~${entrypoint.name}` }
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -8671,6 +8671,11 @@ declare interface KnownAssetInfo {
|
|||
*/
|
||||
javascriptModule?: boolean;
|
||||
|
||||
/**
|
||||
* true, when file is a manifest
|
||||
*/
|
||||
manifest?: boolean;
|
||||
|
||||
/**
|
||||
* object of pointers to other assets, keyed by type of relation (only points from parent to child)
|
||||
*/
|
||||
|
|
@ -9977,25 +9982,55 @@ declare interface MakeDirectoryOptions {
|
|||
}
|
||||
|
||||
/**
|
||||
* Describes a manifest entry that links the emitted path to the producing asset.
|
||||
* Describes a manifest entrypoint.
|
||||
*/
|
||||
declare interface ManifestEntrypoint {
|
||||
/**
|
||||
* Contains the names of entrypoints.
|
||||
*/
|
||||
imports: string[];
|
||||
|
||||
/**
|
||||
* Contains the names of parent entrypoints.
|
||||
*/
|
||||
parents?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a manifest asset that links the emitted path to the producing asset.
|
||||
*/
|
||||
declare interface ManifestItem {
|
||||
/**
|
||||
* The compilation asset that produced this manifest entry.
|
||||
* The path absolute URL (this indicates that the path is absolute from the server's root directory) to file.
|
||||
*/
|
||||
asset?: Asset;
|
||||
file: string;
|
||||
|
||||
/**
|
||||
* The public path recorded in the manifest for this asset.
|
||||
* The source path relative to the context.
|
||||
*/
|
||||
filePath: string;
|
||||
src?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The manifest object.
|
||||
*/
|
||||
declare interface ManifestObject {
|
||||
[index: string]: ManifestItem;
|
||||
[index: string]: any;
|
||||
|
||||
/**
|
||||
* Contains the names of assets.
|
||||
*/
|
||||
assets: Record<string, ManifestItem>;
|
||||
|
||||
/**
|
||||
* Contains the names of entrypoints.
|
||||
*/
|
||||
entrypoints: Record<string, ManifestEntrypoint>;
|
||||
}
|
||||
declare class ManifestPlugin {
|
||||
constructor(options: ManifestPluginOptions);
|
||||
options: Required<ManifestPluginOptions>;
|
||||
options: ManifestPluginOptions &
|
||||
Required<Omit<ManifestPluginOptions, "filter" | "generate">>;
|
||||
|
||||
/**
|
||||
* Apply the plugin
|
||||
|
|
@ -10003,15 +10038,35 @@ declare class ManifestPlugin {
|
|||
apply(compiler: Compiler): void;
|
||||
}
|
||||
declare interface ManifestPluginOptions {
|
||||
/**
|
||||
* Enables/disables generation of the entrypoints manifest section.
|
||||
*/
|
||||
entrypoints?: boolean;
|
||||
|
||||
/**
|
||||
* Specifies the filename of the output file on disk. By default the plugin will emit `manifest.json` inside the 'output.path' directory.
|
||||
*/
|
||||
filename?: string;
|
||||
|
||||
/**
|
||||
* Allows filtering the files which make up the manifest.
|
||||
*/
|
||||
filter?: (item: ManifestItem) => boolean;
|
||||
|
||||
/**
|
||||
* A function that receives the manifest object, modifies it, and returns the modified manifest.
|
||||
*/
|
||||
generate?: (manifest: ManifestObject) => ManifestObject;
|
||||
|
||||
/**
|
||||
* Specifies a path prefix for all keys in the manifest.
|
||||
*/
|
||||
prefix?: string;
|
||||
|
||||
/**
|
||||
* A function that receives the manifest object and returns the manifest string.
|
||||
*/
|
||||
handler?: (manifest: ManifestObject) => string;
|
||||
serialize?: (manifest: ManifestObject) => string;
|
||||
}
|
||||
declare interface MapOptions {
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue