@rushstack/webpack5-module-minifier-plugin 5.6.14 → 5.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +38 -0
- package/CHANGELOG.md +15 -1
- package/dist/tsdoc-metadata.json +1 -1
- package/dist/webpack5-module-minifier-plugin.d.ts +27 -0
- package/lib-commonjs/Constants.js +59 -0
- package/lib-commonjs/Constants.js.map +1 -0
- package/{lib → lib-commonjs}/ModuleMinifierPlugin.js +64 -6
- package/lib-commonjs/ModuleMinifierPlugin.js.map +1 -0
- package/lib-commonjs/ModuleMinifierPlugin.types.js.map +1 -0
- package/{lib → lib-commonjs}/RehydrateAsset.js +14 -1
- package/lib-commonjs/RehydrateAsset.js.map +1 -0
- package/{lib → lib-commonjs}/index.js +3 -1
- package/lib-commonjs/index.js.map +1 -0
- package/lib-dts/Constants.d.ts +54 -0
- package/lib-dts/Constants.d.ts.map +1 -0
- package/{lib → lib-dts}/ModuleMinifierPlugin.d.ts.map +1 -1
- package/{lib → lib-dts}/ModuleMinifierPlugin.types.d.ts +4 -0
- package/lib-dts/ModuleMinifierPlugin.types.d.ts.map +1 -0
- package/{lib → lib-dts}/RehydrateAsset.d.ts.map +1 -1
- package/{lib → lib-dts}/index.d.ts +1 -1
- package/lib-dts/index.d.ts.map +1 -0
- package/lib-esm/Constants.js +56 -0
- package/lib-esm/Constants.js.map +1 -0
- package/lib-esm/GenerateLicenseFileForAsset.js +47 -0
- package/lib-esm/GenerateLicenseFileForAsset.js.map +1 -0
- package/lib-esm/ModuleMinifierPlugin.js +458 -0
- package/lib-esm/ModuleMinifierPlugin.js.map +1 -0
- package/lib-esm/ModuleMinifierPlugin.types.js +4 -0
- package/lib-esm/ModuleMinifierPlugin.types.js.map +1 -0
- package/lib-esm/RehydrateAsset.js +80 -0
- package/lib-esm/RehydrateAsset.js.map +1 -0
- package/lib-esm/index.js +6 -0
- package/lib-esm/index.js.map +1 -0
- package/package.json +28 -7
- package/lib/Constants.d.ts +0 -33
- package/lib/Constants.d.ts.map +0 -1
- package/lib/Constants.js +0 -38
- package/lib/Constants.js.map +0 -1
- package/lib/ModuleMinifierPlugin.js.map +0 -1
- package/lib/ModuleMinifierPlugin.types.d.ts.map +0 -1
- package/lib/ModuleMinifierPlugin.types.js.map +0 -1
- package/lib/RehydrateAsset.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- /package/{lib → lib-commonjs}/GenerateLicenseFileForAsset.js +0 -0
- /package/{lib → lib-commonjs}/GenerateLicenseFileForAsset.js.map +0 -0
- /package/{lib → lib-commonjs}/ModuleMinifierPlugin.types.js +0 -0
- /package/{lib → lib-dts}/GenerateLicenseFileForAsset.d.ts +0 -0
- /package/{lib → lib-dts}/GenerateLicenseFileForAsset.d.ts.map +0 -0
- /package/{lib → lib-dts}/ModuleMinifierPlugin.d.ts +0 -0
- /package/{lib → lib-dts}/RehydrateAsset.d.ts +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
|
2
|
+
// See LICENSE in the project root for license information.
|
|
3
|
+
/**
|
|
4
|
+
* Prefix to wrap `function (module, __webpack_exports__, __webpack_require__) { ... }` so that the minifier doesn't delete it.
|
|
5
|
+
* Public because alternate Minifier implementations may wish to know about it.
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export const MODULE_WRAPPER_PREFIX = '__MINIFY_MODULE__(';
|
|
9
|
+
/**
|
|
10
|
+
* Suffix to wrap `function (module, __webpack_exports__, __webpack_require__) { ... }` so that the minifier doesn't delete it.
|
|
11
|
+
* Public because alternate Minifier implementations may wish to know about it.
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export const MODULE_WRAPPER_SUFFIX = ');';
|
|
15
|
+
/**
|
|
16
|
+
* Prefix to wrap ECMAScript method shorthand `(module, __webpack_exports__, __webpack_require__) { ... }` so that the minifier doesn't delete it.
|
|
17
|
+
* Used when webpack emits modules using shorthand syntax.
|
|
18
|
+
* Combined with the suffix, creates: `__MINIFY_MODULE__({__DEFAULT_ID__(params){body}});`
|
|
19
|
+
* Public because alternate Minifier implementations may wish to know about it.
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
export const MODULE_WRAPPER_SHORTHAND_PREFIX = `${MODULE_WRAPPER_PREFIX}{__DEFAULT_ID__`;
|
|
23
|
+
/**
|
|
24
|
+
* Suffix to wrap ECMAScript method shorthand `(module, __webpack_exports__, __webpack_require__) { ... }` so that the minifier doesn't delete it.
|
|
25
|
+
* Used when webpack emits modules using shorthand syntax.
|
|
26
|
+
* Combined with the prefix, creates: `__MINIFY_MODULE__({__DEFAULT_ID__(params){body}});`
|
|
27
|
+
* Public because alternate Minifier implementations may wish to know about it.
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export const MODULE_WRAPPER_SHORTHAND_SUFFIX = `}${MODULE_WRAPPER_SUFFIX}`;
|
|
31
|
+
/**
|
|
32
|
+
* Token preceding a module id in the emitted asset so the minifier can operate on the Webpack runtime or chunk boilerplate in isolation
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
export const CHUNK_MODULE_TOKEN = '__WEBPACK_CHUNK_MODULE__';
|
|
36
|
+
/**
|
|
37
|
+
* RegExp for replacing chunk module placeholders
|
|
38
|
+
* Handles three possible representations:
|
|
39
|
+
* - `"id":__WEBPACK_CHUNK_MODULE__HASH__` (methodShorthand: false, object)
|
|
40
|
+
* - `__WEBPACK_CHUNK_MODULE__HASH__` (array syntax)
|
|
41
|
+
* - `"id":__WEBPACK_CHUNK_MODULE__HASH__` with leading ':' (methodShorthand: true, object)
|
|
42
|
+
* Captures optional leading `:` to handle shorthand format properly
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
export const CHUNK_MODULE_REGEX = /(:?)__WEBPACK_CHUNK_MODULE__([A-Za-z0-9$_]+)/g;
|
|
46
|
+
/**
|
|
47
|
+
* Stage # to use when this should be the first tap in the hook
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
export const STAGE_BEFORE = -10000;
|
|
51
|
+
/**
|
|
52
|
+
* Stage # to use when this should be the last tap in the hook
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
export const STAGE_AFTER = 100;
|
|
56
|
+
//# sourceMappingURL=Constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Constants.js","sourceRoot":"","sources":["../src/Constants.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAyB,oBAAoB,CAAC;AAChF;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAS,IAAI,CAAC;AAEhD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAqD,GAAG,qBAAqB,iBAAiB,CAAC;AAC3I;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAuC,IAAI,qBAAqB,EAAE,CAAC;AAE/G;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA+B,0BAA0B,CAAC;AAEzF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAW,+CAA+C,CAAC;AAE1F;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAW,CAAC,KAAK,CAAC;AAC3C;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAQ,GAAG,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\n/**\n * Prefix to wrap `function (module, __webpack_exports__, __webpack_require__) { ... }` so that the minifier doesn't delete it.\n * Public because alternate Minifier implementations may wish to know about it.\n * @public\n */\nexport const MODULE_WRAPPER_PREFIX: '__MINIFY_MODULE__(' = '__MINIFY_MODULE__(';\n/**\n * Suffix to wrap `function (module, __webpack_exports__, __webpack_require__) { ... }` so that the minifier doesn't delete it.\n * Public because alternate Minifier implementations may wish to know about it.\n * @public\n */\nexport const MODULE_WRAPPER_SUFFIX: ');' = ');';\n\n/**\n * Prefix to wrap ECMAScript method shorthand `(module, __webpack_exports__, __webpack_require__) { ... }` so that the minifier doesn't delete it.\n * Used when webpack emits modules using shorthand syntax.\n * Combined with the suffix, creates: `__MINIFY_MODULE__({__DEFAULT_ID__(params){body}});`\n * Public because alternate Minifier implementations may wish to know about it.\n * @public\n */\nexport const MODULE_WRAPPER_SHORTHAND_PREFIX: `${typeof MODULE_WRAPPER_PREFIX}{__DEFAULT_ID__` = `${MODULE_WRAPPER_PREFIX}{__DEFAULT_ID__`;\n/**\n * Suffix to wrap ECMAScript method shorthand `(module, __webpack_exports__, __webpack_require__) { ... }` so that the minifier doesn't delete it.\n * Used when webpack emits modules using shorthand syntax.\n * Combined with the prefix, creates: `__MINIFY_MODULE__({__DEFAULT_ID__(params){body}});`\n * Public because alternate Minifier implementations may wish to know about it.\n * @public\n */\nexport const MODULE_WRAPPER_SHORTHAND_SUFFIX: `}${typeof MODULE_WRAPPER_SUFFIX}` = `}${MODULE_WRAPPER_SUFFIX}`;\n\n/**\n * Token preceding a module id in the emitted asset so the minifier can operate on the Webpack runtime or chunk boilerplate in isolation\n * @public\n */\nexport const CHUNK_MODULE_TOKEN: '__WEBPACK_CHUNK_MODULE__' = '__WEBPACK_CHUNK_MODULE__';\n\n/**\n * RegExp for replacing chunk module placeholders\n * Handles three possible representations:\n * - `\"id\":__WEBPACK_CHUNK_MODULE__HASH__` (methodShorthand: false, object)\n * - `__WEBPACK_CHUNK_MODULE__HASH__` (array syntax)\n * - `\"id\":__WEBPACK_CHUNK_MODULE__HASH__` with leading ':' (methodShorthand: true, object)\n * Captures optional leading `:` to handle shorthand format properly\n * @public\n */\nexport const CHUNK_MODULE_REGEX: RegExp = /(:?)__WEBPACK_CHUNK_MODULE__([A-Za-z0-9$_]+)/g;\n\n/**\n * Stage # to use when this should be the first tap in the hook\n * @public\n */\nexport const STAGE_BEFORE: -10000 = -10000;\n/**\n * Stage # to use when this should be the last tap in the hook\n * @public\n */\nexport const STAGE_AFTER: 100 = 100;\n"]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
|
2
|
+
// See LICENSE in the project root for license information.
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
function getAllComments(modules) {
|
|
5
|
+
var _a;
|
|
6
|
+
const allComments = new Set();
|
|
7
|
+
for (const webpackModule of modules) {
|
|
8
|
+
const submodules = (webpackModule.context === null &&
|
|
9
|
+
webpackModule._modules) || [webpackModule];
|
|
10
|
+
for (const submodule of submodules) {
|
|
11
|
+
const subModuleComments = (_a = submodule.factoryMeta) === null || _a === void 0 ? void 0 : _a.comments;
|
|
12
|
+
if (subModuleComments) {
|
|
13
|
+
for (const comment of subModuleComments) {
|
|
14
|
+
const value = comment.type === 'Line' ? `//${comment.value}\n` : `/*${comment.value}*/\n`;
|
|
15
|
+
allComments.add(value);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return allComments;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Generates a companion asset containing all extracted comments. If it is non-empty, returns a banner comment directing users to said companion asset.
|
|
24
|
+
*
|
|
25
|
+
* @param compilation - The webpack compilation
|
|
26
|
+
* @param asset - The asset to process
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
export function generateLicenseFileForAsset(compilation, asset) {
|
|
30
|
+
// Extracted comments from the modules.
|
|
31
|
+
const modules = compilation.chunkGraph.getChunkModulesIterable(asset.chunk);
|
|
32
|
+
const comments = getAllComments(modules);
|
|
33
|
+
const assetName = asset.fileName;
|
|
34
|
+
let banner = '';
|
|
35
|
+
if (comments.size) {
|
|
36
|
+
// There are license comments in this chunk, so generate the companion file and inject a banner
|
|
37
|
+
const licenseSource = new compilation.compiler.webpack.sources.ConcatSource();
|
|
38
|
+
comments.forEach((comment) => {
|
|
39
|
+
licenseSource.add(comment);
|
|
40
|
+
});
|
|
41
|
+
const licenseFileName = `${assetName}.LICENSE.txt`;
|
|
42
|
+
compilation.emitAsset(licenseFileName, licenseSource);
|
|
43
|
+
banner = `/*! For license information please see ${path.basename(licenseFileName)} */\n`;
|
|
44
|
+
}
|
|
45
|
+
return banner;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=GenerateLicenseFileForAsset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GenerateLicenseFileForAsset.js","sourceRoot":"","sources":["../src/GenerateLicenseFileForAsset.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAOlC,SAAS,cAAc,CAAC,OAAyB;;IAC/C,MAAM,WAAW,GAAgB,IAAI,GAAG,EAAE,CAAC;IAE3C,KAAK,MAAM,aAAa,IAAI,OAAO,EAAE,CAAC;QACpC,MAAM,UAAU,GAAqB,CAAC,aAAa,CAAC,OAAO,KAAK,IAAI;YACjE,aAAiD,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAClF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,MAAM,iBAAiB,GAAkC,MACvD,SAAS,CAAC,WAGX,0CAAE,QAAQ,CAAC;YAEZ,IAAI,iBAAiB,EAAE,CAAC;gBACtB,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;oBACxC,MAAM,KAAK,GAAW,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,KAAK,MAAM,CAAC;oBAClG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B,CAAC,WAAwB,EAAE,KAAiB;IACrF,uCAAuC;IACvC,MAAM,OAAO,GAAqB,WAAW,CAAC,UAAU,CAAC,uBAAuB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC9F,MAAM,QAAQ,GAAgB,cAAc,CAAC,OAAO,CAAC,CAAC;IAEtD,MAAM,SAAS,GAAW,KAAK,CAAC,QAAQ,CAAC;IAEzC,IAAI,MAAM,GAAW,EAAE,CAAC;IAExB,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;QAClB,+FAA+F;QAC/F,MAAM,aAAa,GAAyB,IAAI,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;QACpG,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC3B,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QACH,MAAM,eAAe,GAAW,GAAG,SAAS,cAAc,CAAC;QAC3D,WAAW,CAAC,SAAS,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;QACtD,MAAM,GAAG,0CAA0C,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,OAAO,CAAC;IAC3F,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport * as path from 'node:path';\n\nimport type { Comment } from 'estree';\nimport type { Compilation, Module, sources } from 'webpack';\n\nimport type { IAssetInfo } from './ModuleMinifierPlugin.types';\n\nfunction getAllComments(modules: Iterable<Module>): Set<string> {\n const allComments: Set<string> = new Set();\n\n for (const webpackModule of modules) {\n const submodules: Iterable<Module> = (webpackModule.context === null &&\n (webpackModule as { _modules?: Iterable<Module> })._modules) || [webpackModule];\n for (const submodule of submodules) {\n const subModuleComments: Iterable<Comment> | undefined = (\n submodule.factoryMeta as {\n comments?: Iterable<Comment>;\n }\n )?.comments;\n\n if (subModuleComments) {\n for (const comment of subModuleComments) {\n const value: string = comment.type === 'Line' ? `//${comment.value}\\n` : `/*${comment.value}*/\\n`;\n allComments.add(value);\n }\n }\n }\n }\n\n return allComments;\n}\n\n/**\n * Generates a companion asset containing all extracted comments. If it is non-empty, returns a banner comment directing users to said companion asset.\n *\n * @param compilation - The webpack compilation\n * @param asset - The asset to process\n * @public\n */\nexport function generateLicenseFileForAsset(compilation: Compilation, asset: IAssetInfo): string {\n // Extracted comments from the modules.\n const modules: Iterable<Module> = compilation.chunkGraph.getChunkModulesIterable(asset.chunk);\n const comments: Set<string> = getAllComments(modules);\n\n const assetName: string = asset.fileName;\n\n let banner: string = '';\n\n if (comments.size) {\n // There are license comments in this chunk, so generate the companion file and inject a banner\n const licenseSource: sources.ConcatSource = new compilation.compiler.webpack.sources.ConcatSource();\n comments.forEach((comment) => {\n licenseSource.add(comment);\n });\n const licenseFileName: string = `${assetName}.LICENSE.txt`;\n compilation.emitAsset(licenseFileName, licenseSource);\n banner = `/*! For license information please see ${path.basename(licenseFileName)} */\\n`;\n }\n\n return banner;\n}\n"]}
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
|
2
|
+
// See LICENSE in the project root for license information.
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { AsyncSeriesWaterfallHook, SyncWaterfallHook } from 'tapable';
|
|
5
|
+
import { getIdentifier } from '@rushstack/module-minifier';
|
|
6
|
+
import { CHUNK_MODULE_TOKEN, MODULE_WRAPPER_PREFIX, MODULE_WRAPPER_SUFFIX, MODULE_WRAPPER_SHORTHAND_PREFIX, MODULE_WRAPPER_SHORTHAND_SUFFIX, STAGE_BEFORE, STAGE_AFTER } from './Constants';
|
|
7
|
+
import { generateLicenseFileForAsset } from './GenerateLicenseFileForAsset';
|
|
8
|
+
import { rehydrateAsset } from './RehydrateAsset';
|
|
9
|
+
// The name of the plugin, for use in taps
|
|
10
|
+
const PLUGIN_NAME = 'ModuleMinifierPlugin';
|
|
11
|
+
// Monotonically increasing identifier to be incremented any time the code generation logic changes
|
|
12
|
+
// Will be applied to the webpack hash.
|
|
13
|
+
const CODE_GENERATION_REVISION = 1;
|
|
14
|
+
// Match behavior of terser's "some" option
|
|
15
|
+
// https://github.com/terser/terser/blob/d3d924fa9e4c57bbe286b811c6068bcc7026e902/lib/output.js#L175
|
|
16
|
+
const LICENSE_COMMENT_REGEX = /@preserve|@lic|@cc_on|^\**!/i;
|
|
17
|
+
const TAP_BEFORE = {
|
|
18
|
+
name: PLUGIN_NAME,
|
|
19
|
+
stage: STAGE_BEFORE
|
|
20
|
+
};
|
|
21
|
+
const TAP_AFTER = {
|
|
22
|
+
name: PLUGIN_NAME,
|
|
23
|
+
stage: STAGE_AFTER
|
|
24
|
+
};
|
|
25
|
+
const compilationMetadataMap = new WeakMap();
|
|
26
|
+
function hashCodeFragment(code) {
|
|
27
|
+
return createHash('sha256').update(code).digest('hex');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Base implementation of asset rehydration
|
|
31
|
+
*
|
|
32
|
+
* @param dehydratedAssets The dehydrated assets
|
|
33
|
+
* @param compilation The webpack compilation
|
|
34
|
+
*/
|
|
35
|
+
function defaultRehydrateAssets(dehydratedAssets, compilation) {
|
|
36
|
+
const { assets, modules } = dehydratedAssets;
|
|
37
|
+
const compilationMetadata = compilationMetadataMap.get(compilation);
|
|
38
|
+
if (!compilationMetadata) {
|
|
39
|
+
throw new Error(`Could not get compilation metadata`);
|
|
40
|
+
}
|
|
41
|
+
const { metadataByAssetFileName } = compilationMetadata;
|
|
42
|
+
// Now assets/modules contain fully minified code. Rehydrate.
|
|
43
|
+
for (const [assetName, info] of assets) {
|
|
44
|
+
const banner = info.type === 'javascript' ? generateLicenseFileForAsset(compilation, info) : '';
|
|
45
|
+
const replacementSource = rehydrateAsset(compilation, info, modules, banner, true);
|
|
46
|
+
metadataByAssetFileName.set(assetName, {
|
|
47
|
+
positionByModuleId: info.renderInfo
|
|
48
|
+
});
|
|
49
|
+
compilation.updateAsset(assetName, replacementSource);
|
|
50
|
+
}
|
|
51
|
+
return dehydratedAssets;
|
|
52
|
+
}
|
|
53
|
+
function isMinificationResultError(result) {
|
|
54
|
+
return !!result.error;
|
|
55
|
+
}
|
|
56
|
+
function isLicenseComment(comment) {
|
|
57
|
+
return LICENSE_COMMENT_REGEX.test(comment.value);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* RegExp for detecting function keyword with optional whitespace
|
|
61
|
+
*/
|
|
62
|
+
const FUNCTION_KEYWORD_REGEX = /function\s*\(/;
|
|
63
|
+
/**
|
|
64
|
+
* Detects if the module code uses ECMAScript method shorthand format.
|
|
65
|
+
* Shorthand format would appear when webpack emits object methods without function keyword
|
|
66
|
+
* For example: `id(params) { body }` instead of `id: function(params) { body }`
|
|
67
|
+
*
|
|
68
|
+
* Following the problem statement's recommendation: inspect the rendered code prior to the first `{`
|
|
69
|
+
* and look for either a `=>` or `function(`. If neither are encountered, assume object shorthand format.
|
|
70
|
+
*
|
|
71
|
+
* @param code - The module source code to check
|
|
72
|
+
* @returns true if the code is in method shorthand format
|
|
73
|
+
*/
|
|
74
|
+
function isMethodShorthandFormat(code) {
|
|
75
|
+
// Find the position of the first opening brace
|
|
76
|
+
const firstBraceIndex = code.indexOf('{');
|
|
77
|
+
if (firstBraceIndex < 0) {
|
|
78
|
+
// No brace found, not a function format
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
// Get the code before the first brace
|
|
82
|
+
const beforeBrace = code.slice(0, firstBraceIndex);
|
|
83
|
+
// Check if it contains '=>' or 'function('
|
|
84
|
+
// If it does, it's a regular arrow function or function expression, not shorthand
|
|
85
|
+
// Use a simple check that handles common whitespace variations
|
|
86
|
+
if (beforeBrace.includes('=>') || FUNCTION_KEYWORD_REGEX.test(beforeBrace)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
// If neither '=>' nor 'function(' are found, assume object method shorthand format
|
|
90
|
+
// ECMAScript method shorthand is used in object literals: { methodName(params){body} }
|
|
91
|
+
// Webpack emits this as just (params){body} which only works in the object literal context
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Webpack plugin that minifies code on a per-module basis rather than per-asset. The actual minification is handled by the input `minifier` object.
|
|
96
|
+
* @public
|
|
97
|
+
*/
|
|
98
|
+
export class ModuleMinifierPlugin {
|
|
99
|
+
constructor(options) {
|
|
100
|
+
this.hooks = {
|
|
101
|
+
rehydrateAssets: new AsyncSeriesWaterfallHook(['dehydratedContent', 'compilation']),
|
|
102
|
+
postProcessCodeFragment: new SyncWaterfallHook(['code', 'context'])
|
|
103
|
+
};
|
|
104
|
+
const { minifier, sourceMap } = options;
|
|
105
|
+
this._optionsForHash = {
|
|
106
|
+
...options,
|
|
107
|
+
minifier: undefined,
|
|
108
|
+
revision: CODE_GENERATION_REVISION
|
|
109
|
+
};
|
|
110
|
+
this._enhancers = [];
|
|
111
|
+
this.hooks.rehydrateAssets.tap(PLUGIN_NAME, defaultRehydrateAssets);
|
|
112
|
+
this.minifier = minifier;
|
|
113
|
+
this._sourceMap = sourceMap;
|
|
114
|
+
}
|
|
115
|
+
static getCompilationStatistics(compilation) {
|
|
116
|
+
return compilationMetadataMap.get(compilation);
|
|
117
|
+
}
|
|
118
|
+
apply(compiler) {
|
|
119
|
+
for (const enhancer of this._enhancers) {
|
|
120
|
+
enhancer.apply(compiler);
|
|
121
|
+
}
|
|
122
|
+
const { options: { devtool, mode }, webpack } = compiler;
|
|
123
|
+
webpack.Template.numberToIdentifier = getIdentifier;
|
|
124
|
+
const { CachedSource, ConcatSource, RawSource, ReplaceSource, SourceMapSource } = webpack.sources;
|
|
125
|
+
// The explicit setting is preferred due to accuracy, but try to guess based on devtool
|
|
126
|
+
const useSourceMaps = typeof this._sourceMap === 'boolean'
|
|
127
|
+
? this._sourceMap
|
|
128
|
+
: typeof devtool === 'string'
|
|
129
|
+
? devtool.endsWith('source-map')
|
|
130
|
+
: mode === 'production' && devtool !== false;
|
|
131
|
+
this._optionsForHash.sourceMap = useSourceMaps;
|
|
132
|
+
const binaryConfig = Buffer.from(JSON.stringify(this._optionsForHash), 'utf-8');
|
|
133
|
+
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation, compilationData) => {
|
|
134
|
+
const { normalModuleFactory } = compilationData;
|
|
135
|
+
function addCommentExtraction(parser) {
|
|
136
|
+
parser.hooks.program.tap(PLUGIN_NAME, (program, comments) => {
|
|
137
|
+
const relevantComments = comments.filter(isLicenseComment);
|
|
138
|
+
if (comments.length > 0) {
|
|
139
|
+
// Webpack's typings now restrict the properties on factoryMeta for unknown reasons
|
|
140
|
+
const module = parser.state.module;
|
|
141
|
+
if (!module.factoryMeta) {
|
|
142
|
+
module.factoryMeta = {
|
|
143
|
+
comments: relevantComments
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
module.factoryMeta.comments = relevantComments;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
normalModuleFactory.hooks.parser.for('javascript/auto').tap(PLUGIN_NAME, addCommentExtraction);
|
|
153
|
+
normalModuleFactory.hooks.parser.for('javascript/dynamic').tap(PLUGIN_NAME, addCommentExtraction);
|
|
154
|
+
normalModuleFactory.hooks.parser.for('javascript/esm').tap(PLUGIN_NAME, addCommentExtraction);
|
|
155
|
+
/**
|
|
156
|
+
* Set of local module ids that have been processed.
|
|
157
|
+
*/
|
|
158
|
+
const submittedModules = new Set();
|
|
159
|
+
/**
|
|
160
|
+
* The text and comments of all minified modules.
|
|
161
|
+
*/
|
|
162
|
+
const minifiedModules = new Map();
|
|
163
|
+
/**
|
|
164
|
+
* The text and comments of all minified chunks. Most of these are trivial, but the runtime chunk is a bit larger.
|
|
165
|
+
*/
|
|
166
|
+
const minifiedAssets = new Map();
|
|
167
|
+
const metadataByModule = new WeakMap();
|
|
168
|
+
const metadataByAssetFileName = new Map();
|
|
169
|
+
const compilationStatistics = {
|
|
170
|
+
metadataByModule,
|
|
171
|
+
metadataByAssetFileName
|
|
172
|
+
};
|
|
173
|
+
compilationMetadataMap.set(compilation, compilationStatistics);
|
|
174
|
+
function getOrCreateMetadata(mod) {
|
|
175
|
+
let moduleStats = metadataByModule.get(mod);
|
|
176
|
+
if (!moduleStats) {
|
|
177
|
+
moduleStats = {
|
|
178
|
+
hashByChunk: new Map(),
|
|
179
|
+
sizeByHash: new Map()
|
|
180
|
+
};
|
|
181
|
+
metadataByModule.set(mod, moduleStats);
|
|
182
|
+
}
|
|
183
|
+
return moduleStats;
|
|
184
|
+
}
|
|
185
|
+
let pendingMinificationRequests = 0;
|
|
186
|
+
/**
|
|
187
|
+
* Indicates that all files have been sent to the minifier and therefore that when pending hits 0, assets can be rehydrated.
|
|
188
|
+
*/
|
|
189
|
+
let allRequestsIssued = false;
|
|
190
|
+
let resolveMinifyPromise;
|
|
191
|
+
const postProcessCode = (code, context) => this.hooks.postProcessCodeFragment.call(code, context);
|
|
192
|
+
/**
|
|
193
|
+
* Callback to invoke when a file has finished minifying.
|
|
194
|
+
*/
|
|
195
|
+
function onFileMinified() {
|
|
196
|
+
if (--pendingMinificationRequests === 0 && allRequestsIssued) {
|
|
197
|
+
resolveMinifyPromise();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const { minifier } = this;
|
|
201
|
+
let minifierConnection;
|
|
202
|
+
// Typings for this object are not exposed
|
|
203
|
+
// eslint-disable-next-line @typescript-eslint/typedef
|
|
204
|
+
const javascriptHooks = webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(compilation);
|
|
205
|
+
/**
|
|
206
|
+
* The minifier needs to know if the module was wrapped in a factory function, because
|
|
207
|
+
* function (module, exports, require) { // <implementation> }
|
|
208
|
+
* minifies to nothing. Unfortunately we can't tell by inspection if the output was wrapped or not.
|
|
209
|
+
* However, the JavaScriptModulesPlugin invokes three hooks in order when rendering a module:
|
|
210
|
+
* 1) renderModuleContent - Invoked for every module.
|
|
211
|
+
* 2) renderModuleContainer - Invoked when wrapping a module in a factory.
|
|
212
|
+
* 3) renderModulePackage - Invoked for every module as the last hook.
|
|
213
|
+
*/
|
|
214
|
+
let nextModule;
|
|
215
|
+
const sourceCache = new WeakMap();
|
|
216
|
+
javascriptHooks.renderModuleContent.tap(TAP_AFTER, (source) => {
|
|
217
|
+
// Clear the identification state of the current module.
|
|
218
|
+
nextModule = undefined;
|
|
219
|
+
return source;
|
|
220
|
+
});
|
|
221
|
+
javascriptHooks.renderModuleContainer.tap(TAP_AFTER, (source, mod) => {
|
|
222
|
+
// Module is being wrapped in a factory, so it is safe for per-module minification
|
|
223
|
+
// Leave external modules in-place to avoid needing special handling for externals
|
|
224
|
+
if (mod.context !== null || !mod.externalType) {
|
|
225
|
+
nextModule = mod;
|
|
226
|
+
}
|
|
227
|
+
return source;
|
|
228
|
+
});
|
|
229
|
+
javascriptHooks.renderModulePackage.tap(TAP_AFTER,
|
|
230
|
+
/**
|
|
231
|
+
* Extracts the code for the module and sends it to be minified.
|
|
232
|
+
*/
|
|
233
|
+
function minifyModule(source, mod, chunkRenderContext) {
|
|
234
|
+
if (nextModule !== mod) {
|
|
235
|
+
// This module is being inlined. Abandon per-module minification.
|
|
236
|
+
return source;
|
|
237
|
+
}
|
|
238
|
+
const id = compilation.chunkGraph.getModuleId(mod);
|
|
239
|
+
if (id === null) {
|
|
240
|
+
// This module has no id. Abandon per-module minification.
|
|
241
|
+
return source;
|
|
242
|
+
}
|
|
243
|
+
const metadata = getOrCreateMetadata(mod);
|
|
244
|
+
const cachedResult = sourceCache.get(source);
|
|
245
|
+
if (cachedResult) {
|
|
246
|
+
metadata.hashByChunk.set(chunkRenderContext.chunk, cachedResult.hash);
|
|
247
|
+
return cachedResult.source;
|
|
248
|
+
}
|
|
249
|
+
// Get the source code to check its format
|
|
250
|
+
const sourceCode = source.source().toString();
|
|
251
|
+
// Detect if this is ECMAScript method shorthand format
|
|
252
|
+
const isShorthand = isMethodShorthandFormat(sourceCode);
|
|
253
|
+
// If this module is wrapped in a factory, need to add boilerplate so that the minifier keeps the function
|
|
254
|
+
const wrapped = isShorthand
|
|
255
|
+
? new ConcatSource(MODULE_WRAPPER_SHORTHAND_PREFIX, source, MODULE_WRAPPER_SHORTHAND_SUFFIX)
|
|
256
|
+
: new ConcatSource(MODULE_WRAPPER_PREFIX + '\n', source, '\n' + MODULE_WRAPPER_SUFFIX);
|
|
257
|
+
const nameForMap = `(modules)/${id}`;
|
|
258
|
+
const { source: wrappedCodeRaw, map } = useSourceMaps
|
|
259
|
+
? wrapped.sourceAndMap()
|
|
260
|
+
: {
|
|
261
|
+
source: wrapped.source(),
|
|
262
|
+
map: undefined
|
|
263
|
+
};
|
|
264
|
+
const wrappedCode = wrappedCodeRaw.toString();
|
|
265
|
+
const hash = hashCodeFragment(wrappedCode);
|
|
266
|
+
metadata.hashByChunk.set(chunkRenderContext.chunk, hash);
|
|
267
|
+
if (!submittedModules.has(hash)) {
|
|
268
|
+
submittedModules.add(hash);
|
|
269
|
+
++pendingMinificationRequests;
|
|
270
|
+
minifier.minify({
|
|
271
|
+
hash,
|
|
272
|
+
code: wrappedCode,
|
|
273
|
+
nameForMap: useSourceMaps ? nameForMap : undefined,
|
|
274
|
+
externals: undefined
|
|
275
|
+
}, (result) => {
|
|
276
|
+
if (isMinificationResultError(result)) {
|
|
277
|
+
compilation.errors.push(result.error);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
try {
|
|
281
|
+
const { code: minified, map: minifierMap } = result;
|
|
282
|
+
const rawOutput = useSourceMaps
|
|
283
|
+
? new SourceMapSource(minified, // Code
|
|
284
|
+
nameForMap, // File
|
|
285
|
+
minifierMap, // Base source map
|
|
286
|
+
wrappedCode, // Source from before transform
|
|
287
|
+
map, // Source Map from before transform
|
|
288
|
+
true // Remove original source
|
|
289
|
+
)
|
|
290
|
+
: new RawSource(minified);
|
|
291
|
+
const unwrapped = new ReplaceSource(rawOutput);
|
|
292
|
+
const len = minified.length;
|
|
293
|
+
// Trim off the boilerplate used to preserve the factory
|
|
294
|
+
// Use different prefix/suffix lengths for shorthand vs regular format
|
|
295
|
+
// Capture isShorthand from closure instead of looking it up
|
|
296
|
+
if (isShorthand) {
|
|
297
|
+
// For shorthand format: __MINIFY_MODULE__({__DEFAULT_ID__(args){...}});
|
|
298
|
+
// Remove prefix and suffix by their lengths
|
|
299
|
+
unwrapped.replace(0, MODULE_WRAPPER_SHORTHAND_PREFIX.length - 1, '');
|
|
300
|
+
unwrapped.replace(len - MODULE_WRAPPER_SHORTHAND_SUFFIX.length, len - 1, '');
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
// Regular format: __MINIFY_MODULE__(function(args){...}); or __MINIFY_MODULE__((args)=>{...});
|
|
304
|
+
unwrapped.replace(0, MODULE_WRAPPER_PREFIX.length - 1, '');
|
|
305
|
+
unwrapped.replace(len - MODULE_WRAPPER_SUFFIX.length, len - 1, '');
|
|
306
|
+
}
|
|
307
|
+
const withIds = postProcessCode(unwrapped, {
|
|
308
|
+
compilation,
|
|
309
|
+
module: mod,
|
|
310
|
+
loggingName: mod.identifier()
|
|
311
|
+
});
|
|
312
|
+
const cached = new CachedSource(withIds);
|
|
313
|
+
const minifiedSize = Buffer.byteLength(cached.source(), 'utf-8');
|
|
314
|
+
metadata.sizeByHash.set(hash, minifiedSize);
|
|
315
|
+
minifiedModules.set(hash, {
|
|
316
|
+
source: cached,
|
|
317
|
+
module: mod,
|
|
318
|
+
id,
|
|
319
|
+
isShorthand
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
compilation.errors.push(err);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
onFileMinified();
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
// Create token with optional ':' prefix for shorthand modules
|
|
330
|
+
// For non-shorthand: __WEBPACK_CHUNK_MODULE__hash (becomes "id":__WEBPACK_CHUNK_MODULE__hash in object)
|
|
331
|
+
// For shorthand: :__WEBPACK_CHUNK_MODULE__hash (becomes "id"__WEBPACK_CHUNK_MODULE__hash, ':' makes it valid property assignment)
|
|
332
|
+
const tokenPrefix = isShorthand ? ':' : '';
|
|
333
|
+
const result = new RawSource(`${tokenPrefix}${CHUNK_MODULE_TOKEN}${hash}`);
|
|
334
|
+
sourceCache.set(source, {
|
|
335
|
+
hash,
|
|
336
|
+
source: result,
|
|
337
|
+
isShorthand
|
|
338
|
+
});
|
|
339
|
+
// Return an expression to replace later
|
|
340
|
+
return result;
|
|
341
|
+
});
|
|
342
|
+
// The optimizeChunkModules hook is the last async hook that occurs before chunk rendering
|
|
343
|
+
compilation.hooks.optimizeChunkModules.tapPromise(PLUGIN_NAME, async () => {
|
|
344
|
+
minifierConnection = await minifier.connectAsync();
|
|
345
|
+
submittedModules.clear();
|
|
346
|
+
});
|
|
347
|
+
const isJSAsset = /\.[cm]?js(\?.+)?$/;
|
|
348
|
+
// This should happen before any other tasks that operate during processAssets
|
|
349
|
+
compilation.hooks.processAssets.tapPromise(TAP_BEFORE, async () => {
|
|
350
|
+
const { chunkGraph, chunks } = compilation;
|
|
351
|
+
// Still need to minify the rendered assets
|
|
352
|
+
for (const chunk of chunks) {
|
|
353
|
+
const allChunkModules = chunkGraph.getChunkModulesIterableBySourceType(chunk, 'javascript');
|
|
354
|
+
if (!allChunkModules) {
|
|
355
|
+
// This chunk does not contain javascript modules
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
for (const assetName of chunk.files) {
|
|
359
|
+
const asset = compilation.assets[assetName];
|
|
360
|
+
// Verify that this is a JS asset
|
|
361
|
+
if (isJSAsset.test(assetName)) {
|
|
362
|
+
++pendingMinificationRequests;
|
|
363
|
+
const { source: wrappedCodeRaw, map } = useSourceMaps
|
|
364
|
+
? asset.sourceAndMap()
|
|
365
|
+
: {
|
|
366
|
+
source: asset.source(),
|
|
367
|
+
map: undefined
|
|
368
|
+
};
|
|
369
|
+
const rawCode = wrappedCodeRaw.toString();
|
|
370
|
+
const nameForMap = `(chunks)/${assetName}`;
|
|
371
|
+
const hash = hashCodeFragment(rawCode);
|
|
372
|
+
minifier.minify({
|
|
373
|
+
hash,
|
|
374
|
+
code: rawCode,
|
|
375
|
+
nameForMap: useSourceMaps ? nameForMap : undefined,
|
|
376
|
+
externals: undefined
|
|
377
|
+
}, (result) => {
|
|
378
|
+
if (isMinificationResultError(result)) {
|
|
379
|
+
compilation.errors.push(result.error);
|
|
380
|
+
// eslint-disable-next-line no-console
|
|
381
|
+
console.error(result.error);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
try {
|
|
385
|
+
const { code: minified, map: minifierMap } = result;
|
|
386
|
+
const rawOutput = useSourceMaps
|
|
387
|
+
? new SourceMapSource(minified, // Code
|
|
388
|
+
nameForMap, // File
|
|
389
|
+
minifierMap !== null && minifierMap !== void 0 ? minifierMap : undefined, // Base source map
|
|
390
|
+
rawCode, // Source from before transform
|
|
391
|
+
map !== null && map !== void 0 ? map : undefined, // Source Map from before transform
|
|
392
|
+
true // Remove original source
|
|
393
|
+
)
|
|
394
|
+
: new RawSource(minified);
|
|
395
|
+
const withIds = postProcessCode(new ReplaceSource(rawOutput), {
|
|
396
|
+
compilation,
|
|
397
|
+
module: undefined,
|
|
398
|
+
loggingName: assetName
|
|
399
|
+
});
|
|
400
|
+
minifiedAssets.set(assetName, {
|
|
401
|
+
source: new CachedSource(withIds),
|
|
402
|
+
chunk,
|
|
403
|
+
fileName: assetName,
|
|
404
|
+
renderInfo: new Map(),
|
|
405
|
+
type: 'javascript'
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
compilation.errors.push(err);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
onFileMinified();
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
// This isn't a JS asset. Don't try to minify the asset wrapper, though if it contains modules, those might still get replaced with minified versions.
|
|
417
|
+
minifiedAssets.set(assetName, {
|
|
418
|
+
// Still need to restore ids
|
|
419
|
+
source: postProcessCode(new ReplaceSource(asset), {
|
|
420
|
+
compilation,
|
|
421
|
+
module: undefined,
|
|
422
|
+
loggingName: assetName
|
|
423
|
+
}),
|
|
424
|
+
chunk,
|
|
425
|
+
fileName: assetName,
|
|
426
|
+
renderInfo: new Map(),
|
|
427
|
+
type: 'unknown'
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
allRequestsIssued = true;
|
|
433
|
+
if (pendingMinificationRequests) {
|
|
434
|
+
await new Promise((resolve) => {
|
|
435
|
+
resolveMinifyPromise = resolve;
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
// Handle any error from the minifier.
|
|
439
|
+
await (minifierConnection === null || minifierConnection === void 0 ? void 0 : minifierConnection.disconnectAsync());
|
|
440
|
+
// All assets and modules have been minified, hand them off to be rehydrated
|
|
441
|
+
await this.hooks.rehydrateAssets.promise({
|
|
442
|
+
assets: minifiedAssets,
|
|
443
|
+
modules: minifiedModules
|
|
444
|
+
}, compilation);
|
|
445
|
+
});
|
|
446
|
+
// Need to update chunk hashes with information from this plugin
|
|
447
|
+
javascriptHooks.chunkHash.tap(PLUGIN_NAME, (chunk, hash) => {
|
|
448
|
+
// Apply the options hash
|
|
449
|
+
hash.update(binaryConfig);
|
|
450
|
+
// Apply the hash from the minifier
|
|
451
|
+
if (minifierConnection) {
|
|
452
|
+
hash.update(minifierConnection.configHash, 'utf8');
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
//# sourceMappingURL=ModuleMinifierPlugin.js.map
|