├── test ├── fixtures │ ├── empty.js │ ├── file.js │ ├── emitted.js │ ├── shebang-1.js │ ├── cache-1.js │ ├── cache-3.js │ ├── copy.cjs │ ├── copy.js │ ├── copy.mjs │ ├── cache-2.js │ ├── import-export │ │ ├── async-dep.js │ │ ├── dep.js │ │ └── entry.js │ ├── async-import-export │ │ ├── async-dep.js │ │ └── entry.js │ ├── shebang.js │ ├── inline-optimization.js │ ├── unreachable-code.js │ ├── unreachable-code-2.js │ ├── excluded1.js │ ├── excluded2.js │ ├── comments-2.js │ ├── included1.js │ ├── included2.js │ ├── ecma-7 │ │ └── entry.js │ ├── warning.js │ ├── nested │ │ └── comments.js │ ├── comments-4.js │ ├── asset-resource.js │ ├── file.worker.js │ ├── emit-loader.js │ ├── cache.js │ ├── entry.js │ ├── file-loader.js │ ├── cache-4.js │ ├── entry.mjs │ ├── minify │ │ ├── es5.js │ │ └── es6.js │ ├── worker-loader.js │ ├── comments-3.js │ ├── ecma-8 │ │ └── entry.js │ ├── comments.js │ ├── ecma-5 │ │ └── entry.js │ └── ecma-6 │ │ └── entry.js ├── helpers │ ├── getErrors.js │ ├── getWarnings.js │ ├── compile.js │ ├── countPlugins.js │ ├── BrokenCodePlugin.js │ ├── readAssets.js │ ├── ExistingCommentsFile.js │ ├── execute.js │ ├── normalizeErrors.js │ ├── ModifyExistingAsset.js │ ├── EmitNewAsset.js │ ├── index.js │ ├── readAsset.js │ ├── snapshotResolver.js │ └── getCompiler.js ├── exclude-option.test.js ├── include-option.test.js ├── test-option.test.js ├── __snapshots__ │ ├── worker.test.js.snap │ ├── validate-options.test.js.snap │ ├── parallel-option.test.js.snap │ ├── exclude-option.test.js.snap │ └── include-option.test.js.snap ├── validate-options.test.js ├── parallel-option.test.js ├── worker.test.js ├── terserOptions-option.test.js └── extractComments-option.test.js ├── jest.config.js ├── .prettierignore ├── .gitattributes ├── .husky ├── pre-commit └── commit-msg ├── lint-staged.config.js ├── commitlint.config.js ├── eslint.config.mjs ├── .editorconfig ├── .gitignore ├── tsconfig.json ├── babel.config.js ├── .github └── workflows │ ├── dependency-review.yml │ └── nodejs.yml ├── types ├── minify.d.ts ├── utils.d.ts └── index.d.ts ├── .cspell.json ├── LICENSE ├── src ├── minify.js └── options.json ├── package.json └── README.md /test/fixtures/empty.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/file.js: -------------------------------------------------------------------------------- 1 | module.exports = "ok"; 2 | -------------------------------------------------------------------------------- /test/fixtures/emitted.js: -------------------------------------------------------------------------------- 1 | console.log('HERE'); 2 | -------------------------------------------------------------------------------- /test/fixtures/shebang-1.js: -------------------------------------------------------------------------------- 1 | console.log('HERE'); 2 | -------------------------------------------------------------------------------- /test/fixtures/cache-1.js: -------------------------------------------------------------------------------- 1 | module.exports = 'string'; 2 | -------------------------------------------------------------------------------- /test/fixtures/cache-3.js: -------------------------------------------------------------------------------- 1 | module.exports = class A {}; 2 | -------------------------------------------------------------------------------- /test/fixtures/copy.cjs: -------------------------------------------------------------------------------- 1 | var foo = 12; 2 | console.log(foo); 3 | -------------------------------------------------------------------------------- /test/fixtures/copy.js: -------------------------------------------------------------------------------- 1 | var foo = 12; 2 | console.log(foo); 3 | -------------------------------------------------------------------------------- /test/fixtures/copy.mjs: -------------------------------------------------------------------------------- 1 | var foo = 12; 2 | console.log(foo); 3 | -------------------------------------------------------------------------------- /test/fixtures/cache-2.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { return /test/ }; 2 | -------------------------------------------------------------------------------- /test/fixtures/import-export/async-dep.js: -------------------------------------------------------------------------------- 1 | export default "async-dep"; 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: "node", 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/async-import-export/async-dep.js: -------------------------------------------------------------------------------- 1 | export default "async-dep"; 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist 3 | /node_modules 4 | /test/fixtures 5 | CHANGELOG.md -------------------------------------------------------------------------------- /test/fixtures/import-export/dep.js: -------------------------------------------------------------------------------- 1 | export const bar = 'bar'; 2 | export default 'foo'; 3 | -------------------------------------------------------------------------------- /test/fixtures/shebang.js: -------------------------------------------------------------------------------- 1 | /* Comment */ 2 | /*! Legal Comment */ 3 | console.log('HERE'); 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | package-lock.json -diff 2 | * text=auto 3 | bin/* eol=lf 4 | yarn.lock -diff 5 | -------------------------------------------------------------------------------- /test/fixtures/inline-optimization.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | !function(){ console.log(42); }(); 4 | -------------------------------------------------------------------------------- /test/fixtures/unreachable-code.js: -------------------------------------------------------------------------------- 1 | function foo(x) { if (x) { return bar(); not_called1(); } } 2 | -------------------------------------------------------------------------------- /test/fixtures/unreachable-code-2.js: -------------------------------------------------------------------------------- 1 | function foo(x) { if (x) { return bar(); not_called1(); } } 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /test/fixtures/excluded1.js: -------------------------------------------------------------------------------- 1 | module.exports = function Bar1() { 2 | const b = 2 + 2; 3 | console.log(b + 1 + 2); 4 | }; -------------------------------------------------------------------------------- /test/fixtures/excluded2.js: -------------------------------------------------------------------------------- 1 | module.exports = function Bar2() { 2 | const b = 2 + 2; 3 | console.log(b + 1 + 2); 4 | }; -------------------------------------------------------------------------------- /test/fixtures/comments-2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Information. 3 | * @license MIT 4 | */ 5 | 6 | module.exports = Math.random(); 7 | -------------------------------------------------------------------------------- /test/fixtures/included1.js: -------------------------------------------------------------------------------- 1 | module.exports = function Bar1() { 2 | const b = 2 + 2; 3 | console.log(b + 1 + 2); 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/included2.js: -------------------------------------------------------------------------------- 1 | module.exports = function Bar2() { 2 | const b = 2 + 2; 3 | console.log(b + 1 + 2); 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/ecma-7/entry.js: -------------------------------------------------------------------------------- 1 | function pow(base, exponent) { 2 | return base ** exponent; // 9 3 | } 4 | 5 | module.exports = pow; 6 | -------------------------------------------------------------------------------- /test/fixtures/warning.js: -------------------------------------------------------------------------------- 1 | const form = new Array; 2 | 3 | if (!form instanceof FormData) { 4 | console.log("error in form") 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/nested/comments.js: -------------------------------------------------------------------------------- 1 | /*! Legal Comment */ 2 | 3 | /** @license Copyright 2112 Moon. **/ 4 | 5 | module.exports = Math.random(); 6 | -------------------------------------------------------------------------------- /test/fixtures/async-import-export/entry.js: -------------------------------------------------------------------------------- 1 | import("./async-dep").then(() => { 2 | console.log('Good') 3 | }); 4 | 5 | export default "Awesome"; 6 | -------------------------------------------------------------------------------- /test/fixtures/comments-4.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Duplicate comment in difference files. 3 | * @license MIT 4 | */ 5 | 6 | module.exports = Math.random(); 7 | -------------------------------------------------------------------------------- /test/fixtures/asset-resource.js: -------------------------------------------------------------------------------- 1 | import script from './emitted.js' 2 | 3 | const myVar = 12; 4 | 5 | console.log(myVar, script); 6 | 7 | export default 12; 8 | -------------------------------------------------------------------------------- /test/fixtures/file.worker.js: -------------------------------------------------------------------------------- 1 | /*! Legal Comment 1 */ 2 | 3 | /*! Legal Comment 2 */ 4 | 5 | /*! Legal Comment 3 */ 6 | 7 | onmessage = function (event) {}; 8 | -------------------------------------------------------------------------------- /test/helpers/getErrors.js: -------------------------------------------------------------------------------- 1 | import normalizeErrors from "./normalizeErrors"; 2 | 3 | export default (stats) => normalizeErrors(stats.compilation.errors).sort(); 4 | -------------------------------------------------------------------------------- /test/helpers/getWarnings.js: -------------------------------------------------------------------------------- 1 | import normalizeErrors from "./normalizeErrors"; 2 | 3 | export default (stats) => normalizeErrors(stats.compilation.warnings).sort(); 4 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "*": ["prettier --write --ignore-unknown", "cspell --no-must-find-files"], 3 | "*.js": ["eslint --cache --fix"], 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/emit-loader.js: -------------------------------------------------------------------------------- 1 | module.exports = function(content) { 2 | this.emitFile("extra-file.js", 'var a = 1; console.log(a);'); 3 | 4 | return content; 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/cache.js: -------------------------------------------------------------------------------- 1 | // foo 2 | /* @preserve*/ 3 | // bar 4 | const a = 2 + 2; 5 | 6 | module.exports = function Foo() { 7 | const b = 2 + 2; 8 | console.log(b + 1 + 2); 9 | }; 10 | -------------------------------------------------------------------------------- /test/fixtures/entry.js: -------------------------------------------------------------------------------- 1 | // foo 2 | /* @preserve*/ 3 | // bar 4 | const a = 2 + 2; 5 | 6 | module.exports = function Foo() { 7 | const b = 2 + 2; 8 | console.log(b + 1 + 2); 9 | }; 10 | -------------------------------------------------------------------------------- /test/fixtures/file-loader.js: -------------------------------------------------------------------------------- 1 | import script from '!!file-loader?name=[path][name].[ext]!./emitted.js' 2 | 3 | const myVar = 12; 4 | 5 | console.log(myVar, script); 6 | 7 | export default 12; 8 | -------------------------------------------------------------------------------- /test/fixtures/cache-4.js: -------------------------------------------------------------------------------- 1 | // foo 2 | /* @preserve*/ 3 | // bar 4 | const a = 2 + 2; 5 | 6 | module.exports = function Foo() { 7 | const b = 2 + 2; 8 | console.log(b + 1 + 2); 9 | }; 10 | -------------------------------------------------------------------------------- /test/fixtures/entry.mjs: -------------------------------------------------------------------------------- 1 | // foo 2 | // bar 3 | const a = 2 + 2; 4 | 5 | function test() { 6 | const b = 2 + 2; 7 | console.log(b + 1 + 2 + a); 8 | } 9 | 10 | test(); 11 | 12 | export default test; 13 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | rules: { 4 | "header-max-length": [0], 5 | "body-max-line-length": [0], 6 | "footer-max-line-length": [0], 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "eslint/config"; 2 | import configs from "eslint-config-webpack/configs.js"; 3 | 4 | export default defineConfig([ 5 | { 6 | extends: [configs["recommended-dirty"]], 7 | }, 8 | ]); 9 | -------------------------------------------------------------------------------- /test/fixtures/minify/es5.js: -------------------------------------------------------------------------------- 1 | function myFunction() { 2 | var baz = document.getElementById("root").innerHTML; 3 | 4 | document.getElementById("demo").innerHTML = "Paragraph changed." + baz; 5 | } 6 | 7 | module.exports = myFunction; 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /test/helpers/compile.js: -------------------------------------------------------------------------------- 1 | export default (compiler) => 2 | new Promise((resolve, reject) => { 3 | compiler.run((error, stats) => { 4 | if (error) { 5 | return reject(error); 6 | } 7 | 8 | return resolve(stats); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | npm-debug.log* 4 | .eslintcache 5 | 6 | /coverage 7 | /dist 8 | /local 9 | /reports 10 | /node_modules 11 | /test/outputs 12 | 13 | .DS_Store 14 | Thumbs.db 15 | .idea 16 | .vscode 17 | *.sublime-project 18 | *.sublime-workspace 19 | *.iml 20 | -------------------------------------------------------------------------------- /test/fixtures/worker-loader.js: -------------------------------------------------------------------------------- 1 | /*! Legal Comment */ 2 | 3 | import Worker from './file.worker.js'; 4 | 5 | const worker = new Worker(); 6 | 7 | worker.postMessage({ a: 1 }); 8 | worker.onmessage = function (event) {}; 9 | 10 | worker.addEventListener('message', function (event) {}); 11 | -------------------------------------------------------------------------------- /test/fixtures/comments-3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Duplicate comment in same file. 3 | * @license MIT 4 | */ 5 | 6 | /** 7 | * Duplicate comment in same file. 8 | * @license MIT 9 | */ 10 | 11 | /** 12 | * Duplicate comment in difference files. 13 | * @license MIT 14 | */ 15 | 16 | module.exports = Math.random(); 17 | -------------------------------------------------------------------------------- /test/fixtures/ecma-8/entry.js: -------------------------------------------------------------------------------- 1 | async function fetchJson(url) { 2 | try { 3 | let request = await fetch(url); 4 | let text = await request.text(); 5 | return JSON.parse(text); 6 | } catch (error) { 7 | console.log(`ERROR: ${error.stack}`); 8 | } 9 | } 10 | 11 | module.exports = fetchJson; 12 | -------------------------------------------------------------------------------- /test/fixtures/minify/es6.js: -------------------------------------------------------------------------------- 1 | class Point { 2 | constructor(x, y) { 3 | this.x = x; 4 | this.y = y; 5 | } 6 | 7 | static distance(a, b) { 8 | const dx = a.x - b.x; 9 | const dy = a.y - b.y; 10 | 11 | return Math.hypot(dx, dy); 12 | } 13 | } 14 | 15 | console.log('HERE'); 16 | 17 | export default Point; 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "moduleResolution": "node", 5 | "allowJs": true, 6 | "checkJs": true, 7 | "strict": true, 8 | "types": ["node"], 9 | "resolveJsonModule": true, 10 | "allowSyntheticDefaultImports": true 11 | }, 12 | "include": ["./src/**/*"] 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/import-export/entry.js: -------------------------------------------------------------------------------- 1 | import foo, { bar } from './dep'; 2 | 3 | function Foo() { 4 | const b = foo; 5 | const baz = `baz${Math.random()}`; 6 | return () => { 7 | return { 8 | a: b + bar + baz, 9 | b, 10 | baz, 11 | }; 12 | }; 13 | } 14 | 15 | console.log(Foo()); 16 | 17 | export default Foo; 18 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const MIN_BABEL_VERSION = 7; 2 | 3 | module.exports = (api) => { 4 | api.assertVersion(MIN_BABEL_VERSION); 5 | api.cache(true); 6 | 7 | return { 8 | presets: [ 9 | [ 10 | "@babel/preset-env", 11 | { 12 | targets: { 13 | node: "10.13.0", 14 | }, 15 | }, 16 | ], 17 | ], 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | name: "Dependency Review" 2 | on: [pull_request] 3 | 4 | permissions: 5 | contents: read 6 | 7 | jobs: 8 | dependency-review: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: "Checkout Repository" 12 | uses: actions/checkout@v5 13 | - name: "Dependency Review" 14 | uses: actions/dependency-review-action@v4 15 | -------------------------------------------------------------------------------- /test/helpers/countPlugins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {object} root0 options 3 | * @param {import("tapable").Hook[]} root0.hooks hooks 4 | * @returns {number} count of plugins 5 | */ 6 | export default function countPlugins({ hooks }) { 7 | return Object.keys(hooks).reduce((aggregate, name) => { 8 | aggregate[name] = Array.isArray(hooks[name].taps) 9 | ? hooks[name].taps.length 10 | : 0; 11 | return aggregate; 12 | }, {}); 13 | } 14 | -------------------------------------------------------------------------------- /test/helpers/BrokenCodePlugin.js: -------------------------------------------------------------------------------- 1 | import webpack from "webpack"; 2 | 3 | export default class BrokenCodePlugin { 4 | apply(compiler) { 5 | const plugin = { name: this.constructor.name }; 6 | 7 | compiler.hooks.thisCompilation.tap(plugin, (compilation) => { 8 | compilation.hooks.additionalAssets.tap(plugin, () => { 9 | compilation.assets["broken.js"] = new webpack.sources.RawSource( 10 | "`Broken===", 11 | ); 12 | }); 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/helpers/readAssets.js: -------------------------------------------------------------------------------- 1 | import readAsset from "./readAsset"; 2 | 3 | /** 4 | * @param {import("webpack").Compiler} compiler compiler 5 | * @param {import("webpack").Stats} stats stats 6 | * @returns {Record} assets 7 | */ 8 | export default function readAssets(compiler, stats) { 9 | const assets = {}; 10 | 11 | for (const asset of Object.keys(stats.compilation.assets)) { 12 | assets[asset] = readAsset(asset, compiler, stats); 13 | } 14 | 15 | return assets; 16 | } 17 | -------------------------------------------------------------------------------- /test/helpers/ExistingCommentsFile.js: -------------------------------------------------------------------------------- 1 | import webpack from "webpack"; 2 | 3 | export default class ExistingCommentsFile { 4 | apply(compiler) { 5 | const plugin = { name: this.constructor.name }; 6 | 7 | compiler.hooks.thisCompilation.tap(plugin, (compilation) => { 8 | compilation.hooks.additionalAssets.tap(plugin, () => { 9 | compilation.assets["licenses.txt"] = new webpack.sources.RawSource( 10 | "// Existing Comment", 11 | ); 12 | }); 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/helpers/execute.js: -------------------------------------------------------------------------------- 1 | import Module from "module"; 2 | import path from "path"; 3 | 4 | const parentModule = module; 5 | 6 | export default (code) => { 7 | const resource = "test.js"; 8 | const module = new Module(resource, parentModule); 9 | 10 | module.paths = Module._nodeModulePaths( 11 | path.resolve(__dirname, "../fixtures"), 12 | ); 13 | module.filename = resource; 14 | 15 | module._compile( 16 | `let __export__;${code};module.exports = __export__;`, 17 | resource, 18 | ); 19 | 20 | return module.exports; 21 | }; 22 | -------------------------------------------------------------------------------- /test/helpers/normalizeErrors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} str string 3 | * @returns {string} string without cwd 4 | */ 5 | function removeCWD(str) { 6 | const isWin = process.platform === "win32"; 7 | let cwd = process.cwd(); 8 | 9 | if (isWin) { 10 | str = str.replace(/\\/g, "/"); 11 | 12 | cwd = cwd.replace(/\\/g, "/"); 13 | } 14 | 15 | return str.replace(new RegExp(cwd, "g"), ""); 16 | } 17 | 18 | export default (errors) => 19 | errors.map((error) => 20 | removeCWD(error.toString().split("\n").slice(0, 2).join("\n")), 21 | ); 22 | -------------------------------------------------------------------------------- /test/fixtures/comments.js: -------------------------------------------------------------------------------- 1 | import('./nested/comments.js'); 2 | 3 | /*! Legal Comment */ 4 | 5 | /** 6 | * @preserve Copyright 2009 SomeThirdParty. 7 | * Here is the full license text and copyright 8 | * notice for this file. Note that the notice can span several 9 | * lines and is only terminated by the closing star and slash: 10 | */ 11 | 12 | /** 13 | * Utility functions for the foo package. 14 | * @license Apache-2.0 15 | */ 16 | 17 | /*! Legal Foo */ 18 | 19 | // Foo 20 | 21 | /* 22 | Foo Bar 23 | */ 24 | 25 | /* 26 | * Foo 27 | */ 28 | 29 | // @lic 30 | 31 | module.exports = Math.random(); 32 | -------------------------------------------------------------------------------- /test/helpers/ModifyExistingAsset.js: -------------------------------------------------------------------------------- 1 | import webpack from "webpack"; 2 | 3 | export default class ExistingCommentsFile { 4 | constructor(options = {}) { 5 | this.options = options; 6 | } 7 | 8 | apply(compiler) { 9 | const plugin = { name: this.constructor.name }; 10 | 11 | compiler.hooks.thisCompilation.tap(plugin, (compilation) => { 12 | compilation.hooks.additionalAssets.tap(plugin, () => { 13 | compilation.assets[this.options.name] = 14 | new webpack.sources.ConcatSource( 15 | `function changed() {} ${ 16 | this.options.comment ? "/*! CHANGED */" : "" 17 | }`, 18 | compilation.assets[this.options.name], 19 | ); 20 | }); 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /types/minify.d.ts: -------------------------------------------------------------------------------- 1 | export type MinimizedResult = import("./index.js").MinimizedResult; 2 | export type CustomOptions = import("./index.js").CustomOptions; 3 | /** @typedef {import("./index.js").MinimizedResult} MinimizedResult */ 4 | /** @typedef {import("./index.js").CustomOptions} CustomOptions */ 5 | /** 6 | * @template T 7 | * @param {import("./index.js").InternalOptions} options options 8 | * @returns {Promise} minified result 9 | */ 10 | export function minify( 11 | options: import("./index.js").InternalOptions, 12 | ): Promise; 13 | /** 14 | * @param {string} options options 15 | * @returns {Promise} minified result 16 | */ 17 | export function transform(options: string): Promise; 18 | -------------------------------------------------------------------------------- /test/helpers/EmitNewAsset.js: -------------------------------------------------------------------------------- 1 | export default class EmitNewAsset { 2 | constructor(options = {}) { 3 | this.options = options; 4 | } 5 | 6 | apply(compiler) { 7 | const pluginName = this.constructor.name; 8 | 9 | const { RawSource } = compiler.webpack.sources; 10 | 11 | compiler.hooks.compilation.tap(pluginName, (compilation) => { 12 | compilation.hooks.processAssets.tap( 13 | { 14 | name: pluginName, 15 | stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT, 16 | }, 17 | () => { 18 | compilation.emitAsset( 19 | this.options.name, 20 | new RawSource(` 21 | var foo = 'bar'; 22 | 23 | var bar = 'foo'; 24 | `), 25 | ); 26 | }, 27 | ); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { default as BrokenCodePlugin } from "./BrokenCodePlugin"; 2 | export { default as countPlugins } from "./countPlugins"; 3 | export { default as ExistingCommentsFile } from "./ExistingCommentsFile"; 4 | export { default as EmitNewAsset } from "./EmitNewAsset"; 5 | export { default as execute } from "./execute"; 6 | export { default as readsAssets } from "./readAssets"; 7 | export { default as ModifyExistingAsset } from "./ModifyExistingAsset"; 8 | export { default as getCompiler } from "./getCompiler"; 9 | export { default as compile } from "./compile"; 10 | export { default as getWarnings } from "./getWarnings"; 11 | export { default as getErrors } from "./getErrors"; 12 | export { default as normalizeErrors } from "./normalizeErrors"; 13 | export { default as readAsset } from "./readAsset"; 14 | -------------------------------------------------------------------------------- /test/helpers/readAsset.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | /** 4 | * @param {string} asset asset name 5 | * @param {import("webpack").Compiler} compiler compiler 6 | * @param {import("webpack").Stats} stats stats 7 | * @returns {string} asset code 8 | */ 9 | export default (asset, compiler, stats) => { 10 | const usedFs = compiler.outputFileSystem; 11 | const outputPath = stats.compilation.outputOptions.path; 12 | 13 | let data = ""; 14 | let targetFile = asset; 15 | 16 | const queryStringIdx = targetFile.indexOf("?"); 17 | 18 | if (queryStringIdx >= 0) { 19 | targetFile = targetFile.slice(0, queryStringIdx); 20 | } 21 | 22 | try { 23 | data = usedFs.readFileSync(path.join(outputPath, targetFile)).toString(); 24 | } catch (error) { 25 | data = error.toString(); 26 | } 27 | 28 | return data; 29 | }; 30 | -------------------------------------------------------------------------------- /test/helpers/snapshotResolver.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | const webpack = require("webpack"); 4 | 5 | const [webpackVersion] = webpack.version; 6 | const snapshotExtension = `.snap.webpack${webpackVersion}`; 7 | 8 | // eslint-disable-next-line no-console 9 | console.log("Current webpack version:", webpackVersion); 10 | 11 | module.exports = { 12 | resolveSnapshotPath: (testPath) => 13 | path.join( 14 | path.dirname(testPath), 15 | "__snapshots__", 16 | `${path.basename(testPath)}${snapshotExtension}`, 17 | ), 18 | resolveTestPath: (snapshotPath) => 19 | snapshotPath 20 | .replace(`${path.sep}__snapshots__`, "") 21 | .slice(0, -snapshotExtension.length), 22 | testPathForConsistencyCheck: path.join( 23 | "consistency_check", 24 | "__tests__", 25 | "example.test.js", 26 | ), 27 | }; 28 | -------------------------------------------------------------------------------- /test/fixtures/ecma-5/entry.js: -------------------------------------------------------------------------------- 1 | var Person = { 2 | firstName: null, // the person’s first name 3 | lastName: null // the person’s last name 4 | }; 5 | 6 | // “subclass” Person 7 | var Employee = Object.create(Person, { 8 | id: { // the employees’s id 9 | value: null, 10 | enumerable: true, 11 | configurable: true, 12 | writable: true 13 | } 14 | }); 15 | 16 | // “subclass” Employee 17 | var Manager = Object.create(Employee, { 18 | department: { // the manager’s department 19 | value: null, 20 | enumerable: true, 21 | configurable: true, 22 | writable: true 23 | } 24 | }); 25 | 26 | module.exports = { 27 | Person: Person, 28 | Employee: Employee, 29 | Manager: Manager 30 | }; 31 | 32 | (function() { 33 | var zzz = {}; 34 | var abc = { 35 | data() { 36 | return { 37 | a: 2 38 | }; 39 | }}; 40 | console.log(abc) 41 | })(); 42 | -------------------------------------------------------------------------------- /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "language": "en,en-gb", 4 | "words": [ 5 | "esbuild", 6 | "autocrlf", 7 | "jridgewell", 8 | "MACPC", 9 | "CACH", 10 | "EAAG", 11 | "GAAIA", 12 | "GACT", 13 | "KAAIC", 14 | "QAASA", 15 | "emca", 16 | "fnames", 17 | "fullhash", 18 | "nosources", 19 | "chunkhash", 20 | "pathinfo", 21 | "OAAOA", 22 | "SAAUE", 23 | "IAAM", 24 | "MAAOC", 25 | "IAAID", 26 | "IAAI", 27 | "OAAOC", 28 | "SAAUA", 29 | "IAAI", 30 | "CAAC", 31 | "memfs", 32 | "filebase", 33 | "Etags", 34 | "uglif", 35 | "minifier", 36 | "filebase", 37 | "toplevel", 38 | "commitlint", 39 | "tapable" 40 | ], 41 | "ignorePaths": [ 42 | "CHANGELOG.md", 43 | "package.json", 44 | "dist/**", 45 | "**/__snapshots__/**", 46 | "package-lock.json", 47 | "/test/outputs", 48 | "node_modules", 49 | "coverage", 50 | "*.log" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /test/helpers/getCompiler.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | import { Volume, createFsFromVolume } from "memfs"; 4 | import webpack from "webpack"; 5 | 6 | /** 7 | * @param {import("webpack").Configuration | import("webpack").Configuration[]} options options 8 | * @returns {import("webpack").Compiler | import("webpack").MultiCompiler} compiler 9 | */ 10 | export default function getCompiler(options = {}) { 11 | const compiler = webpack( 12 | Array.isArray(options) 13 | ? options 14 | : { 15 | mode: "production", 16 | bail: true, 17 | entry: path.resolve(__dirname, "../fixtures/entry.js"), 18 | optimization: { 19 | minimize: false, 20 | }, 21 | output: { 22 | pathinfo: false, 23 | path: path.resolve(__dirname, "dist"), 24 | filename: "[name].js", 25 | chunkFilename: "[id].[name].js", 26 | }, 27 | plugins: [], 28 | ...options, 29 | }, 30 | ); 31 | 32 | compiler.outputFileSystem = createFsFromVolume(new Volume()); 33 | 34 | return compiler; 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | 'Software'), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/fixtures/ecma-6/entry.js: -------------------------------------------------------------------------------- 1 | class Mortgage { 2 | constructor(principal, years, rate) { 3 | this.principal = principal; 4 | this.years = years; 5 | this.rate = rate; 6 | } 7 | 8 | get monthlyPayment() { 9 | let monthlyRate = this.rate / 100 / 12; 10 | 11 | return this.principal * monthlyRate / (1 - (Math.pow(1/(1 + monthlyRate), this.years * 12))); 12 | } 13 | 14 | get amortization() { 15 | let monthlyPayment = this.monthlyPayment; 16 | let monthlyRate = this.rate / 100 / 12; 17 | let balance = this.principal; 18 | let amortization = []; 19 | 20 | for (let y=0; y} options options 7 | * @returns {Promise} minified result 8 | */ 9 | async function minify(options) { 10 | const { name, input, inputSourceMap, extractComments } = options; 11 | const { implementation, options: minimizerOptions } = options.minimizer; 12 | 13 | return implementation( 14 | { [name]: input }, 15 | inputSourceMap, 16 | minimizerOptions, 17 | extractComments, 18 | ); 19 | } 20 | 21 | /** 22 | * @param {string} options options 23 | * @returns {Promise} minified result 24 | */ 25 | async function transform(options) { 26 | // 'use strict' => this === undefined (Clean Scope) 27 | // Safer for possible security issues, albeit not critical at all here 28 | 29 | const evaluatedOptions = 30 | /** 31 | * @template T 32 | * @type {import("./index.js").InternalOptions} 33 | */ 34 | ( 35 | // eslint-disable-next-line no-new-func 36 | new Function( 37 | "exports", 38 | "require", 39 | "module", 40 | "__filename", 41 | "__dirname", 42 | `'use strict'\nreturn ${options}`, 43 | // eslint-disable-next-line n/exports-style 44 | )(exports, require, module, __filename, __dirname) 45 | ); 46 | 47 | return minify(evaluatedOptions); 48 | } 49 | 50 | module.exports = { minify, transform }; 51 | -------------------------------------------------------------------------------- /test/exclude-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | import TerserPlugin from "../src/index"; 4 | 5 | import { 6 | compile, 7 | getCompiler, 8 | getErrors, 9 | getWarnings, 10 | readsAssets, 11 | } from "./helpers"; 12 | 13 | describe("exclude option", () => { 14 | let compiler; 15 | 16 | beforeEach(() => { 17 | compiler = getCompiler({ 18 | entry: { 19 | excluded1: path.resolve(__dirname, "./fixtures/excluded1.js"), 20 | excluded2: path.resolve(__dirname, "./fixtures/excluded2.js"), 21 | entry: path.resolve(__dirname, "./fixtures/entry.js"), 22 | }, 23 | }); 24 | }); 25 | 26 | it("should match snapshot for a single RegExp value", async () => { 27 | new TerserPlugin({ 28 | exclude: /excluded1/i, 29 | }).apply(compiler); 30 | 31 | const stats = await compile(compiler); 32 | 33 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 34 | expect(getErrors(stats)).toMatchSnapshot("errors"); 35 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 36 | }); 37 | 38 | it("should match snapshot for a single String value", async () => { 39 | new TerserPlugin({ 40 | exclude: "excluded1", 41 | }).apply(compiler); 42 | 43 | const stats = await compile(compiler); 44 | 45 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 46 | expect(getErrors(stats)).toMatchSnapshot("errors"); 47 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 48 | }); 49 | 50 | it("should match snapshot for multiple RegExp values", async () => { 51 | new TerserPlugin({ 52 | exclude: [/excluded1/i, /excluded2/i], 53 | }).apply(compiler); 54 | 55 | const stats = await compile(compiler); 56 | 57 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 58 | expect(getErrors(stats)).toMatchSnapshot("errors"); 59 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 60 | }); 61 | 62 | it("should match snapshot for multiple String values", async () => { 63 | new TerserPlugin({ 64 | exclude: ["excluded1", "excluded2"], 65 | }).apply(compiler); 66 | 67 | const stats = await compile(compiler); 68 | 69 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 70 | expect(getErrors(stats)).toMatchSnapshot("errors"); 71 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/include-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | import TerserPlugin from "../src/index"; 4 | 5 | import { 6 | compile, 7 | getCompiler, 8 | getErrors, 9 | getWarnings, 10 | readsAssets, 11 | } from "./helpers"; 12 | 13 | describe("include option", () => { 14 | let compiler; 15 | 16 | beforeEach(() => { 17 | compiler = getCompiler({ 18 | entry: { 19 | included1: path.resolve(__dirname, "./fixtures/included1.js"), 20 | included2: path.resolve(__dirname, "./fixtures/included2.js"), 21 | entry: path.resolve(__dirname, "./fixtures/entry.js"), 22 | }, 23 | }); 24 | }); 25 | 26 | it("should match snapshot for a single RegExp value", async () => { 27 | new TerserPlugin({ 28 | include: /included1/i, 29 | }).apply(compiler); 30 | 31 | const stats = await compile(compiler); 32 | 33 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 34 | expect(getErrors(stats)).toMatchSnapshot("errors"); 35 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 36 | }); 37 | 38 | it("should match snapshot for a single String value", async () => { 39 | new TerserPlugin({ 40 | include: "included1", 41 | }).apply(compiler); 42 | 43 | const stats = await compile(compiler); 44 | 45 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 46 | expect(getErrors(stats)).toMatchSnapshot("errors"); 47 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 48 | }); 49 | 50 | it("should match snapshot for multiple RegExp values", async () => { 51 | new TerserPlugin({ 52 | include: [/included1/i, /included2/i], 53 | }).apply(compiler); 54 | 55 | const stats = await compile(compiler); 56 | 57 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 58 | expect(getErrors(stats)).toMatchSnapshot("errors"); 59 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 60 | }); 61 | 62 | it("should match snapshot for multiple String values", async () => { 63 | new TerserPlugin({ 64 | include: ["included1", "included2"], 65 | }).apply(compiler); 66 | 67 | const stats = await compile(compiler); 68 | 69 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 70 | expect(getErrors(stats)).toMatchSnapshot("errors"); 71 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/test-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | import TerserPlugin from "../src/index"; 4 | 5 | import { 6 | compile, 7 | getCompiler, 8 | getErrors, 9 | getWarnings, 10 | readsAssets, 11 | } from "./helpers"; 12 | 13 | describe("test option", () => { 14 | let compiler; 15 | 16 | beforeEach(() => { 17 | compiler = getCompiler({ 18 | entry: { 19 | js: path.resolve(__dirname, "./fixtures/entry.js"), 20 | mjs: path.resolve(__dirname, "./fixtures/entry.mjs"), 21 | importExport: path.resolve( 22 | __dirname, 23 | "./fixtures/import-export/entry.js", 24 | ), 25 | AsyncImportExport: path.resolve( 26 | __dirname, 27 | "./fixtures/async-import-export/entry.js", 28 | ), 29 | }, 30 | output: { 31 | path: path.resolve(__dirname, "./dist"), 32 | filename: "[name].js?var=[fullhash]", 33 | chunkFilename: "[id].[name].js?ver=[fullhash]", 34 | }, 35 | }); 36 | }); 37 | 38 | it("should match snapshot with empty value", async () => { 39 | new TerserPlugin().apply(compiler); 40 | 41 | const stats = await compile(compiler); 42 | 43 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 44 | expect(getErrors(stats)).toMatchSnapshot("errors"); 45 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 46 | }); 47 | 48 | it("should match snapshot for a single `test` value ({RegExp})", async () => { 49 | new TerserPlugin({ 50 | test: /(m)?js\.js(\?.*)?$/i, 51 | }).apply(compiler); 52 | 53 | const stats = await compile(compiler); 54 | 55 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 56 | expect(getErrors(stats)).toMatchSnapshot("errors"); 57 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 58 | }); 59 | 60 | it('should match snapshot for a single "test" value ({String})', async () => { 61 | new TerserPlugin({ 62 | test: "js.js", 63 | }).apply(compiler); 64 | 65 | const stats = await compile(compiler); 66 | 67 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 68 | expect(getErrors(stats)).toMatchSnapshot("errors"); 69 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 70 | }); 71 | 72 | it('should match snapshot for multiple "test" values ({RegExp})', async () => { 73 | new TerserPlugin({ 74 | test: [/(m)?js\.js(\?.*)?$/i, /AsyncImportExport\.js(\?.*)?$/i], 75 | }).apply(compiler); 76 | 77 | const stats = await compile(compiler); 78 | 79 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 80 | expect(getErrors(stats)).toMatchSnapshot("errors"); 81 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 82 | }); 83 | 84 | it('should match snapshot for multiple "test" values ({String})', async () => { 85 | new TerserPlugin({ 86 | test: ["js.js", "AsyncImportExport.js"], 87 | }).apply(compiler); 88 | 89 | const stats = await compile(compiler); 90 | 91 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 92 | expect(getErrors(stats)).toMatchSnapshot("errors"); 93 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 94 | }); 95 | 96 | it('should match snapshot and uglify "mjs"', async () => { 97 | compiler = getCompiler({ 98 | entry: { 99 | js: path.resolve(__dirname, "./fixtures/entry.js"), 100 | mjs: path.resolve(__dirname, "./fixtures/entry.mjs"), 101 | importExport: path.resolve( 102 | __dirname, 103 | "./fixtures/import-export/entry.js", 104 | ), 105 | AsyncImportExport: path.resolve( 106 | __dirname, 107 | "./fixtures/async-import-export/entry.js", 108 | ), 109 | }, 110 | output: { 111 | path: path.resolve(__dirname, "./dist"), 112 | filename: "[name].mjs?var=[fullhash]", 113 | chunkFilename: "[id].[name].mjs?ver=[fullhash]", 114 | }, 115 | }); 116 | 117 | new TerserPlugin().apply(compiler); 118 | 119 | const stats = await compile(compiler); 120 | 121 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 122 | expect(getErrors(stats)).toMatchSnapshot("errors"); 123 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: terser-webpack-plugin 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - next 8 | pull_request: 9 | branches: 10 | - main 11 | - next 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | lint: 18 | name: Lint - ${{ matrix.os }} - Node v${{ matrix.node-version }} 19 | 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | strategy: 24 | matrix: 25 | os: [ubuntu-latest] 26 | node-version: [lts/*] 27 | 28 | runs-on: ${{ matrix.os }} 29 | 30 | concurrency: 31 | group: lint-${{ matrix.os }}-v${{ matrix.node-version }}-${{ github.ref }} 32 | cancel-in-progress: true 33 | 34 | steps: 35 | - uses: actions/checkout@v5 36 | with: 37 | fetch-depth: 0 38 | 39 | - name: Use Node.js ${{ matrix.node-version }} 40 | uses: actions/setup-node@v4 41 | with: 42 | node-version: ${{ matrix.node-version }} 43 | cache: "npm" 44 | 45 | - name: Install dependencies 46 | run: npm ci 47 | 48 | - name: Lint 49 | run: npm run lint 50 | 51 | - name: Build types 52 | run: npm run build:types 53 | 54 | - name: Check types 55 | run: if [ -n "$(git status types --porcelain)" ]; then echo "Missing types. Update types by running 'npm run build:types'"; exit 1; else echo "All types are valid"; fi 56 | 57 | - name: Security audit 58 | run: npm run security 59 | 60 | - name: Validate PR commits with commitlint 61 | if: github.event_name == 'pull_request' 62 | run: npx commitlint --from ${{ github.event.pull_request.head.sha }}~${{ github.event.pull_request.commits }} --to ${{ github.event.pull_request.head.sha }} --verbose 63 | 64 | test: 65 | name: Test - ${{ matrix.os }} - Node v${{ matrix.node-version }}, Webpack ${{ matrix.webpack-version }} 66 | 67 | strategy: 68 | matrix: 69 | os: [ubuntu-latest, windows-latest, macos-latest] 70 | node-version: [10.x, 12.x, 14.x, 16.x, 18.x, 20.x, 22.x, 24.x] 71 | webpack-version: [latest] 72 | 73 | runs-on: ${{ matrix.os }} 74 | 75 | concurrency: 76 | group: test-${{ matrix.os }}-v${{ matrix.node-version }}-${{ matrix.webpack-version }}-${{ github.ref }} 77 | cancel-in-progress: true 78 | 79 | steps: 80 | - name: Setup Git 81 | if: matrix.os == 'windows-latest' 82 | run: git config --global core.autocrlf input 83 | 84 | - uses: actions/checkout@v5 85 | - uses: actions/github-script@v7 86 | id: calculate_architecture 87 | with: 88 | result-encoding: string 89 | script: | 90 | if ('${{ matrix.os }}' === 'macos-latest' && ('${{ matrix['node-version'] }}' === '10.x' || '${{ matrix['node-version'] }}' === '12.x' || '${{ matrix['node-version'] }}' === '14.x')) { 91 | return "x64" 92 | } else { 93 | return '' 94 | } 95 | - name: Use Node.js ${{ matrix.node-version }} 96 | uses: actions/setup-node@v4 97 | with: 98 | node-version: ${{ matrix.node-version }} 99 | architecture: ${{ steps.calculate_architecture.outputs.result }} 100 | cache: "npm" 101 | 102 | - name: Install dependencies (old) 103 | run: | 104 | npm install -D typescript@4 --force 105 | if: matrix.node-version == '10.x' || matrix.node-version == '12.x' || matrix.node-version == '14.x' || matrix.node-version == '16.x' || matrix.node-version == '18.x' 106 | 107 | - name: Install dependencies 108 | run: npm ci 109 | if: matrix.node-version != '10.x' && matrix.node-version != '12.x' && matrix.node-version != '14.x' && matrix.node-version != '16.x' && matrix.node-version != '18.x' 110 | 111 | - name: Install webpack ${{ matrix.webpack-version }} 112 | if: matrix.webpack-version != 'latest' 113 | run: npm i webpack@${{ matrix.webpack-version }} 114 | 115 | - name: Run tests for webpack version ${{ matrix.webpack-version }} 116 | run: npm run test:coverage -- --ci 117 | 118 | - name: Submit coverage data to codecov 119 | uses: codecov/codecov-action@v5 120 | with: 121 | token: ${{ secrets.CODECOV_TOKEN }} 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terser-webpack-plugin", 3 | "version": "5.3.16", 4 | "description": "Terser plugin for webpack", 5 | "keywords": [ 6 | "uglify", 7 | "uglify-js", 8 | "uglify-es", 9 | "terser", 10 | "webpack", 11 | "webpack-plugin", 12 | "minification", 13 | "compress", 14 | "compressor", 15 | "min", 16 | "minification", 17 | "minifier", 18 | "minify", 19 | "optimize", 20 | "optimizer" 21 | ], 22 | "homepage": "https://github.com/webpack/terser-webpack-plugin", 23 | "bugs": "https://github.com/webpack/terser-webpack-plugin/issues", 24 | "repository": "webpack/terser-webpack-plugin", 25 | "funding": { 26 | "type": "opencollective", 27 | "url": "https://opencollective.com/webpack" 28 | }, 29 | "license": "MIT", 30 | "author": "webpack Contrib Team", 31 | "main": "dist/index.js", 32 | "types": "types/index.d.ts", 33 | "files": [ 34 | "dist", 35 | "types" 36 | ], 37 | "scripts": { 38 | "clean": "del-cli dist types", 39 | "prebuild": "npm run clean", 40 | "build:types": "tsc --declaration --emitDeclarationOnly --outDir types && prettier \"types/**/*.ts\" --write", 41 | "build:code": "cross-env NODE_ENV=production babel src -d dist --copy-files", 42 | "build": "npm-run-all -p \"build:**\"", 43 | "commitlint": "commitlint --from=main", 44 | "security": "npm audit --production", 45 | "lint:prettier": "prettier --list-different .", 46 | "lint:code": "eslint --cache .", 47 | "lint:spelling": "cspell \"**/*.*\"", 48 | "lint:types": "tsc --pretty --noEmit", 49 | "lint": "npm-run-all -l -p \"lint:**\"", 50 | "fix:code": "npm run lint:code -- --fix", 51 | "fix:prettier": "npm run lint:prettier -- --write", 52 | "fix": "npm-run-all -l fix:code fix:prettier", 53 | "test:only": "cross-env NODE_ENV=test jest", 54 | "test:watch": "npm run test:only -- --watch", 55 | "test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage", 56 | "pretest": "npm run lint", 57 | "test": "npm run test:coverage", 58 | "prepare": "husky install && npm run build", 59 | "release": "standard-version" 60 | }, 61 | "dependencies": { 62 | "@jridgewell/trace-mapping": "^0.3.25", 63 | "jest-worker": "^27.4.5", 64 | "schema-utils": "^4.3.0", 65 | "serialize-javascript": "^6.0.2", 66 | "terser": "^5.31.1" 67 | }, 68 | "devDependencies": { 69 | "@babel/cli": "^7.24.7", 70 | "@babel/core": "^7.24.7", 71 | "@babel/preset-env": "^7.24.7", 72 | "@commitlint/cli": "^17.7.1", 73 | "@commitlint/config-conventional": "^17.7.0", 74 | "@eslint/js": "^9.29.0", 75 | "@eslint/markdown": "^7.1.0", 76 | "@stylistic/eslint-plugin": "^5.2.2", 77 | "@swc/core": "^1.3.102", 78 | "@types/node": "^24.2.1", 79 | "@types/serialize-javascript": "^5.0.2", 80 | "@types/uglify-js": "^3.17.5", 81 | "copy-webpack-plugin": "^9.0.1", 82 | "cross-env": "^7.0.3", 83 | "cspell": "^6.31.2", 84 | "del": "^6.0.0", 85 | "del-cli": "^3.0.1", 86 | "esbuild": "^0.25.0", 87 | "eslint": "^9.29.0", 88 | "eslint-config-prettier": "^10.1.1", 89 | "eslint-config-webpack": "^4.5.1", 90 | "eslint-plugin-import": "^2.32.0", 91 | "eslint-plugin-jest": "^29.0.1", 92 | "eslint-plugin-jsdoc": "^54.0.0", 93 | "eslint-plugin-n": "^17.21.0", 94 | "eslint-plugin-prettier": "^5.5.0", 95 | "eslint-plugin-unicorn": "^60.0.0", 96 | "file-loader": "^6.2.0", 97 | "husky": "^7.0.2", 98 | "jest": "^27.5.1", 99 | "lint-staged": "^13.2.3", 100 | "memfs": "^3.4.13", 101 | "npm-run-all": "^4.1.5", 102 | "prettier": "^3.6.0", 103 | "prettier-2": "npm:prettier@^2", 104 | "standard-version": "^9.3.1", 105 | "typescript": "^5.9.2", 106 | "typescript-eslint": "^8.39.1", 107 | "uglify-js": "^3.19.3", 108 | "webpack": "^5.101.0", 109 | "webpack-cli": "^4.10.0", 110 | "worker-loader": "^3.0.8" 111 | }, 112 | "peerDependencies": { 113 | "webpack": "^5.1.0" 114 | }, 115 | "peerDependenciesMeta": { 116 | "@swc/core": { 117 | "optional": true 118 | }, 119 | "uglify-js": { 120 | "optional": true 121 | }, 122 | "esbuild": { 123 | "optional": true 124 | } 125 | }, 126 | "engines": { 127 | "node": ">= 10.13.0" 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /types/utils.d.ts: -------------------------------------------------------------------------------- 1 | export type Task = () => Promise; 2 | export type FunctionReturning = () => T; 3 | export type ExtractCommentsOptions = 4 | import("./index.js").ExtractCommentsOptions; 5 | export type ExtractCommentsFunction = 6 | import("./index.js").ExtractCommentsFunction; 7 | export type ExtractCommentsCondition = 8 | import("./index.js").ExtractCommentsCondition; 9 | export type Input = import("./index.js").Input; 10 | export type MinimizedResult = import("./index.js").MinimizedResult; 11 | export type CustomOptions = import("./index.js").CustomOptions; 12 | export type RawSourceMap = import("./index.js").RawSourceMap; 13 | export type PredefinedOptions = import("./index.js").PredefinedOptions; 14 | export type ExtractedComments = Array; 15 | /** 16 | * @param {Input} input input 17 | * @param {RawSourceMap=} sourceMap source map 18 | * @param {CustomOptions=} minimizerOptions options 19 | * @returns {Promise} minimized result 20 | */ 21 | export function esbuildMinify( 22 | input: Input, 23 | sourceMap?: RawSourceMap | undefined, 24 | minimizerOptions?: CustomOptions | undefined, 25 | ): Promise; 26 | export namespace esbuildMinify { 27 | /** 28 | * @returns {string | undefined} the minimizer version 29 | */ 30 | function getMinimizerVersion(): string | undefined; 31 | /** 32 | * @returns {boolean | undefined} true if worker thread is supported, false otherwise 33 | */ 34 | function supportsWorkerThreads(): boolean | undefined; 35 | } 36 | /** 37 | * @template T 38 | * @typedef {() => T} FunctionReturning 39 | */ 40 | /** 41 | * @template T 42 | * @param {FunctionReturning} fn memorized function 43 | * @returns {FunctionReturning} new function 44 | */ 45 | export function memoize(fn: FunctionReturning): FunctionReturning; 46 | /** 47 | * @param {Input} input input 48 | * @param {RawSourceMap=} sourceMap source map 49 | * @param {CustomOptions=} minimizerOptions options 50 | * @returns {Promise} minimized result 51 | */ 52 | export function swcMinify( 53 | input: Input, 54 | sourceMap?: RawSourceMap | undefined, 55 | minimizerOptions?: CustomOptions | undefined, 56 | ): Promise; 57 | export namespace swcMinify { 58 | /** 59 | * @returns {string | undefined} the minimizer version 60 | */ 61 | function getMinimizerVersion(): string | undefined; 62 | /** 63 | * @returns {boolean | undefined} true if worker thread is supported, false otherwise 64 | */ 65 | function supportsWorkerThreads(): boolean | undefined; 66 | } 67 | /** 68 | * @param {Input} input input 69 | * @param {RawSourceMap=} sourceMap source map 70 | * @param {CustomOptions=} minimizerOptions options 71 | * @param {ExtractCommentsOptions=} extractComments extract comments option 72 | * @returns {Promise} minimized result 73 | */ 74 | export function terserMinify( 75 | input: Input, 76 | sourceMap?: RawSourceMap | undefined, 77 | minimizerOptions?: CustomOptions | undefined, 78 | extractComments?: ExtractCommentsOptions | undefined, 79 | ): Promise; 80 | export namespace terserMinify { 81 | /** 82 | * @returns {string | undefined} the minimizer version 83 | */ 84 | function getMinimizerVersion(): string | undefined; 85 | /** 86 | * @returns {boolean | undefined} true if worker thread is supported, false otherwise 87 | */ 88 | function supportsWorkerThreads(): boolean | undefined; 89 | } 90 | /** 91 | * @template T 92 | * @typedef {() => Promise} Task 93 | */ 94 | /** 95 | * Run tasks with limited concurrency. 96 | * @template T 97 | * @param {number} limit Limit of tasks that run at once. 98 | * @param {Task[]} tasks List of tasks to run. 99 | * @returns {Promise} A promise that fulfills to an array of the results 100 | */ 101 | export function throttleAll(limit: number, tasks: Task[]): Promise; 102 | /** 103 | * @param {Input} input input 104 | * @param {RawSourceMap=} sourceMap source map 105 | * @param {CustomOptions=} minimizerOptions options 106 | * @param {ExtractCommentsOptions=} extractComments extract comments option 107 | * @returns {Promise} minimized result 108 | */ 109 | export function uglifyJsMinify( 110 | input: Input, 111 | sourceMap?: RawSourceMap | undefined, 112 | minimizerOptions?: CustomOptions | undefined, 113 | extractComments?: ExtractCommentsOptions | undefined, 114 | ): Promise; 115 | export namespace uglifyJsMinify { 116 | /** 117 | * @returns {string | undefined} the minimizer version 118 | */ 119 | function getMinimizerVersion(): string | undefined; 120 | /** 121 | * @returns {boolean | undefined} true if worker thread is supported, false otherwise 122 | */ 123 | function supportsWorkerThreads(): boolean | undefined; 124 | } 125 | -------------------------------------------------------------------------------- /src/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": { 3 | "Rule": { 4 | "description": "Filtering rule as regex or string.", 5 | "anyOf": [ 6 | { 7 | "instanceof": "RegExp", 8 | "tsType": "RegExp" 9 | }, 10 | { 11 | "type": "string", 12 | "minLength": 1 13 | } 14 | ] 15 | }, 16 | "Rules": { 17 | "description": "Filtering rules.", 18 | "anyOf": [ 19 | { 20 | "type": "array", 21 | "items": { 22 | "description": "A rule condition.", 23 | "oneOf": [ 24 | { 25 | "$ref": "#/definitions/Rule" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "$ref": "#/definitions/Rule" 32 | } 33 | ] 34 | } 35 | }, 36 | "title": "TerserPluginOptions", 37 | "type": "object", 38 | "additionalProperties": false, 39 | "properties": { 40 | "test": { 41 | "description": "Include all modules that pass test assertion.", 42 | "link": "https://github.com/webpack/terser-webpack-plugin#test", 43 | "oneOf": [ 44 | { 45 | "$ref": "#/definitions/Rules" 46 | } 47 | ] 48 | }, 49 | "include": { 50 | "description": "Include all modules matching any of these conditions.", 51 | "link": "https://github.com/webpack/terser-webpack-plugin#include", 52 | "oneOf": [ 53 | { 54 | "$ref": "#/definitions/Rules" 55 | } 56 | ] 57 | }, 58 | "exclude": { 59 | "description": "Exclude all modules matching any of these conditions.", 60 | "link": "https://github.com/webpack/terser-webpack-plugin#exclude", 61 | "oneOf": [ 62 | { 63 | "$ref": "#/definitions/Rules" 64 | } 65 | ] 66 | }, 67 | "terserOptions": { 68 | "description": "Options for `terser` (by default) or custom `minify` function.", 69 | "link": "https://github.com/webpack/terser-webpack-plugin#terseroptions", 70 | "additionalProperties": true, 71 | "type": "object" 72 | }, 73 | "extractComments": { 74 | "description": "Whether comments shall be extracted to a separate file.", 75 | "link": "https://github.com/webpack/terser-webpack-plugin#extractcomments", 76 | "anyOf": [ 77 | { 78 | "type": "boolean" 79 | }, 80 | { 81 | "type": "string", 82 | "minLength": 1 83 | }, 84 | { 85 | "instanceof": "RegExp" 86 | }, 87 | { 88 | "instanceof": "Function" 89 | }, 90 | { 91 | "additionalProperties": false, 92 | "properties": { 93 | "condition": { 94 | "anyOf": [ 95 | { 96 | "type": "boolean" 97 | }, 98 | { 99 | "type": "string", 100 | "minLength": 1 101 | }, 102 | { 103 | "instanceof": "RegExp" 104 | }, 105 | { 106 | "instanceof": "Function" 107 | } 108 | ], 109 | "description": "Condition what comments you need extract.", 110 | "link": "https://github.com/webpack/terser-webpack-plugin#condition" 111 | }, 112 | "filename": { 113 | "anyOf": [ 114 | { 115 | "type": "string", 116 | "minLength": 1 117 | }, 118 | { 119 | "instanceof": "Function" 120 | } 121 | ], 122 | "description": "The file where the extracted comments will be stored. Default is to append the suffix .LICENSE.txt to the original filename.", 123 | "link": "https://github.com/webpack/terser-webpack-plugin#filename" 124 | }, 125 | "banner": { 126 | "anyOf": [ 127 | { 128 | "type": "boolean" 129 | }, 130 | { 131 | "type": "string", 132 | "minLength": 1 133 | }, 134 | { 135 | "instanceof": "Function" 136 | } 137 | ], 138 | "description": "The banner text that points to the extracted file and will be added on top of the original file", 139 | "link": "https://github.com/webpack/terser-webpack-plugin#banner" 140 | } 141 | }, 142 | "type": "object" 143 | } 144 | ] 145 | }, 146 | "parallel": { 147 | "description": "Use multi-process parallel running to improve the build speed.", 148 | "link": "https://github.com/webpack/terser-webpack-plugin#parallel", 149 | "anyOf": [ 150 | { 151 | "type": "boolean" 152 | }, 153 | { 154 | "type": "integer" 155 | } 156 | ] 157 | }, 158 | "minify": { 159 | "description": "Allows you to override default minify function.", 160 | "link": "https://github.com/webpack/terser-webpack-plugin#number", 161 | "instanceof": "Function" 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /test/__snapshots__/worker.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`worker normalizes when minimizerOptions.output.comments is string: all 1`] = ` 4 | Object { 5 | "code": "var foo=1;/* hello */ 6 | // Comment 7 | /* duplicate */ 8 | /* duplicate */", 9 | "extractedComments": Array [], 10 | "map": undefined, 11 | } 12 | `; 13 | 14 | exports[`worker should match snapshot when minimizerOptions.compress.comments is boolean 1`] = ` 15 | Object { 16 | "code": "var foo=1;", 17 | "extractedComments": Array [], 18 | "map": undefined, 19 | } 20 | `; 21 | 22 | exports[`worker should match snapshot when minimizerOptions.compress.comments is object 1`] = ` 23 | Object { 24 | "code": "var foo=1;", 25 | "extractedComments": Array [], 26 | "map": undefined, 27 | } 28 | `; 29 | 30 | exports[`worker should match snapshot when minimizerOptions.extractComments is number 1`] = ` 31 | Object { 32 | "code": "var foo=1;", 33 | "extractedComments": Array [], 34 | "map": undefined, 35 | } 36 | `; 37 | 38 | exports[`worker should match snapshot when minimizerOptions.mangle is "null" 1`] = ` 39 | Object { 40 | "code": "var foo=1;", 41 | "extractedComments": Array [], 42 | "map": undefined, 43 | } 44 | `; 45 | 46 | exports[`worker should match snapshot when minimizerOptions.mangle is boolean 1`] = ` 47 | Object { 48 | "code": "var foo=1;", 49 | "extractedComments": Array [], 50 | "map": undefined, 51 | } 52 | `; 53 | 54 | exports[`worker should match snapshot when minimizerOptions.mangle is object 1`] = ` 55 | Object { 56 | "code": "var a=1;", 57 | "extractedComments": Array [], 58 | "map": undefined, 59 | } 60 | `; 61 | 62 | exports[`worker should match snapshot when minimizerOptions.output.comments is string: some 1`] = ` 63 | Object { 64 | "code": "var foo=1;", 65 | "extractedComments": Array [], 66 | "map": undefined, 67 | } 68 | `; 69 | 70 | exports[`worker should match snapshot when options.extractComments is "all" value 1`] = ` 71 | Object { 72 | "code": "var foo=1;", 73 | "extractedComments": Array [ 74 | "/* hello */", 75 | "// Comment", 76 | "/* duplicate */", 77 | ], 78 | "map": undefined, 79 | } 80 | `; 81 | 82 | exports[`worker should match snapshot when options.extractComments is "false" 1`] = ` 83 | Object { 84 | "code": "var foo=1;", 85 | "extractedComments": Array [], 86 | "map": undefined, 87 | } 88 | `; 89 | 90 | exports[`worker should match snapshot when options.extractComments is "some" value 1`] = ` 91 | Object { 92 | "code": "var foo=1;", 93 | "extractedComments": Array [], 94 | "map": undefined, 95 | } 96 | `; 97 | 98 | exports[`worker should match snapshot when options.extractComments is "true" 1`] = ` 99 | Object { 100 | "code": "var foo=1;", 101 | "extractedComments": Array [], 102 | "map": undefined, 103 | } 104 | `; 105 | 106 | exports[`worker should match snapshot when options.extractComments is Function 1`] = ` 107 | Object { 108 | "code": "var foo=1;", 109 | "extractedComments": Array [ 110 | "/* hello */", 111 | "// Comment", 112 | "/* duplicate */", 113 | ], 114 | "map": undefined, 115 | } 116 | `; 117 | 118 | exports[`worker should match snapshot when options.extractComments is Object with "all" value 1`] = ` 119 | Object { 120 | "code": "var foo=1;", 121 | "extractedComments": Array [ 122 | "/* hello */", 123 | "// Comment", 124 | "/* duplicate */", 125 | ], 126 | "map": undefined, 127 | } 128 | `; 129 | 130 | exports[`worker should match snapshot when options.extractComments is Object with "some" value 1`] = ` 131 | Object { 132 | "code": "var foo=1;", 133 | "extractedComments": Array [], 134 | "map": undefined, 135 | } 136 | `; 137 | 138 | exports[`worker should match snapshot when options.extractComments is Object with "true" value 1`] = ` 139 | Object { 140 | "code": "var foo=1;", 141 | "extractedComments": Array [], 142 | "map": undefined, 143 | } 144 | `; 145 | 146 | exports[`worker should match snapshot when options.extractComments is RegExp 1`] = ` 147 | Object { 148 | "code": "var foo=1;", 149 | "extractedComments": Array [ 150 | "/* hello */", 151 | ], 152 | "map": undefined, 153 | } 154 | `; 155 | 156 | exports[`worker should match snapshot when options.extractComments is empty Object 1`] = ` 157 | Object { 158 | "code": "var foo=1;", 159 | "extractedComments": Array [], 160 | "map": undefined, 161 | } 162 | `; 163 | 164 | exports[`worker should match snapshot when options.output.comments is "some" 1`] = ` 165 | "var foo = 1;/* hello */ 166 | // Comment 167 | /* duplicate */ 168 | /* duplicate */" 169 | `; 170 | 171 | exports[`worker should match snapshot with extract option set to a single file 1`] = ` 172 | Object { 173 | "code": "/******/function hello(o){console.log(o)}", 174 | "extractedComments": Array [], 175 | "map": undefined, 176 | } 177 | `; 178 | 179 | exports[`worker should match snapshot with options.inputSourceMap 1`] = ` 180 | Object { 181 | "code": "function foo(f){if(f)return bar()}", 182 | "extractedComments": Array [], 183 | "map": Object { 184 | "mappings": "AAAA,SAASA,IAAIC,GAAK,GAAIA,EAAK,OAAOC,KAAwB", 185 | "names": Array [ 186 | "foo", 187 | "x", 188 | "bar", 189 | ], 190 | "sources": Array [ 191 | "test6.js", 192 | ], 193 | "version": 3, 194 | }, 195 | } 196 | `; 197 | -------------------------------------------------------------------------------- /test/validate-options.test.js: -------------------------------------------------------------------------------- 1 | import TerserPlugin from "../src"; 2 | 3 | describe("validation", () => { 4 | it("validate", () => { 5 | /* eslint-disable no-new */ 6 | expect(() => { 7 | new TerserPlugin({ test: /foo/ }); 8 | }).not.toThrow(); 9 | 10 | expect(() => { 11 | new TerserPlugin({ test: "foo" }); 12 | }).not.toThrow(); 13 | 14 | expect(() => { 15 | new TerserPlugin({ test: [/foo/] }); 16 | }).not.toThrow(); 17 | 18 | expect(() => { 19 | new TerserPlugin({ test: [/foo/, /bar/] }); 20 | }).not.toThrow(); 21 | 22 | expect(() => { 23 | new TerserPlugin({ test: ["foo", "bar"] }); 24 | }).not.toThrow(); 25 | 26 | expect(() => { 27 | new TerserPlugin({ test: [/foo/, "bar"] }); 28 | }).not.toThrow(); 29 | 30 | expect(() => { 31 | new TerserPlugin({ test: true }); 32 | }).toThrowErrorMatchingSnapshot(); 33 | 34 | expect(() => { 35 | new TerserPlugin({ test: [true] }); 36 | }).toThrowErrorMatchingSnapshot(); 37 | 38 | expect(() => { 39 | new TerserPlugin({ include: /foo/ }); 40 | }).not.toThrow(); 41 | 42 | expect(() => { 43 | new TerserPlugin({ include: "foo" }); 44 | }).not.toThrow(); 45 | 46 | expect(() => { 47 | new TerserPlugin({ include: [/foo/] }); 48 | }).not.toThrow(); 49 | 50 | expect(() => { 51 | new TerserPlugin({ include: [/foo/, /bar/] }); 52 | }).not.toThrow(); 53 | 54 | expect(() => { 55 | new TerserPlugin({ include: ["foo", "bar"] }); 56 | }).not.toThrow(); 57 | 58 | expect(() => { 59 | new TerserPlugin({ include: [/foo/, "bar"] }); 60 | }).not.toThrow(); 61 | 62 | expect(() => { 63 | new TerserPlugin({ include: true }); 64 | }).toThrowErrorMatchingSnapshot(); 65 | 66 | expect(() => { 67 | new TerserPlugin({ include: [true] }); 68 | }).toThrowErrorMatchingSnapshot(); 69 | 70 | expect(() => { 71 | new TerserPlugin({ exclude: /foo/ }); 72 | }).not.toThrow(); 73 | 74 | expect(() => { 75 | new TerserPlugin({ exclude: "foo" }); 76 | }).not.toThrow(); 77 | 78 | expect(() => { 79 | new TerserPlugin({ exclude: [/foo/] }); 80 | }).not.toThrow(); 81 | 82 | expect(() => { 83 | new TerserPlugin({ exclude: [/foo/, /bar/] }); 84 | }).not.toThrow(); 85 | 86 | expect(() => { 87 | new TerserPlugin({ exclude: ["foo", "bar"] }); 88 | }).not.toThrow(); 89 | 90 | expect(() => { 91 | new TerserPlugin({ exclude: [/foo/, "bar"] }); 92 | }).not.toThrow(); 93 | 94 | expect(() => { 95 | new TerserPlugin({ exclude: true }); 96 | }).toThrowErrorMatchingSnapshot(); 97 | 98 | expect(() => { 99 | new TerserPlugin({ exclude: [true] }); 100 | }).toThrowErrorMatchingSnapshot(); 101 | 102 | expect(() => { 103 | new TerserPlugin({ parallel: true }); 104 | }).not.toThrow(); 105 | 106 | expect(() => { 107 | new TerserPlugin({ parallel: false }); 108 | }).not.toThrow(); 109 | 110 | expect(() => { 111 | new TerserPlugin({ parallel: 2 }); 112 | }).not.toThrow(); 113 | 114 | expect(() => { 115 | new TerserPlugin({ parallel: "2" }); 116 | }).toThrowErrorMatchingSnapshot(); 117 | 118 | expect(() => { 119 | new TerserPlugin({ parallel: {} }); 120 | }).toThrowErrorMatchingSnapshot(); 121 | 122 | expect(() => { 123 | new TerserPlugin({ minify() {} }); 124 | }).not.toThrow(); 125 | 126 | expect(() => { 127 | new TerserPlugin({ minify: true }); 128 | }).toThrowErrorMatchingSnapshot(); 129 | 130 | expect(() => { 131 | new TerserPlugin({ terserOptions: {} }); 132 | }).not.toThrow(); 133 | 134 | expect(() => { 135 | new TerserPlugin({ terserOptions: null }); 136 | }).toThrowErrorMatchingSnapshot(); 137 | 138 | expect(() => { 139 | new TerserPlugin({ 140 | terserOptions: { 141 | ecma: undefined, 142 | parse: {}, 143 | compress: {}, 144 | mangle: true, 145 | module: false, 146 | output: null, 147 | toplevel: false, 148 | nameCache: null, 149 | ie8: false, 150 | keep_classnames: false, 151 | keep_fnames: false, 152 | safari10: false, 153 | }, 154 | }); 155 | }).not.toThrow(); 156 | 157 | expect(() => { 158 | new TerserPlugin({ terserOptions: { emca: 5 } }); 159 | }).not.toThrow(); 160 | 161 | expect(() => { 162 | new TerserPlugin({ extractComments: true }); 163 | }).not.toThrow(); 164 | 165 | expect(() => { 166 | new TerserPlugin({ extractComments: false }); 167 | }).not.toThrow(); 168 | 169 | expect(() => { 170 | new TerserPlugin({ extractComments: "comment" }); 171 | }).not.toThrow(); 172 | 173 | expect(() => { 174 | new TerserPlugin({ extractComments: /comment/ }); 175 | }).not.toThrow(); 176 | 177 | expect(() => { 178 | new TerserPlugin({ extractComments() {} }); 179 | }).not.toThrow(); 180 | 181 | expect(() => { 182 | new TerserPlugin({ 183 | extractComments: { 184 | condition: true, 185 | }, 186 | }); 187 | }).not.toThrow(); 188 | 189 | expect(() => { 190 | new TerserPlugin({ 191 | extractComments: { 192 | condition: "comment", 193 | }, 194 | }); 195 | }).not.toThrow(); 196 | 197 | expect(() => { 198 | new TerserPlugin({ 199 | extractComments: { 200 | condition: /comment/, 201 | }, 202 | }); 203 | }).not.toThrow(); 204 | 205 | expect(() => { 206 | new TerserPlugin({ 207 | extractComments: { 208 | condition() {}, 209 | }, 210 | }); 211 | }).not.toThrow(); 212 | 213 | expect(() => { 214 | new TerserPlugin({ 215 | extractComments: { 216 | condition: {}, 217 | }, 218 | }); 219 | }).toThrowErrorMatchingSnapshot(); 220 | 221 | expect(() => { 222 | new TerserPlugin({ 223 | extractComments: { 224 | filename: "test.js", 225 | }, 226 | }); 227 | }).not.toThrow(); 228 | 229 | expect(() => { 230 | new TerserPlugin({ 231 | extractComments: { 232 | filename() {}, 233 | }, 234 | }); 235 | }).not.toThrow(); 236 | 237 | expect(() => { 238 | new TerserPlugin({ 239 | extractComments: { 240 | filename: true, 241 | }, 242 | }); 243 | }).toThrowErrorMatchingSnapshot(); 244 | 245 | expect(() => { 246 | new TerserPlugin({ 247 | extractComments: { 248 | banner: true, 249 | }, 250 | }); 251 | }).not.toThrow(); 252 | 253 | expect(() => { 254 | new TerserPlugin({ 255 | extractComments: { 256 | banner: "banner", 257 | }, 258 | }); 259 | }).not.toThrow(); 260 | 261 | expect(() => { 262 | new TerserPlugin({ 263 | extractComments: { 264 | banner() {}, 265 | }, 266 | }); 267 | }).not.toThrow(); 268 | 269 | expect(() => { 270 | new TerserPlugin({ 271 | extractComments: { 272 | banner: /test/, 273 | }, 274 | }); 275 | }).toThrowErrorMatchingSnapshot(); 276 | 277 | expect(() => { 278 | new TerserPlugin({ extractComments: { unknown: true } }); 279 | }).toThrowErrorMatchingSnapshot(); 280 | 281 | expect(() => { 282 | new TerserPlugin({ unknown: true }); 283 | }).toThrowErrorMatchingSnapshot(); 284 | }); 285 | }); 286 | /* eslint-enable no-new */ 287 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | export = TerserPlugin; 2 | /** 3 | * @template [T=import("terser").MinifyOptions] 4 | */ 5 | declare class TerserPlugin { 6 | /** 7 | * @private 8 | * @param {unknown} input Input to check 9 | * @returns {boolean} Whether input is a source map 10 | */ 11 | private static isSourceMap; 12 | /** 13 | * @private 14 | * @param {unknown} warning warning 15 | * @param {string} file file 16 | * @returns {Error} built warning 17 | */ 18 | private static buildWarning; 19 | /** 20 | * @private 21 | * @param {Error | ErrorObject | string} error error 22 | * @param {string} file file 23 | * @param {TraceMap=} sourceMap source map 24 | * @param {Compilation["requestShortener"]=} requestShortener request shortener 25 | * @returns {Error} built error 26 | */ 27 | private static buildError; 28 | /** 29 | * @private 30 | * @param {Parallel} parallel value of the `parallel` option 31 | * @returns {number} number of cores for parallelism 32 | */ 33 | private static getAvailableNumberOfCores; 34 | /** 35 | * @private 36 | * @param {NonNullable["environment"]>} environment environment 37 | * @returns {number} ecma version 38 | */ 39 | private static getEcmaVersion; 40 | /** 41 | * @param {BasePluginOptions & DefinedDefaultMinimizerAndOptions=} options options 42 | */ 43 | constructor( 44 | options?: 45 | | (BasePluginOptions & DefinedDefaultMinimizerAndOptions) 46 | | undefined, 47 | ); 48 | /** 49 | * @private 50 | * @type {InternalPluginOptions} 51 | */ 52 | private options; 53 | /** 54 | * @private 55 | * @param {Compiler} compiler compiler 56 | * @param {Compilation} compilation compilation 57 | * @param {Record} assets assets 58 | * @param {{ availableNumberOfCores: number }} optimizeOptions optimize options 59 | * @returns {Promise} 60 | */ 61 | private optimize; 62 | /** 63 | * @param {Compiler} compiler compiler 64 | * @returns {void} 65 | */ 66 | apply(compiler: Compiler): void; 67 | } 68 | declare namespace TerserPlugin { 69 | export { 70 | terserMinify, 71 | uglifyJsMinify, 72 | swcMinify, 73 | esbuildMinify, 74 | Schema, 75 | Compiler, 76 | Compilation, 77 | Configuration, 78 | Asset, 79 | AssetInfo, 80 | JestWorker, 81 | RawSourceMap, 82 | TraceMap, 83 | Rule, 84 | Rules, 85 | ExtractCommentsFunction, 86 | ExtractCommentsCondition, 87 | ExtractCommentsFilename, 88 | ExtractCommentsBanner, 89 | ExtractCommentsObject, 90 | ExtractCommentsOptions, 91 | ErrorObject, 92 | MinimizedResult, 93 | Input, 94 | CustomOptions, 95 | InferDefaultType, 96 | PredefinedOptions, 97 | MinimizerOptions, 98 | BasicMinimizerImplementation, 99 | MinimizeFunctionHelpers, 100 | MinimizerImplementation, 101 | InternalOptions, 102 | MinimizerWorker, 103 | Parallel, 104 | BasePluginOptions, 105 | DefinedDefaultMinimizerAndOptions, 106 | InternalPluginOptions, 107 | }; 108 | } 109 | import { terserMinify } from "./utils"; 110 | import { uglifyJsMinify } from "./utils"; 111 | import { swcMinify } from "./utils"; 112 | import { esbuildMinify } from "./utils"; 113 | type Schema = import("schema-utils/declarations/validate").Schema; 114 | type Compiler = import("webpack").Compiler; 115 | type Compilation = import("webpack").Compilation; 116 | type Configuration = import("webpack").Configuration; 117 | type Asset = import("webpack").Asset; 118 | type AssetInfo = import("webpack").AssetInfo; 119 | type JestWorker = import("jest-worker").Worker; 120 | type RawSourceMap = import("@jridgewell/trace-mapping").EncodedSourceMap & { 121 | sources: string[]; 122 | sourcesContent?: string[]; 123 | file: string; 124 | }; 125 | type TraceMap = import("@jridgewell/trace-mapping").TraceMap; 126 | type Rule = RegExp | string; 127 | type Rules = Rule[] | Rule; 128 | type ExtractCommentsFunction = ( 129 | astNode: any, 130 | comment: { 131 | value: string; 132 | type: "comment1" | "comment2" | "comment3" | "comment4"; 133 | pos: number; 134 | line: number; 135 | col: number; 136 | }, 137 | ) => boolean; 138 | type ExtractCommentsCondition = 139 | | boolean 140 | | "all" 141 | | "some" 142 | | RegExp 143 | | ExtractCommentsFunction; 144 | type ExtractCommentsFilename = string | ((fileData: any) => string); 145 | type ExtractCommentsBanner = 146 | | boolean 147 | | string 148 | | ((commentsFile: string) => string); 149 | type ExtractCommentsObject = { 150 | /** 151 | * condition which comments need to be expected 152 | */ 153 | condition?: ExtractCommentsCondition | undefined; 154 | /** 155 | * filename for extracted comments 156 | */ 157 | filename?: ExtractCommentsFilename | undefined; 158 | /** 159 | * banner in filename for extracted comments 160 | */ 161 | banner?: ExtractCommentsBanner | undefined; 162 | }; 163 | type ExtractCommentsOptions = ExtractCommentsCondition | ExtractCommentsObject; 164 | type ErrorObject = { 165 | /** 166 | * message 167 | */ 168 | message: string; 169 | /** 170 | * line number 171 | */ 172 | line?: number | undefined; 173 | /** 174 | * column number 175 | */ 176 | column?: number | undefined; 177 | /** 178 | * error stack trace 179 | */ 180 | stack?: string | undefined; 181 | }; 182 | type MinimizedResult = { 183 | /** 184 | * code 185 | */ 186 | code?: string | undefined; 187 | /** 188 | * source map 189 | */ 190 | map?: RawSourceMap | undefined; 191 | /** 192 | * errors 193 | */ 194 | errors?: Array | undefined; 195 | /** 196 | * warnings 197 | */ 198 | warnings?: Array | undefined; 199 | /** 200 | * extracted comments 201 | */ 202 | extractedComments?: Array | undefined; 203 | }; 204 | type Input = { 205 | [file: string]: string; 206 | }; 207 | type CustomOptions = { 208 | [key: string]: any; 209 | }; 210 | type InferDefaultType = T extends infer U ? U : CustomOptions; 211 | type PredefinedOptions = { 212 | /** 213 | * true when code is a EC module, otherwise false 214 | */ 215 | module?: 216 | | (T extends { 217 | module?: infer P; 218 | } 219 | ? P 220 | : boolean | string) 221 | | undefined; 222 | /** 223 | * ecma version 224 | */ 225 | ecma?: 226 | | (T extends { 227 | ecma?: infer P; 228 | } 229 | ? P 230 | : number | string) 231 | | undefined; 232 | }; 233 | type MinimizerOptions = PredefinedOptions & InferDefaultType; 234 | type BasicMinimizerImplementation = ( 235 | input: Input, 236 | sourceMap: RawSourceMap | undefined, 237 | minifyOptions: MinimizerOptions, 238 | extractComments: ExtractCommentsOptions | undefined, 239 | ) => Promise | MinimizedResult; 240 | type MinimizeFunctionHelpers = { 241 | /** 242 | * function that returns version of minimizer 243 | */ 244 | getMinimizerVersion?: (() => string | undefined) | undefined; 245 | /** 246 | * true when minimizer support worker threads, otherwise false 247 | */ 248 | supportsWorkerThreads?: (() => boolean | undefined) | undefined; 249 | }; 250 | type MinimizerImplementation = BasicMinimizerImplementation & 251 | MinimizeFunctionHelpers; 252 | type InternalOptions = { 253 | /** 254 | * name 255 | */ 256 | name: string; 257 | /** 258 | * input 259 | */ 260 | input: string; 261 | /** 262 | * input source map 263 | */ 264 | inputSourceMap: RawSourceMap | undefined; 265 | /** 266 | * extract comments option 267 | */ 268 | extractComments: ExtractCommentsOptions | undefined; 269 | /** 270 | * minimizer 271 | */ 272 | minimizer: { 273 | implementation: MinimizerImplementation; 274 | options: MinimizerOptions; 275 | }; 276 | }; 277 | type MinimizerWorker = JestWorker & { 278 | transform: (options: string) => Promise; 279 | minify: (options: InternalOptions) => Promise; 280 | }; 281 | type Parallel = undefined | boolean | number; 282 | type BasePluginOptions = { 283 | /** 284 | * test rule 285 | */ 286 | test?: Rules | undefined; 287 | /** 288 | * include rile 289 | */ 290 | include?: Rules | undefined; 291 | /** 292 | * exclude rule 293 | */ 294 | exclude?: Rules | undefined; 295 | /** 296 | * extract comments options 297 | */ 298 | extractComments?: ExtractCommentsOptions | undefined; 299 | /** 300 | * parallel option 301 | */ 302 | parallel?: Parallel | undefined; 303 | }; 304 | type DefinedDefaultMinimizerAndOptions = 305 | T extends import("terser").MinifyOptions 306 | ? { 307 | minify?: MinimizerImplementation | undefined; 308 | terserOptions?: MinimizerOptions | undefined; 309 | } 310 | : { 311 | minify: MinimizerImplementation; 312 | terserOptions?: MinimizerOptions | undefined; 313 | }; 314 | type InternalPluginOptions = BasePluginOptions & { 315 | minimizer: { 316 | implementation: MinimizerImplementation; 317 | options: MinimizerOptions; 318 | }; 319 | }; 320 | -------------------------------------------------------------------------------- /test/__snapshots__/validate-options.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`validation validate 1`] = ` 4 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 5 | - options.test should be one of these: 6 | [RegExp | non-empty string, ...] | RegExp | non-empty string 7 | -> Filtering rules. 8 | -> Read more at https://github.com/webpack/terser-webpack-plugin#test 9 | Details: 10 | * options.test should be an array: 11 | [RegExp | non-empty string, ...] 12 | * options.test should be one of these: 13 | RegExp | non-empty string 14 | -> Filtering rule as regex or string. 15 | Details: 16 | * options.test should be an instance of RegExp. 17 | * options.test should be a non-empty string." 18 | `; 19 | 20 | exports[`validation validate 2`] = ` 21 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 22 | - options.test should be one of these: 23 | [RegExp | non-empty string, ...] | RegExp | non-empty string 24 | -> Filtering rules. 25 | -> Read more at https://github.com/webpack/terser-webpack-plugin#test 26 | Details: 27 | * options.test[0] should be one of these: 28 | RegExp | non-empty string 29 | -> Filtering rule as regex or string. 30 | Details: 31 | * options.test[0] should be an instance of RegExp. 32 | * options.test[0] should be a non-empty string." 33 | `; 34 | 35 | exports[`validation validate 3`] = ` 36 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 37 | - options.include should be one of these: 38 | [RegExp | non-empty string, ...] | RegExp | non-empty string 39 | -> Filtering rules. 40 | -> Read more at https://github.com/webpack/terser-webpack-plugin#include 41 | Details: 42 | * options.include should be an array: 43 | [RegExp | non-empty string, ...] 44 | * options.include should be one of these: 45 | RegExp | non-empty string 46 | -> Filtering rule as regex or string. 47 | Details: 48 | * options.include should be an instance of RegExp. 49 | * options.include should be a non-empty string." 50 | `; 51 | 52 | exports[`validation validate 4`] = ` 53 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 54 | - options.include should be one of these: 55 | [RegExp | non-empty string, ...] | RegExp | non-empty string 56 | -> Filtering rules. 57 | -> Read more at https://github.com/webpack/terser-webpack-plugin#include 58 | Details: 59 | * options.include[0] should be one of these: 60 | RegExp | non-empty string 61 | -> Filtering rule as regex or string. 62 | Details: 63 | * options.include[0] should be an instance of RegExp. 64 | * options.include[0] should be a non-empty string." 65 | `; 66 | 67 | exports[`validation validate 5`] = ` 68 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 69 | - options.exclude should be one of these: 70 | [RegExp | non-empty string, ...] | RegExp | non-empty string 71 | -> Filtering rules. 72 | -> Read more at https://github.com/webpack/terser-webpack-plugin#exclude 73 | Details: 74 | * options.exclude should be an array: 75 | [RegExp | non-empty string, ...] 76 | * options.exclude should be one of these: 77 | RegExp | non-empty string 78 | -> Filtering rule as regex or string. 79 | Details: 80 | * options.exclude should be an instance of RegExp. 81 | * options.exclude should be a non-empty string." 82 | `; 83 | 84 | exports[`validation validate 6`] = ` 85 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 86 | - options.exclude should be one of these: 87 | [RegExp | non-empty string, ...] | RegExp | non-empty string 88 | -> Filtering rules. 89 | -> Read more at https://github.com/webpack/terser-webpack-plugin#exclude 90 | Details: 91 | * options.exclude[0] should be one of these: 92 | RegExp | non-empty string 93 | -> Filtering rule as regex or string. 94 | Details: 95 | * options.exclude[0] should be an instance of RegExp. 96 | * options.exclude[0] should be a non-empty string." 97 | `; 98 | 99 | exports[`validation validate 7`] = ` 100 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 101 | - options.parallel should be one of these: 102 | boolean | integer 103 | -> Use multi-process parallel running to improve the build speed. 104 | -> Read more at https://github.com/webpack/terser-webpack-plugin#parallel 105 | Details: 106 | * options.parallel should be a boolean. 107 | * options.parallel should be an integer." 108 | `; 109 | 110 | exports[`validation validate 8`] = ` 111 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 112 | - options.parallel should be one of these: 113 | boolean | integer 114 | -> Use multi-process parallel running to improve the build speed. 115 | -> Read more at https://github.com/webpack/terser-webpack-plugin#parallel 116 | Details: 117 | * options.parallel should be a boolean. 118 | * options.parallel should be an integer." 119 | `; 120 | 121 | exports[`validation validate 9`] = ` 122 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 123 | - options.minify should be an instance of function. 124 | -> Allows you to override default minify function. 125 | -> Read more at https://github.com/webpack/terser-webpack-plugin#number" 126 | `; 127 | 128 | exports[`validation validate 10`] = ` 129 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 130 | - options.terserOptions should be an object: 131 | object { … } 132 | -> Options for \`terser\` (by default) or custom \`minify\` function. 133 | -> Read more at https://github.com/webpack/terser-webpack-plugin#terseroptions" 134 | `; 135 | 136 | exports[`validation validate 11`] = ` 137 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 138 | - options.extractComments should be one of these: 139 | boolean | non-empty string | RegExp | function | object { condition?, filename?, banner? } 140 | -> Whether comments shall be extracted to a separate file. 141 | -> Read more at https://github.com/webpack/terser-webpack-plugin#extractcomments 142 | Details: 143 | * options.extractComments.condition should be one of these: 144 | boolean | non-empty string | RegExp | function 145 | -> Condition what comments you need extract. 146 | -> Read more at https://github.com/webpack/terser-webpack-plugin#condition 147 | Details: 148 | * options.extractComments.condition should be a boolean. 149 | * options.extractComments.condition should be a non-empty string. 150 | * options.extractComments.condition should be an instance of RegExp. 151 | * options.extractComments.condition should be an instance of function." 152 | `; 153 | 154 | exports[`validation validate 12`] = ` 155 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 156 | - options.extractComments should be one of these: 157 | boolean | non-empty string | RegExp | function | object { condition?, filename?, banner? } 158 | -> Whether comments shall be extracted to a separate file. 159 | -> Read more at https://github.com/webpack/terser-webpack-plugin#extractcomments 160 | Details: 161 | * options.extractComments.filename should be one of these: 162 | non-empty string | function 163 | -> The file where the extracted comments will be stored. Default is to append the suffix .LICENSE.txt to the original filename. 164 | -> Read more at https://github.com/webpack/terser-webpack-plugin#filename 165 | Details: 166 | * options.extractComments.filename should be a non-empty string. 167 | * options.extractComments.filename should be an instance of function." 168 | `; 169 | 170 | exports[`validation validate 13`] = ` 171 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 172 | - options.extractComments should be one of these: 173 | boolean | non-empty string | RegExp | function | object { condition?, filename?, banner? } 174 | -> Whether comments shall be extracted to a separate file. 175 | -> Read more at https://github.com/webpack/terser-webpack-plugin#extractcomments 176 | Details: 177 | * options.extractComments.banner should be one of these: 178 | boolean | non-empty string | function 179 | -> The banner text that points to the extracted file and will be added on top of the original file 180 | -> Read more at https://github.com/webpack/terser-webpack-plugin#banner 181 | Details: 182 | * options.extractComments.banner should be a boolean. 183 | * options.extractComments.banner should be a non-empty string. 184 | * options.extractComments.banner should be an instance of function." 185 | `; 186 | 187 | exports[`validation validate 14`] = ` 188 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 189 | - options.extractComments has an unknown property 'unknown'. These properties are valid: 190 | object { condition?, filename?, banner? }" 191 | `; 192 | 193 | exports[`validation validate 15`] = ` 194 | "Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema. 195 | - options has an unknown property 'unknown'. These properties are valid: 196 | object { test?, include?, exclude?, terserOptions?, extractComments?, parallel?, minify? }" 197 | `; 198 | -------------------------------------------------------------------------------- /test/parallel-option.test.js: -------------------------------------------------------------------------------- 1 | import os from "os"; 2 | import path from "path"; 3 | 4 | import { Worker } from "jest-worker"; 5 | 6 | import TerserPlugin from "../src/index"; 7 | 8 | import { 9 | compile, 10 | getCompiler, 11 | getErrors, 12 | getWarnings, 13 | readsAssets, 14 | } from "./helpers"; 15 | 16 | jest.mock("os", () => { 17 | const actualOs = jest.requireActual("os"); 18 | const isAvailableParallelism = 19 | typeof actualOs.availableParallelism !== "undefined"; 20 | 21 | const mocked = { 22 | availableParallelism: isAvailableParallelism ? jest.fn(() => 4) : undefined, 23 | cpus: jest.fn(() => ({ length: 4 })), 24 | }; 25 | 26 | return { ...actualOs, ...mocked }; 27 | }); 28 | 29 | // Based on https://github.com/facebook/jest/blob/edde20f75665c2b1e3c8937f758902b5cf28a7b4/packages/jest-runner/src/__tests__/test_runner.test.js 30 | let workerTransform; 31 | let workerEnd; 32 | 33 | const ENABLE_WORKER_THREADS = 34 | typeof process.env.ENABLE_WORKER_THREADS !== "undefined" 35 | ? process.env.ENABLE_WORKER_THREADS === "true" 36 | : true; 37 | 38 | jest.mock("jest-worker", () => ({ 39 | Worker: jest.fn().mockImplementation((workerPath) => ({ 40 | transform: (workerTransform = jest.fn((data) => 41 | require(workerPath).transform(data), 42 | )), 43 | end: (workerEnd = jest.fn()), 44 | getStderr: jest.fn(), 45 | getStdout: jest.fn(), 46 | })), 47 | })); 48 | 49 | const workerPath = require.resolve("../src/minify"); 50 | 51 | const getParallelism = () => { 52 | if (typeof os.availableParallelism !== "undefined") { 53 | return os.availableParallelism(); 54 | } 55 | 56 | return os.cpus().length; 57 | }; 58 | 59 | describe("parallel option", () => { 60 | let compiler; 61 | 62 | beforeEach(() => { 63 | jest.clearAllMocks(); 64 | 65 | compiler = getCompiler({ 66 | entry: { 67 | one: path.resolve(__dirname, "./fixtures/entry.js"), 68 | two: path.resolve(__dirname, "./fixtures/entry.js"), 69 | three: path.resolve(__dirname, "./fixtures/entry.js"), 70 | four: path.resolve(__dirname, "./fixtures/entry.js"), 71 | }, 72 | }); 73 | }); 74 | 75 | it("should match snapshot when a value is not specify", async () => { 76 | new TerserPlugin().apply(compiler); 77 | 78 | const stats = await compile(compiler); 79 | 80 | expect(Worker).toHaveBeenCalledTimes(1); 81 | expect(Worker).toHaveBeenLastCalledWith(workerPath, { 82 | enableWorkerThreads: ENABLE_WORKER_THREADS, 83 | numWorkers: getParallelism() - 1, 84 | }); 85 | expect(workerTransform).toHaveBeenCalledTimes( 86 | Object.keys(stats.compilation.assets).length, 87 | ); 88 | expect(workerEnd).toHaveBeenCalledTimes(1); 89 | 90 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 91 | expect(getErrors(stats)).toMatchSnapshot("errors"); 92 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 93 | }); 94 | 95 | it('should match snapshot for the "false" value', async () => { 96 | new TerserPlugin({ parallel: false }).apply(compiler); 97 | 98 | const stats = await compile(compiler); 99 | 100 | expect(Worker).toHaveBeenCalledTimes(0); 101 | 102 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 103 | expect(getErrors(stats)).toMatchSnapshot("errors"); 104 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 105 | }); 106 | 107 | it('should match snapshot for the "true" value', async () => { 108 | new TerserPlugin({ parallel: true }).apply(compiler); 109 | 110 | const stats = await compile(compiler); 111 | 112 | expect(Worker).toHaveBeenCalledTimes(1); 113 | expect(Worker).toHaveBeenLastCalledWith(workerPath, { 114 | enableWorkerThreads: ENABLE_WORKER_THREADS, 115 | numWorkers: getParallelism() - 1, 116 | }); 117 | expect(workerTransform).toHaveBeenCalledTimes( 118 | Object.keys(stats.compilation.assets).length, 119 | ); 120 | expect(workerEnd).toHaveBeenCalledTimes(1); 121 | 122 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 123 | expect(getErrors(stats)).toMatchSnapshot("errors"); 124 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 125 | }); 126 | 127 | it('should match snapshot for the "undefined" value', async () => { 128 | new TerserPlugin({ parallel: undefined }).apply(compiler); 129 | 130 | const stats = await compile(compiler); 131 | 132 | expect(Worker).toHaveBeenCalledTimes(1); 133 | expect(Worker).toHaveBeenLastCalledWith(workerPath, { 134 | enableWorkerThreads: ENABLE_WORKER_THREADS, 135 | numWorkers: getParallelism() - 1, 136 | }); 137 | expect(workerTransform).toHaveBeenCalledTimes( 138 | Object.keys(stats.compilation.assets).length, 139 | ); 140 | expect(workerEnd).toHaveBeenCalledTimes(1); 141 | 142 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 143 | expect(getErrors(stats)).toMatchSnapshot("errors"); 144 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 145 | }); 146 | 147 | it('should match snapshot for the "2" value', async () => { 148 | new TerserPlugin({ parallel: 2 }).apply(compiler); 149 | 150 | const stats = await compile(compiler); 151 | 152 | expect(Worker).toHaveBeenCalledTimes(1); 153 | expect(Worker).toHaveBeenLastCalledWith(workerPath, { 154 | enableWorkerThreads: ENABLE_WORKER_THREADS, 155 | numWorkers: 2, 156 | }); 157 | expect(workerTransform).toHaveBeenCalledTimes( 158 | Object.keys(stats.compilation.assets).length, 159 | ); 160 | expect(workerEnd).toHaveBeenCalledTimes(1); 161 | 162 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 163 | expect(getErrors(stats)).toMatchSnapshot("errors"); 164 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 165 | }); 166 | 167 | it('should match snapshot for the "true" value when only one file passed', async () => { 168 | compiler = getCompiler({ 169 | entry: path.resolve(__dirname, "./fixtures/entry.js"), 170 | }); 171 | 172 | new TerserPlugin({ parallel: true }).apply(compiler); 173 | 174 | const stats = await compile(compiler); 175 | 176 | expect(Worker).toHaveBeenCalledTimes(1); 177 | expect(Worker).toHaveBeenLastCalledWith(workerPath, { 178 | enableWorkerThreads: ENABLE_WORKER_THREADS, 179 | numWorkers: Math.min(1, os.cpus().length - 1), 180 | }); 181 | expect(workerTransform).toHaveBeenCalledTimes( 182 | Object.keys(stats.compilation.assets).length, 183 | ); 184 | expect(workerEnd).toHaveBeenCalledTimes(1); 185 | 186 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 187 | expect(getErrors(stats)).toMatchSnapshot("errors"); 188 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 189 | }); 190 | 191 | it('should match snapshot for the "true" value and the number of files is less than the number of cores', async () => { 192 | const entries = {}; 193 | 194 | for (let i = 0; i < os.cpus().length / 2; i++) { 195 | entries[`entry-${i}`] = path.resolve(__dirname, "./fixtures/entry.js"); 196 | } 197 | 198 | compiler = getCompiler({ entry: entries }); 199 | 200 | new TerserPlugin({ parallel: true }).apply(compiler); 201 | 202 | const stats = await compile(compiler); 203 | 204 | expect(Worker).toHaveBeenCalledTimes(1); 205 | expect(Worker).toHaveBeenLastCalledWith(workerPath, { 206 | enableWorkerThreads: ENABLE_WORKER_THREADS, 207 | numWorkers: Math.min(Object.keys(entries).length, os.cpus().length - 1), 208 | }); 209 | expect(workerTransform).toHaveBeenCalledTimes( 210 | Object.keys(stats.compilation.assets).length, 211 | ); 212 | expect(workerEnd).toHaveBeenCalledTimes(1); 213 | 214 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 215 | expect(getErrors(stats)).toMatchSnapshot("errors"); 216 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 217 | }); 218 | 219 | it('should match snapshot for the "true" value and the number of files is same than the number of cores', async () => { 220 | const entries = {}; 221 | 222 | for (let i = 0; i < os.cpus().length; i++) { 223 | entries[`entry-${i}`] = path.resolve(__dirname, "./fixtures/entry.js"); 224 | } 225 | 226 | compiler = getCompiler({ entry: entries }); 227 | 228 | new TerserPlugin({ parallel: true }).apply(compiler); 229 | 230 | const stats = await compile(compiler); 231 | 232 | expect(Worker).toHaveBeenCalledTimes(1); 233 | expect(Worker).toHaveBeenLastCalledWith(workerPath, { 234 | enableWorkerThreads: ENABLE_WORKER_THREADS, 235 | numWorkers: Math.min(Object.keys(entries).length, os.cpus().length - 1), 236 | }); 237 | expect(workerTransform).toHaveBeenCalledTimes( 238 | Object.keys(stats.compilation.assets).length, 239 | ); 240 | expect(workerEnd).toHaveBeenCalledTimes(1); 241 | 242 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 243 | expect(getErrors(stats)).toMatchSnapshot("errors"); 244 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 245 | }); 246 | 247 | it('should match snapshot for the "true" value and the number of files is more than the number of cores', async () => { 248 | const entries = {}; 249 | 250 | for (let i = 0; i < os.cpus().length * 2; i++) { 251 | entries[`entry-${i}`] = path.resolve(__dirname, "./fixtures/entry.js"); 252 | } 253 | 254 | compiler = getCompiler({ 255 | entry: { 256 | one: path.resolve(__dirname, "./fixtures/entry.js"), 257 | two: path.resolve(__dirname, "./fixtures/entry.js"), 258 | three: path.resolve(__dirname, "./fixtures/entry.js"), 259 | four: path.resolve(__dirname, "./fixtures/entry.js"), 260 | five: path.resolve(__dirname, "./fixtures/entry.js"), 261 | six: path.resolve(__dirname, "./fixtures/entry.js"), 262 | seven: path.resolve(__dirname, "./fixtures/entry.js"), 263 | eight: path.resolve(__dirname, "./fixtures/entry.js"), 264 | }, 265 | }); 266 | 267 | new TerserPlugin({ parallel: true }).apply(compiler); 268 | 269 | const stats = await compile(compiler); 270 | 271 | expect(Worker).toHaveBeenCalledTimes(1); 272 | expect(Worker).toHaveBeenLastCalledWith(workerPath, { 273 | enableWorkerThreads: ENABLE_WORKER_THREADS, 274 | numWorkers: Math.min(Object.keys(entries).length, os.cpus().length - 1), 275 | }); 276 | expect(workerTransform).toHaveBeenCalledTimes( 277 | Object.keys(stats.compilation.assets).length, 278 | ); 279 | expect(workerEnd).toHaveBeenCalledTimes(1); 280 | 281 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 282 | expect(getErrors(stats)).toMatchSnapshot("errors"); 283 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 284 | }); 285 | }); 286 | -------------------------------------------------------------------------------- /test/__snapshots__/parallel-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`parallel option should match snapshot for the "2" value: assets 1`] = ` 4 | Object { 5 | "four.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 6 | "one.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 7 | "three.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 8 | "two.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 9 | } 10 | `; 11 | 12 | exports[`parallel option should match snapshot for the "2" value: errors 1`] = `Array []`; 13 | 14 | exports[`parallel option should match snapshot for the "2" value: warnings 1`] = `Array []`; 15 | 16 | exports[`parallel option should match snapshot for the "false" value: assets 1`] = ` 17 | Object { 18 | "four.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 19 | "one.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 20 | "three.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 21 | "two.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 22 | } 23 | `; 24 | 25 | exports[`parallel option should match snapshot for the "false" value: errors 1`] = `Array []`; 26 | 27 | exports[`parallel option should match snapshot for the "false" value: warnings 1`] = `Array []`; 28 | 29 | exports[`parallel option should match snapshot for the "true" value and the number of files is less than the number of cores: assets 1`] = ` 30 | Object { 31 | "entry-0.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 32 | "entry-1.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 33 | } 34 | `; 35 | 36 | exports[`parallel option should match snapshot for the "true" value and the number of files is less than the number of cores: errors 1`] = `Array []`; 37 | 38 | exports[`parallel option should match snapshot for the "true" value and the number of files is less than the number of cores: warnings 1`] = `Array []`; 39 | 40 | exports[`parallel option should match snapshot for the "true" value and the number of files is more than the number of cores: assets 1`] = ` 41 | Object { 42 | "eight.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 43 | "five.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 44 | "four.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 45 | "one.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 46 | "seven.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 47 | "six.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 48 | "three.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 49 | "two.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 50 | } 51 | `; 52 | 53 | exports[`parallel option should match snapshot for the "true" value and the number of files is more than the number of cores: errors 1`] = `Array []`; 54 | 55 | exports[`parallel option should match snapshot for the "true" value and the number of files is more than the number of cores: warnings 1`] = `Array []`; 56 | 57 | exports[`parallel option should match snapshot for the "true" value and the number of files is same than the number of cores: assets 1`] = ` 58 | Object { 59 | "entry-0.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 60 | "entry-1.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 61 | "entry-2.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 62 | "entry-3.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 63 | } 64 | `; 65 | 66 | exports[`parallel option should match snapshot for the "true" value and the number of files is same than the number of cores: errors 1`] = `Array []`; 67 | 68 | exports[`parallel option should match snapshot for the "true" value and the number of files is same than the number of cores: warnings 1`] = `Array []`; 69 | 70 | exports[`parallel option should match snapshot for the "true" value when only one file passed: assets 1`] = ` 71 | Object { 72 | "main.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 73 | } 74 | `; 75 | 76 | exports[`parallel option should match snapshot for the "true" value when only one file passed: errors 1`] = `Array []`; 77 | 78 | exports[`parallel option should match snapshot for the "true" value when only one file passed: warnings 1`] = `Array []`; 79 | 80 | exports[`parallel option should match snapshot for the "true" value: assets 1`] = ` 81 | Object { 82 | "four.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 83 | "one.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 84 | "three.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 85 | "two.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 86 | } 87 | `; 88 | 89 | exports[`parallel option should match snapshot for the "true" value: errors 1`] = `Array []`; 90 | 91 | exports[`parallel option should match snapshot for the "true" value: warnings 1`] = `Array []`; 92 | 93 | exports[`parallel option should match snapshot for the "undefined" value: assets 1`] = ` 94 | Object { 95 | "four.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 96 | "one.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 97 | "three.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 98 | "two.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 99 | } 100 | `; 101 | 102 | exports[`parallel option should match snapshot for the "undefined" value: errors 1`] = `Array []`; 103 | 104 | exports[`parallel option should match snapshot for the "undefined" value: warnings 1`] = `Array []`; 105 | 106 | exports[`parallel option should match snapshot when a value is not specify: assets 1`] = ` 107 | Object { 108 | "four.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 109 | "one.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 110 | "three.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 111 | "two.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 112 | } 113 | `; 114 | 115 | exports[`parallel option should match snapshot when a value is not specify: errors 1`] = `Array []`; 116 | 117 | exports[`parallel option should match snapshot when a value is not specify: warnings 1`] = `Array []`; 118 | -------------------------------------------------------------------------------- /test/worker.test.js: -------------------------------------------------------------------------------- 1 | import serialize from "serialize-javascript"; 2 | 3 | import { transform } from "../src/minify"; 4 | import { terserMinify } from "../src/utils"; 5 | 6 | describe("worker", () => { 7 | it('should match snapshot when options.extractComments is "false"', async () => { 8 | const options = { 9 | name: "test1.js", 10 | input: 11 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 12 | minimizer: { 13 | implementation: terserMinify, 14 | }, 15 | extractComments: false, 16 | }; 17 | const workerResult = await transform(serialize(options)); 18 | 19 | expect(workerResult).toMatchSnapshot(); 20 | }); 21 | 22 | it('should match snapshot when options.extractComments is "true"', async () => { 23 | const options = { 24 | name: "test1.js", 25 | input: 26 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 27 | minimizer: { 28 | implementation: terserMinify, 29 | }, 30 | extractComments: true, 31 | }; 32 | const workerResult = await transform(serialize(options)); 33 | 34 | expect(workerResult).toMatchSnapshot(); 35 | }); 36 | 37 | it("should match snapshot when options.extractComments is RegExp", async () => { 38 | const options = { 39 | name: "test1.js", 40 | input: 41 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 42 | minimizer: { 43 | implementation: terserMinify, 44 | }, 45 | extractComments: /hello/, 46 | }; 47 | const workerResult = await transform(serialize(options)); 48 | 49 | expect(workerResult).toMatchSnapshot(); 50 | }); 51 | 52 | it("should match snapshot when options.extractComments is Function", async () => { 53 | const options = { 54 | name: "test1.js", 55 | input: 56 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 57 | minimizer: { 58 | implementation: terserMinify, 59 | }, 60 | extractComments: () => true, 61 | }; 62 | const workerResult = await transform(serialize(options)); 63 | 64 | expect(workerResult).toMatchSnapshot(); 65 | }); 66 | 67 | it("should match snapshot when options.extractComments is empty Object", async () => { 68 | const options = { 69 | name: "test1.js", 70 | input: 71 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 72 | minimizer: { 73 | implementation: terserMinify, 74 | }, 75 | extractComments: {}, 76 | }; 77 | const workerResult = await transform(serialize(options)); 78 | 79 | expect(workerResult).toMatchSnapshot(); 80 | }); 81 | 82 | it('should match snapshot when options.extractComments is Object with "true" value', async () => { 83 | const options = { 84 | name: "test1.js", 85 | input: 86 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 87 | minimizer: { 88 | implementation: terserMinify, 89 | }, 90 | extractComments: { 91 | condition: true, 92 | }, 93 | }; 94 | const workerResult = await transform(serialize(options)); 95 | 96 | expect(workerResult).toMatchSnapshot(); 97 | }); 98 | 99 | it('should match snapshot when options.extractComments is Object with "some" value', async () => { 100 | const options = { 101 | name: "test1.js", 102 | input: 103 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 104 | minimizer: { 105 | implementation: terserMinify, 106 | }, 107 | extractComments: { 108 | condition: "some", 109 | }, 110 | }; 111 | const workerResult = await transform(serialize(options)); 112 | 113 | expect(workerResult).toMatchSnapshot(); 114 | }); 115 | 116 | it('should match snapshot when options.extractComments is Object with "all" value', async () => { 117 | const options = { 118 | name: "test1.js", 119 | input: 120 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 121 | minimizer: { 122 | implementation: terserMinify, 123 | }, 124 | extractComments: { 125 | condition: "all", 126 | }, 127 | }; 128 | const workerResult = await transform(serialize(options)); 129 | 130 | expect(workerResult).toMatchSnapshot(); 131 | }); 132 | 133 | it('should match snapshot when options.extractComments is "all" value', async () => { 134 | const options = { 135 | name: "test1.js", 136 | input: 137 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 138 | minimizer: { 139 | implementation: terserMinify, 140 | }, 141 | extractComments: "all", 142 | }; 143 | const workerResult = await transform(serialize(options)); 144 | 145 | expect(workerResult).toMatchSnapshot(); 146 | }); 147 | 148 | it('should match snapshot when options.extractComments is "some" value', async () => { 149 | const options = { 150 | name: "test1.js", 151 | input: 152 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 153 | minimizer: { 154 | implementation: terserMinify, 155 | }, 156 | extractComments: "some", 157 | }; 158 | const workerResult = await transform(serialize(options)); 159 | 160 | expect(workerResult).toMatchSnapshot(); 161 | }); 162 | 163 | it("normalizes when minimizerOptions.output.comments is string: all", async () => { 164 | const options = { 165 | name: "test2.js", 166 | input: 167 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 168 | minimizer: { 169 | implementation: terserMinify, 170 | options: { 171 | output: { 172 | comments: "all", 173 | }, 174 | }, 175 | }, 176 | }; 177 | const workerResult = await transform(serialize(options)); 178 | 179 | expect(workerResult).toMatchSnapshot(); 180 | }); 181 | 182 | it("should match snapshot when minimizerOptions.compress.comments is boolean", async () => { 183 | const options = { 184 | name: "test3.js", 185 | input: 186 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 187 | minimizer: { 188 | implementation: terserMinify, 189 | options: { 190 | compress: true, 191 | }, 192 | }, 193 | }; 194 | const workerResult = await transform(serialize(options)); 195 | 196 | expect(workerResult).toMatchSnapshot(); 197 | }); 198 | 199 | it("should match snapshot when minimizerOptions.compress.comments is object", async () => { 200 | const options = { 201 | name: "test3.js", 202 | input: 203 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 204 | minimizer: { 205 | implementation: terserMinify, 206 | options: { 207 | compress: { 208 | passes: 2, 209 | }, 210 | }, 211 | }, 212 | }; 213 | const workerResult = await transform(serialize(options)); 214 | 215 | expect(workerResult).toMatchSnapshot(); 216 | }); 217 | 218 | it("should match snapshot when minimizerOptions.output.comments is string: some", async () => { 219 | const options = { 220 | name: "test3.js", 221 | input: 222 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 223 | minimizer: { 224 | implementation: terserMinify, 225 | options: { 226 | output: { 227 | comments: "some", 228 | }, 229 | }, 230 | }, 231 | }; 232 | const workerResult = await transform(serialize(options)); 233 | 234 | expect(workerResult).toMatchSnapshot(); 235 | }); 236 | 237 | it("should match snapshot when minimizerOptions.extractComments is number", async () => { 238 | const options = { 239 | name: "test4.js", 240 | input: 241 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 242 | minimizer: { 243 | implementation: terserMinify, 244 | options: { 245 | output: { 246 | comments: "some", 247 | }, 248 | }, 249 | }, 250 | extractComments: 1, 251 | }; 252 | const workerResult = await transform(serialize(options)); 253 | 254 | expect(workerResult).toMatchSnapshot(); 255 | }); 256 | 257 | it("should match snapshot with extract option set to a single file", async () => { 258 | const options = { 259 | name: "test5.js", 260 | input: "/******/ function hello(a) {console.log(a)}", 261 | minimizer: { 262 | implementation: terserMinify, 263 | options: { 264 | output: { 265 | comments: "all", 266 | }, 267 | }, 268 | }, 269 | extractComments: { 270 | condition: "should be extracted", 271 | filename: (file) => file.filename.replace(/(\.\w+)$/, ".license$1"), 272 | banner: (licenseFile) => 273 | `License information can be found in ${licenseFile}`, 274 | }, 275 | }; 276 | const workerResult = await transform(serialize(options)); 277 | 278 | expect(workerResult).toMatchSnapshot(); 279 | }); 280 | 281 | it("should match snapshot with options.inputSourceMap", async () => { 282 | const options = { 283 | minimizer: { 284 | implementation: terserMinify, 285 | }, 286 | name: "test6.js", 287 | input: "function foo(x) { if (x) { return bar(); not_called1(); } }", 288 | inputSourceMap: { 289 | version: 3, 290 | sources: ["test1.js"], 291 | names: ["foo", "x", "bar", "not_called1"], 292 | mappings: "AAAA,QAASA,KAAIC,GACT,GAAIA,EAAG,CACH,MAAOC,MACPC", 293 | }, 294 | }; 295 | const workerResult = await transform(serialize(options)); 296 | 297 | expect(workerResult).toMatchSnapshot(); 298 | }); 299 | 300 | it('should match snapshot when options.output.comments is "some"', async () => { 301 | const options = { 302 | name: "test1.js", 303 | input: 304 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 305 | minimizer: { 306 | implementation: (item) => item["test1.js"], 307 | options: { 308 | output: { 309 | comments: "some", 310 | }, 311 | }, 312 | }, 313 | }; 314 | const workerResult = await transform(serialize(options)); 315 | 316 | expect(workerResult).toMatchSnapshot(); 317 | }); 318 | 319 | it('should match snapshot when minimizerOptions.mangle is "null"', async () => { 320 | const options = { 321 | name: "test4.js", 322 | input: 323 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 324 | minimizer: { 325 | implementation: terserMinify, 326 | options: { 327 | mangle: null, 328 | }, 329 | }, 330 | }; 331 | const workerResult = await transform(serialize(options)); 332 | 333 | expect(workerResult).toMatchSnapshot(); 334 | }); 335 | 336 | it("should match snapshot when minimizerOptions.mangle is boolean", async () => { 337 | const options = { 338 | name: "test4.js", 339 | input: 340 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 341 | minimizer: { 342 | implementation: terserMinify, 343 | options: { 344 | mangle: true, 345 | }, 346 | }, 347 | }; 348 | const workerResult = await transform(serialize(options)); 349 | 350 | expect(workerResult).toMatchSnapshot(); 351 | }); 352 | 353 | it("should match snapshot when minimizerOptions.mangle is object", async () => { 354 | const options = { 355 | name: "test4.js", 356 | input: 357 | "var foo = 1;/* hello */\n// Comment\n/* duplicate */\n/* duplicate */", 358 | minimizer: { 359 | implementation: terserMinify, 360 | options: { 361 | mangle: { toplevel: true }, 362 | }, 363 | }, 364 | }; 365 | const workerResult = await transform(serialize(options)); 366 | 367 | expect(workerResult).toMatchSnapshot(); 368 | }); 369 | }); 370 | -------------------------------------------------------------------------------- /test/__snapshots__/exclude-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`exclude option should match snapshot for a single RegExp value: assets 1`] = ` 4 | Object { 5 | "entry.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 6 | "excluded1.js": "/******/ (() => { // webpackBootstrap 7 | /******/ var __webpack_modules__ = ({ 8 | 9 | /***/ 508: 10 | /***/ ((module) => { 11 | 12 | module.exports = function Bar1() { 13 | const b = 2 + 2; 14 | console.log(b + 1 + 2); 15 | }; 16 | 17 | /***/ }) 18 | 19 | /******/ }); 20 | /************************************************************************/ 21 | /******/ // The module cache 22 | /******/ var __webpack_module_cache__ = {}; 23 | /******/ 24 | /******/ // The require function 25 | /******/ function __webpack_require__(moduleId) { 26 | /******/ // Check if module is in cache 27 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 28 | /******/ if (cachedModule !== undefined) { 29 | /******/ return cachedModule.exports; 30 | /******/ } 31 | /******/ // Create a new module (and put it into the cache) 32 | /******/ var module = __webpack_module_cache__[moduleId] = { 33 | /******/ // no module.id needed 34 | /******/ // no module.loaded needed 35 | /******/ exports: {} 36 | /******/ }; 37 | /******/ 38 | /******/ // Execute the module function 39 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 40 | /******/ 41 | /******/ // Return the exports of the module 42 | /******/ return module.exports; 43 | /******/ } 44 | /******/ 45 | /************************************************************************/ 46 | /******/ 47 | /******/ // startup 48 | /******/ // Load entry module and return exports 49 | /******/ // This entry module is referenced by other modules so it can't be inlined 50 | /******/ var __webpack_exports__ = __webpack_require__(508); 51 | /******/ 52 | /******/ })() 53 | ;", 54 | "excluded2.js": "(()=>{var r={93:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(93)})();", 55 | } 56 | `; 57 | 58 | exports[`exclude option should match snapshot for a single RegExp value: errors 1`] = `Array []`; 59 | 60 | exports[`exclude option should match snapshot for a single RegExp value: warnings 1`] = `Array []`; 61 | 62 | exports[`exclude option should match snapshot for a single String value: assets 1`] = ` 63 | Object { 64 | "entry.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 65 | "excluded1.js": "/******/ (() => { // webpackBootstrap 66 | /******/ var __webpack_modules__ = ({ 67 | 68 | /***/ 508: 69 | /***/ ((module) => { 70 | 71 | module.exports = function Bar1() { 72 | const b = 2 + 2; 73 | console.log(b + 1 + 2); 74 | }; 75 | 76 | /***/ }) 77 | 78 | /******/ }); 79 | /************************************************************************/ 80 | /******/ // The module cache 81 | /******/ var __webpack_module_cache__ = {}; 82 | /******/ 83 | /******/ // The require function 84 | /******/ function __webpack_require__(moduleId) { 85 | /******/ // Check if module is in cache 86 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 87 | /******/ if (cachedModule !== undefined) { 88 | /******/ return cachedModule.exports; 89 | /******/ } 90 | /******/ // Create a new module (and put it into the cache) 91 | /******/ var module = __webpack_module_cache__[moduleId] = { 92 | /******/ // no module.id needed 93 | /******/ // no module.loaded needed 94 | /******/ exports: {} 95 | /******/ }; 96 | /******/ 97 | /******/ // Execute the module function 98 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 99 | /******/ 100 | /******/ // Return the exports of the module 101 | /******/ return module.exports; 102 | /******/ } 103 | /******/ 104 | /************************************************************************/ 105 | /******/ 106 | /******/ // startup 107 | /******/ // Load entry module and return exports 108 | /******/ // This entry module is referenced by other modules so it can't be inlined 109 | /******/ var __webpack_exports__ = __webpack_require__(508); 110 | /******/ 111 | /******/ })() 112 | ;", 113 | "excluded2.js": "(()=>{var r={93:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(93)})();", 114 | } 115 | `; 116 | 117 | exports[`exclude option should match snapshot for a single String value: errors 1`] = `Array []`; 118 | 119 | exports[`exclude option should match snapshot for a single String value: warnings 1`] = `Array []`; 120 | 121 | exports[`exclude option should match snapshot for multiple RegExp values: assets 1`] = ` 122 | Object { 123 | "entry.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 124 | "excluded1.js": "/******/ (() => { // webpackBootstrap 125 | /******/ var __webpack_modules__ = ({ 126 | 127 | /***/ 508: 128 | /***/ ((module) => { 129 | 130 | module.exports = function Bar1() { 131 | const b = 2 + 2; 132 | console.log(b + 1 + 2); 133 | }; 134 | 135 | /***/ }) 136 | 137 | /******/ }); 138 | /************************************************************************/ 139 | /******/ // The module cache 140 | /******/ var __webpack_module_cache__ = {}; 141 | /******/ 142 | /******/ // The require function 143 | /******/ function __webpack_require__(moduleId) { 144 | /******/ // Check if module is in cache 145 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 146 | /******/ if (cachedModule !== undefined) { 147 | /******/ return cachedModule.exports; 148 | /******/ } 149 | /******/ // Create a new module (and put it into the cache) 150 | /******/ var module = __webpack_module_cache__[moduleId] = { 151 | /******/ // no module.id needed 152 | /******/ // no module.loaded needed 153 | /******/ exports: {} 154 | /******/ }; 155 | /******/ 156 | /******/ // Execute the module function 157 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 158 | /******/ 159 | /******/ // Return the exports of the module 160 | /******/ return module.exports; 161 | /******/ } 162 | /******/ 163 | /************************************************************************/ 164 | /******/ 165 | /******/ // startup 166 | /******/ // Load entry module and return exports 167 | /******/ // This entry module is referenced by other modules so it can't be inlined 168 | /******/ var __webpack_exports__ = __webpack_require__(508); 169 | /******/ 170 | /******/ })() 171 | ;", 172 | "excluded2.js": "/******/ (() => { // webpackBootstrap 173 | /******/ var __webpack_modules__ = ({ 174 | 175 | /***/ 93: 176 | /***/ ((module) => { 177 | 178 | module.exports = function Bar2() { 179 | const b = 2 + 2; 180 | console.log(b + 1 + 2); 181 | }; 182 | 183 | /***/ }) 184 | 185 | /******/ }); 186 | /************************************************************************/ 187 | /******/ // The module cache 188 | /******/ var __webpack_module_cache__ = {}; 189 | /******/ 190 | /******/ // The require function 191 | /******/ function __webpack_require__(moduleId) { 192 | /******/ // Check if module is in cache 193 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 194 | /******/ if (cachedModule !== undefined) { 195 | /******/ return cachedModule.exports; 196 | /******/ } 197 | /******/ // Create a new module (and put it into the cache) 198 | /******/ var module = __webpack_module_cache__[moduleId] = { 199 | /******/ // no module.id needed 200 | /******/ // no module.loaded needed 201 | /******/ exports: {} 202 | /******/ }; 203 | /******/ 204 | /******/ // Execute the module function 205 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 206 | /******/ 207 | /******/ // Return the exports of the module 208 | /******/ return module.exports; 209 | /******/ } 210 | /******/ 211 | /************************************************************************/ 212 | /******/ 213 | /******/ // startup 214 | /******/ // Load entry module and return exports 215 | /******/ // This entry module is referenced by other modules so it can't be inlined 216 | /******/ var __webpack_exports__ = __webpack_require__(93); 217 | /******/ 218 | /******/ })() 219 | ;", 220 | } 221 | `; 222 | 223 | exports[`exclude option should match snapshot for multiple RegExp values: errors 1`] = `Array []`; 224 | 225 | exports[`exclude option should match snapshot for multiple RegExp values: warnings 1`] = `Array []`; 226 | 227 | exports[`exclude option should match snapshot for multiple String values: assets 1`] = ` 228 | Object { 229 | "entry.js": "(()=>{var r={921:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(921)})();", 230 | "excluded1.js": "/******/ (() => { // webpackBootstrap 231 | /******/ var __webpack_modules__ = ({ 232 | 233 | /***/ 508: 234 | /***/ ((module) => { 235 | 236 | module.exports = function Bar1() { 237 | const b = 2 + 2; 238 | console.log(b + 1 + 2); 239 | }; 240 | 241 | /***/ }) 242 | 243 | /******/ }); 244 | /************************************************************************/ 245 | /******/ // The module cache 246 | /******/ var __webpack_module_cache__ = {}; 247 | /******/ 248 | /******/ // The require function 249 | /******/ function __webpack_require__(moduleId) { 250 | /******/ // Check if module is in cache 251 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 252 | /******/ if (cachedModule !== undefined) { 253 | /******/ return cachedModule.exports; 254 | /******/ } 255 | /******/ // Create a new module (and put it into the cache) 256 | /******/ var module = __webpack_module_cache__[moduleId] = { 257 | /******/ // no module.id needed 258 | /******/ // no module.loaded needed 259 | /******/ exports: {} 260 | /******/ }; 261 | /******/ 262 | /******/ // Execute the module function 263 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 264 | /******/ 265 | /******/ // Return the exports of the module 266 | /******/ return module.exports; 267 | /******/ } 268 | /******/ 269 | /************************************************************************/ 270 | /******/ 271 | /******/ // startup 272 | /******/ // Load entry module and return exports 273 | /******/ // This entry module is referenced by other modules so it can't be inlined 274 | /******/ var __webpack_exports__ = __webpack_require__(508); 275 | /******/ 276 | /******/ })() 277 | ;", 278 | "excluded2.js": "/******/ (() => { // webpackBootstrap 279 | /******/ var __webpack_modules__ = ({ 280 | 281 | /***/ 93: 282 | /***/ ((module) => { 283 | 284 | module.exports = function Bar2() { 285 | const b = 2 + 2; 286 | console.log(b + 1 + 2); 287 | }; 288 | 289 | /***/ }) 290 | 291 | /******/ }); 292 | /************************************************************************/ 293 | /******/ // The module cache 294 | /******/ var __webpack_module_cache__ = {}; 295 | /******/ 296 | /******/ // The require function 297 | /******/ function __webpack_require__(moduleId) { 298 | /******/ // Check if module is in cache 299 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 300 | /******/ if (cachedModule !== undefined) { 301 | /******/ return cachedModule.exports; 302 | /******/ } 303 | /******/ // Create a new module (and put it into the cache) 304 | /******/ var module = __webpack_module_cache__[moduleId] = { 305 | /******/ // no module.id needed 306 | /******/ // no module.loaded needed 307 | /******/ exports: {} 308 | /******/ }; 309 | /******/ 310 | /******/ // Execute the module function 311 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 312 | /******/ 313 | /******/ // Return the exports of the module 314 | /******/ return module.exports; 315 | /******/ } 316 | /******/ 317 | /************************************************************************/ 318 | /******/ 319 | /******/ // startup 320 | /******/ // Load entry module and return exports 321 | /******/ // This entry module is referenced by other modules so it can't be inlined 322 | /******/ var __webpack_exports__ = __webpack_require__(93); 323 | /******/ 324 | /******/ })() 325 | ;", 326 | } 327 | `; 328 | 329 | exports[`exclude option should match snapshot for multiple String values: errors 1`] = `Array []`; 330 | 331 | exports[`exclude option should match snapshot for multiple String values: warnings 1`] = `Array []`; 332 | -------------------------------------------------------------------------------- /test/__snapshots__/include-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`include option should match snapshot for a single RegExp value: assets 1`] = ` 4 | Object { 5 | "entry.js": "/******/ (() => { // webpackBootstrap 6 | /******/ var __webpack_modules__ = ({ 7 | 8 | /***/ 921: 9 | /***/ ((module) => { 10 | 11 | // foo 12 | /* @preserve*/ 13 | // bar 14 | const a = (/* unused pure expression or super */ null && (2 + 2)); 15 | 16 | module.exports = function Foo() { 17 | const b = 2 + 2; 18 | console.log(b + 1 + 2); 19 | }; 20 | 21 | 22 | /***/ }) 23 | 24 | /******/ }); 25 | /************************************************************************/ 26 | /******/ // The module cache 27 | /******/ var __webpack_module_cache__ = {}; 28 | /******/ 29 | /******/ // The require function 30 | /******/ function __webpack_require__(moduleId) { 31 | /******/ // Check if module is in cache 32 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 33 | /******/ if (cachedModule !== undefined) { 34 | /******/ return cachedModule.exports; 35 | /******/ } 36 | /******/ // Create a new module (and put it into the cache) 37 | /******/ var module = __webpack_module_cache__[moduleId] = { 38 | /******/ // no module.id needed 39 | /******/ // no module.loaded needed 40 | /******/ exports: {} 41 | /******/ }; 42 | /******/ 43 | /******/ // Execute the module function 44 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 45 | /******/ 46 | /******/ // Return the exports of the module 47 | /******/ return module.exports; 48 | /******/ } 49 | /******/ 50 | /************************************************************************/ 51 | /******/ 52 | /******/ // startup 53 | /******/ // Load entry module and return exports 54 | /******/ // This entry module is referenced by other modules so it can't be inlined 55 | /******/ var __webpack_exports__ = __webpack_require__(921); 56 | /******/ 57 | /******/ })() 58 | ;", 59 | "included1.js": "(()=>{var r={46:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(46)})();", 60 | "included2.js": "/******/ (() => { // webpackBootstrap 61 | /******/ var __webpack_modules__ = ({ 62 | 63 | /***/ 515: 64 | /***/ ((module) => { 65 | 66 | module.exports = function Bar2() { 67 | const b = 2 + 2; 68 | console.log(b + 1 + 2); 69 | }; 70 | 71 | 72 | /***/ }) 73 | 74 | /******/ }); 75 | /************************************************************************/ 76 | /******/ // The module cache 77 | /******/ var __webpack_module_cache__ = {}; 78 | /******/ 79 | /******/ // The require function 80 | /******/ function __webpack_require__(moduleId) { 81 | /******/ // Check if module is in cache 82 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 83 | /******/ if (cachedModule !== undefined) { 84 | /******/ return cachedModule.exports; 85 | /******/ } 86 | /******/ // Create a new module (and put it into the cache) 87 | /******/ var module = __webpack_module_cache__[moduleId] = { 88 | /******/ // no module.id needed 89 | /******/ // no module.loaded needed 90 | /******/ exports: {} 91 | /******/ }; 92 | /******/ 93 | /******/ // Execute the module function 94 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 95 | /******/ 96 | /******/ // Return the exports of the module 97 | /******/ return module.exports; 98 | /******/ } 99 | /******/ 100 | /************************************************************************/ 101 | /******/ 102 | /******/ // startup 103 | /******/ // Load entry module and return exports 104 | /******/ // This entry module is referenced by other modules so it can't be inlined 105 | /******/ var __webpack_exports__ = __webpack_require__(515); 106 | /******/ 107 | /******/ })() 108 | ;", 109 | } 110 | `; 111 | 112 | exports[`include option should match snapshot for a single RegExp value: errors 1`] = `Array []`; 113 | 114 | exports[`include option should match snapshot for a single RegExp value: warnings 1`] = `Array []`; 115 | 116 | exports[`include option should match snapshot for a single String value: assets 1`] = ` 117 | Object { 118 | "entry.js": "/******/ (() => { // webpackBootstrap 119 | /******/ var __webpack_modules__ = ({ 120 | 121 | /***/ 921: 122 | /***/ ((module) => { 123 | 124 | // foo 125 | /* @preserve*/ 126 | // bar 127 | const a = (/* unused pure expression or super */ null && (2 + 2)); 128 | 129 | module.exports = function Foo() { 130 | const b = 2 + 2; 131 | console.log(b + 1 + 2); 132 | }; 133 | 134 | 135 | /***/ }) 136 | 137 | /******/ }); 138 | /************************************************************************/ 139 | /******/ // The module cache 140 | /******/ var __webpack_module_cache__ = {}; 141 | /******/ 142 | /******/ // The require function 143 | /******/ function __webpack_require__(moduleId) { 144 | /******/ // Check if module is in cache 145 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 146 | /******/ if (cachedModule !== undefined) { 147 | /******/ return cachedModule.exports; 148 | /******/ } 149 | /******/ // Create a new module (and put it into the cache) 150 | /******/ var module = __webpack_module_cache__[moduleId] = { 151 | /******/ // no module.id needed 152 | /******/ // no module.loaded needed 153 | /******/ exports: {} 154 | /******/ }; 155 | /******/ 156 | /******/ // Execute the module function 157 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 158 | /******/ 159 | /******/ // Return the exports of the module 160 | /******/ return module.exports; 161 | /******/ } 162 | /******/ 163 | /************************************************************************/ 164 | /******/ 165 | /******/ // startup 166 | /******/ // Load entry module and return exports 167 | /******/ // This entry module is referenced by other modules so it can't be inlined 168 | /******/ var __webpack_exports__ = __webpack_require__(921); 169 | /******/ 170 | /******/ })() 171 | ;", 172 | "included1.js": "(()=>{var r={46:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(46)})();", 173 | "included2.js": "/******/ (() => { // webpackBootstrap 174 | /******/ var __webpack_modules__ = ({ 175 | 176 | /***/ 515: 177 | /***/ ((module) => { 178 | 179 | module.exports = function Bar2() { 180 | const b = 2 + 2; 181 | console.log(b + 1 + 2); 182 | }; 183 | 184 | 185 | /***/ }) 186 | 187 | /******/ }); 188 | /************************************************************************/ 189 | /******/ // The module cache 190 | /******/ var __webpack_module_cache__ = {}; 191 | /******/ 192 | /******/ // The require function 193 | /******/ function __webpack_require__(moduleId) { 194 | /******/ // Check if module is in cache 195 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 196 | /******/ if (cachedModule !== undefined) { 197 | /******/ return cachedModule.exports; 198 | /******/ } 199 | /******/ // Create a new module (and put it into the cache) 200 | /******/ var module = __webpack_module_cache__[moduleId] = { 201 | /******/ // no module.id needed 202 | /******/ // no module.loaded needed 203 | /******/ exports: {} 204 | /******/ }; 205 | /******/ 206 | /******/ // Execute the module function 207 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 208 | /******/ 209 | /******/ // Return the exports of the module 210 | /******/ return module.exports; 211 | /******/ } 212 | /******/ 213 | /************************************************************************/ 214 | /******/ 215 | /******/ // startup 216 | /******/ // Load entry module and return exports 217 | /******/ // This entry module is referenced by other modules so it can't be inlined 218 | /******/ var __webpack_exports__ = __webpack_require__(515); 219 | /******/ 220 | /******/ })() 221 | ;", 222 | } 223 | `; 224 | 225 | exports[`include option should match snapshot for a single String value: errors 1`] = `Array []`; 226 | 227 | exports[`include option should match snapshot for a single String value: warnings 1`] = `Array []`; 228 | 229 | exports[`include option should match snapshot for multiple RegExp values: assets 1`] = ` 230 | Object { 231 | "entry.js": "/******/ (() => { // webpackBootstrap 232 | /******/ var __webpack_modules__ = ({ 233 | 234 | /***/ 921: 235 | /***/ ((module) => { 236 | 237 | // foo 238 | /* @preserve*/ 239 | // bar 240 | const a = (/* unused pure expression or super */ null && (2 + 2)); 241 | 242 | module.exports = function Foo() { 243 | const b = 2 + 2; 244 | console.log(b + 1 + 2); 245 | }; 246 | 247 | 248 | /***/ }) 249 | 250 | /******/ }); 251 | /************************************************************************/ 252 | /******/ // The module cache 253 | /******/ var __webpack_module_cache__ = {}; 254 | /******/ 255 | /******/ // The require function 256 | /******/ function __webpack_require__(moduleId) { 257 | /******/ // Check if module is in cache 258 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 259 | /******/ if (cachedModule !== undefined) { 260 | /******/ return cachedModule.exports; 261 | /******/ } 262 | /******/ // Create a new module (and put it into the cache) 263 | /******/ var module = __webpack_module_cache__[moduleId] = { 264 | /******/ // no module.id needed 265 | /******/ // no module.loaded needed 266 | /******/ exports: {} 267 | /******/ }; 268 | /******/ 269 | /******/ // Execute the module function 270 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 271 | /******/ 272 | /******/ // Return the exports of the module 273 | /******/ return module.exports; 274 | /******/ } 275 | /******/ 276 | /************************************************************************/ 277 | /******/ 278 | /******/ // startup 279 | /******/ // Load entry module and return exports 280 | /******/ // This entry module is referenced by other modules so it can't be inlined 281 | /******/ var __webpack_exports__ = __webpack_require__(921); 282 | /******/ 283 | /******/ })() 284 | ;", 285 | "included1.js": "(()=>{var r={46:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(46)})();", 286 | "included2.js": "(()=>{var r={515:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(515)})();", 287 | } 288 | `; 289 | 290 | exports[`include option should match snapshot for multiple RegExp values: errors 1`] = `Array []`; 291 | 292 | exports[`include option should match snapshot for multiple RegExp values: warnings 1`] = `Array []`; 293 | 294 | exports[`include option should match snapshot for multiple String values: assets 1`] = ` 295 | Object { 296 | "entry.js": "/******/ (() => { // webpackBootstrap 297 | /******/ var __webpack_modules__ = ({ 298 | 299 | /***/ 921: 300 | /***/ ((module) => { 301 | 302 | // foo 303 | /* @preserve*/ 304 | // bar 305 | const a = (/* unused pure expression or super */ null && (2 + 2)); 306 | 307 | module.exports = function Foo() { 308 | const b = 2 + 2; 309 | console.log(b + 1 + 2); 310 | }; 311 | 312 | 313 | /***/ }) 314 | 315 | /******/ }); 316 | /************************************************************************/ 317 | /******/ // The module cache 318 | /******/ var __webpack_module_cache__ = {}; 319 | /******/ 320 | /******/ // The require function 321 | /******/ function __webpack_require__(moduleId) { 322 | /******/ // Check if module is in cache 323 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 324 | /******/ if (cachedModule !== undefined) { 325 | /******/ return cachedModule.exports; 326 | /******/ } 327 | /******/ // Create a new module (and put it into the cache) 328 | /******/ var module = __webpack_module_cache__[moduleId] = { 329 | /******/ // no module.id needed 330 | /******/ // no module.loaded needed 331 | /******/ exports: {} 332 | /******/ }; 333 | /******/ 334 | /******/ // Execute the module function 335 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 336 | /******/ 337 | /******/ // Return the exports of the module 338 | /******/ return module.exports; 339 | /******/ } 340 | /******/ 341 | /************************************************************************/ 342 | /******/ 343 | /******/ // startup 344 | /******/ // Load entry module and return exports 345 | /******/ // This entry module is referenced by other modules so it can't be inlined 346 | /******/ var __webpack_exports__ = __webpack_require__(921); 347 | /******/ 348 | /******/ })() 349 | ;", 350 | "included1.js": "(()=>{var r={46:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(46)})();", 351 | "included2.js": "(()=>{var r={515:r=>{r.exports=function(){console.log(7)}}},o={};(function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={exports:{}};return r[e](s,s.exports,t),s.exports})(515)})();", 352 | } 353 | `; 354 | 355 | exports[`include option should match snapshot for multiple String values: errors 1`] = `Array []`; 356 | 357 | exports[`include option should match snapshot for multiple String values: warnings 1`] = `Array []`; 358 | -------------------------------------------------------------------------------- /test/terserOptions-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | import TerserPlugin from "../src/index"; 4 | 5 | import { 6 | compile, 7 | getCompiler, 8 | getErrors, 9 | getWarnings, 10 | readsAssets, 11 | } from "./helpers"; 12 | 13 | describe("terserOptions option", () => { 14 | it('should match snapshot for the "ecma" and set the option depending on the "output.environment" option ("es3")', async () => { 15 | const compiler = getCompiler({ 16 | entry: path.resolve(__dirname, "./fixtures/ecma-5/entry.js"), 17 | target: ["web", "es3"], 18 | }); 19 | 20 | new TerserPlugin({ 21 | terserOptions: { 22 | mangle: false, 23 | output: { 24 | beautify: true, 25 | }, 26 | }, 27 | }).apply(compiler); 28 | 29 | const stats = await compile(compiler); 30 | 31 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 32 | expect(getErrors(stats)).toMatchSnapshot("errors"); 33 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 34 | }); 35 | 36 | it('should match snapshot for the "ecma" and set the option depending on the "output.environment" option ("es5")', async () => { 37 | const compiler = getCompiler({ 38 | entry: path.resolve(__dirname, "./fixtures/ecma-5/entry.js"), 39 | target: ["web", "es5"], 40 | }); 41 | 42 | new TerserPlugin({ 43 | terserOptions: { 44 | mangle: false, 45 | output: { 46 | beautify: true, 47 | }, 48 | }, 49 | }).apply(compiler); 50 | 51 | const stats = await compile(compiler); 52 | 53 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 54 | expect(getErrors(stats)).toMatchSnapshot("errors"); 55 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 56 | }); 57 | 58 | it('should match snapshot for the "ecma" and set the option depending on the "output.environment" option ("es2020")', async () => { 59 | const compiler = getCompiler({ 60 | entry: path.resolve(__dirname, "./fixtures/ecma-5/entry.js"), 61 | target: ["web", "es2020"], 62 | }); 63 | 64 | new TerserPlugin({ 65 | terserOptions: { 66 | mangle: false, 67 | output: { 68 | beautify: true, 69 | }, 70 | }, 71 | }).apply(compiler); 72 | 73 | const stats = await compile(compiler); 74 | 75 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 76 | expect(getErrors(stats)).toMatchSnapshot("errors"); 77 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 78 | }); 79 | 80 | it('should match snapshot for the "ecma" option with the "5" value', async () => { 81 | const compiler = getCompiler({ 82 | entry: path.resolve(__dirname, "./fixtures/ecma-5/entry.js"), 83 | }); 84 | 85 | new TerserPlugin({ 86 | terserOptions: { 87 | ecma: 5, 88 | mangle: false, 89 | output: { 90 | beautify: true, 91 | }, 92 | }, 93 | }).apply(compiler); 94 | 95 | const stats = await compile(compiler); 96 | 97 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 98 | expect(getErrors(stats)).toMatchSnapshot("errors"); 99 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 100 | }); 101 | 102 | it('should match snapshot for the "ecma" option with the "5" value ("swc")', async () => { 103 | const compiler = getCompiler({ 104 | entry: path.resolve(__dirname, "./fixtures/ecma-5/entry.js"), 105 | }); 106 | 107 | new TerserPlugin({ 108 | minify: TerserPlugin.swcMinify, 109 | terserOptions: { 110 | ecma: 5, 111 | mangle: false, 112 | format: { 113 | beautify: true, 114 | }, 115 | }, 116 | }).apply(compiler); 117 | 118 | const stats = await compile(compiler); 119 | 120 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 121 | expect(getErrors(stats)).toMatchSnapshot("errors"); 122 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 123 | }); 124 | 125 | it('should match snapshot for the "ecma" option with the "6" value', async () => { 126 | const compiler = getCompiler({ 127 | entry: path.resolve(__dirname, "./fixtures/ecma-6/entry.js"), 128 | }); 129 | 130 | new TerserPlugin({ 131 | terserOptions: { 132 | ecma: 6, 133 | mangle: false, 134 | output: { 135 | beautify: true, 136 | }, 137 | }, 138 | }).apply(compiler); 139 | 140 | const stats = await compile(compiler); 141 | 142 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 143 | expect(getErrors(stats)).toMatchSnapshot("errors"); 144 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 145 | }); 146 | 147 | it('should match snapshot for the "ecma" option with the "6" value ("swc")', async () => { 148 | const compiler = getCompiler({ 149 | entry: path.resolve(__dirname, "./fixtures/ecma-6/entry.js"), 150 | }); 151 | 152 | new TerserPlugin({ 153 | minify: TerserPlugin.swcMinify, 154 | terserOptions: { 155 | ecma: 6, 156 | mangle: false, 157 | format: { 158 | beautify: true, 159 | }, 160 | }, 161 | }).apply(compiler); 162 | 163 | const stats = await compile(compiler); 164 | 165 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 166 | expect(getErrors(stats)).toMatchSnapshot("errors"); 167 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 168 | }); 169 | 170 | it('should match snapshot for the "ecma" option with the "7" value', async () => { 171 | const compiler = getCompiler({ 172 | entry: path.resolve(__dirname, "./fixtures/ecma-7/entry.js"), 173 | }); 174 | 175 | new TerserPlugin({ 176 | terserOptions: { 177 | ecma: 7, 178 | mangle: false, 179 | output: { 180 | beautify: true, 181 | }, 182 | }, 183 | }).apply(compiler); 184 | 185 | const stats = await compile(compiler); 186 | 187 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 188 | expect(getErrors(stats)).toMatchSnapshot("errors"); 189 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 190 | }); 191 | 192 | it('should match snapshot for the "ecma" option with the "8" value', async () => { 193 | const compiler = getCompiler({ 194 | entry: path.resolve(__dirname, "./fixtures/ecma-8/entry.js"), 195 | }); 196 | 197 | new TerserPlugin({ 198 | terserOptions: { 199 | ecma: 8, 200 | mangle: false, 201 | output: { 202 | beautify: true, 203 | }, 204 | }, 205 | }).apply(compiler); 206 | 207 | const stats = await compile(compiler); 208 | 209 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 210 | expect(getErrors(stats)).toMatchSnapshot("errors"); 211 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 212 | }); 213 | 214 | it('should match snapshot for the "parse.ecma" option with the "8" value', async () => { 215 | const compiler = getCompiler(); 216 | 217 | new TerserPlugin({ 218 | terserOptions: { 219 | parse: { 220 | ecma: 8, 221 | }, 222 | }, 223 | }).apply(compiler); 224 | 225 | const stats = await compile(compiler); 226 | 227 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 228 | expect(getErrors(stats)).toMatchSnapshot("errors"); 229 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 230 | }); 231 | 232 | it('should match snapshot for the "compress" option with the "false" value', async () => { 233 | const compiler = getCompiler(); 234 | 235 | new TerserPlugin({ 236 | terserOptions: { 237 | compress: false, 238 | }, 239 | }).apply(compiler); 240 | 241 | const stats = await compile(compiler); 242 | 243 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 244 | expect(getErrors(stats)).toMatchSnapshot("errors"); 245 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 246 | }); 247 | 248 | it('should match snapshot for the "compress" option with the "true" value', async () => { 249 | const compiler = getCompiler(); 250 | 251 | new TerserPlugin({ 252 | terserOptions: { 253 | compress: true, 254 | }, 255 | }).apply(compiler); 256 | 257 | const stats = await compile(compiler); 258 | 259 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 260 | expect(getErrors(stats)).toMatchSnapshot("errors"); 261 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 262 | }); 263 | 264 | it('should match snapshot for the "compress" option with an object value', async () => { 265 | const compiler = getCompiler(); 266 | 267 | new TerserPlugin({ 268 | terserOptions: { 269 | compress: { 270 | join_vars: false, 271 | }, 272 | }, 273 | }).apply(compiler); 274 | 275 | const stats = await compile(compiler); 276 | 277 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 278 | expect(getErrors(stats)).toMatchSnapshot("errors"); 279 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 280 | }); 281 | 282 | it('should match snapshot for the "mangle" option with the "false" value', async () => { 283 | const compiler = getCompiler(); 284 | 285 | new TerserPlugin({ 286 | terserOptions: { 287 | mangle: false, 288 | }, 289 | }).apply(compiler); 290 | 291 | const stats = await compile(compiler); 292 | 293 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 294 | expect(getErrors(stats)).toMatchSnapshot("errors"); 295 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 296 | }); 297 | 298 | it('should match snapshot for the "mangle" option with the "true" value', async () => { 299 | const compiler = getCompiler(); 300 | 301 | new TerserPlugin({ 302 | terserOptions: { 303 | mangle: true, 304 | }, 305 | }).apply(compiler); 306 | 307 | const stats = await compile(compiler); 308 | 309 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 310 | expect(getErrors(stats)).toMatchSnapshot("errors"); 311 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 312 | }); 313 | 314 | it('should match snapshot for the "mangle" option with object values', async () => { 315 | const compiler = getCompiler(); 316 | 317 | new TerserPlugin({ 318 | terserOptions: { 319 | mangle: { 320 | reserved: ["baz"], 321 | }, 322 | }, 323 | }).apply(compiler); 324 | 325 | const stats = await compile(compiler); 326 | 327 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 328 | expect(getErrors(stats)).toMatchSnapshot("errors"); 329 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 330 | }); 331 | 332 | it('should match snapshot for the "module" option with the "false" value', async () => { 333 | const compiler = getCompiler(); 334 | 335 | new TerserPlugin({ 336 | terserOptions: { 337 | module: false, 338 | }, 339 | }).apply(compiler); 340 | 341 | const stats = await compile(compiler); 342 | 343 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 344 | expect(getErrors(stats)).toMatchSnapshot("errors"); 345 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 346 | }); 347 | 348 | it('should match snapshot for the "module" option with the "true" value', async () => { 349 | const compiler = getCompiler(); 350 | 351 | new TerserPlugin({ 352 | terserOptions: { 353 | module: true, 354 | }, 355 | }).apply(compiler); 356 | 357 | const stats = await compile(compiler); 358 | 359 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 360 | expect(getErrors(stats)).toMatchSnapshot("errors"); 361 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 362 | }); 363 | 364 | it('should match snapshot for the "output.beautify" option with "true" value', async () => { 365 | const compiler = getCompiler(); 366 | 367 | new TerserPlugin({ 368 | terserOptions: { 369 | output: { 370 | beautify: true, 371 | }, 372 | }, 373 | }).apply(compiler); 374 | 375 | const stats = await compile(compiler); 376 | 377 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 378 | expect(getErrors(stats)).toMatchSnapshot("errors"); 379 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 380 | }); 381 | 382 | it('should match snapshot for the "output.comments" option with the "true"', async () => { 383 | const compiler = getCompiler(); 384 | 385 | new TerserPlugin({ 386 | terserOptions: { 387 | output: { 388 | comments: true, 389 | }, 390 | }, 391 | }).apply(compiler); 392 | 393 | const stats = await compile(compiler); 394 | 395 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 396 | expect(getErrors(stats)).toMatchSnapshot("errors"); 397 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 398 | }); 399 | 400 | it('should match snapshot for the "format.beautify" option with "true" value', async () => { 401 | const compiler = getCompiler(); 402 | 403 | new TerserPlugin({ 404 | terserOptions: { 405 | format: { 406 | beautify: true, 407 | }, 408 | }, 409 | }).apply(compiler); 410 | 411 | const stats = await compile(compiler); 412 | 413 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 414 | expect(getErrors(stats)).toMatchSnapshot("errors"); 415 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 416 | }); 417 | 418 | it('should match snapshot for the "format.comments" option with the "true"', async () => { 419 | const compiler = getCompiler(); 420 | 421 | new TerserPlugin({ 422 | terserOptions: { 423 | format: { 424 | comments: true, 425 | }, 426 | }, 427 | }).apply(compiler); 428 | 429 | const stats = await compile(compiler); 430 | 431 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 432 | expect(getErrors(stats)).toMatchSnapshot("errors"); 433 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 434 | }); 435 | 436 | it('should match snapshot for the "toplevel" option with the "false" value', async () => { 437 | const compiler = getCompiler(); 438 | 439 | new TerserPlugin({ 440 | terserOptions: { 441 | toplevel: false, 442 | }, 443 | }).apply(compiler); 444 | 445 | const stats = await compile(compiler); 446 | 447 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 448 | expect(getErrors(stats)).toMatchSnapshot("errors"); 449 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 450 | }); 451 | 452 | it('should match snapshot for the "toplevel" option with the "true" value', async () => { 453 | const compiler = getCompiler(); 454 | 455 | new TerserPlugin({ 456 | terserOptions: { 457 | toplevel: true, 458 | }, 459 | }).apply(compiler); 460 | 461 | const stats = await compile(compiler); 462 | 463 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 464 | expect(getErrors(stats)).toMatchSnapshot("errors"); 465 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 466 | }); 467 | 468 | it('should match snapshot for the "nameCache" option with a empty object value', async () => { 469 | const compiler = getCompiler(); 470 | 471 | new TerserPlugin({ 472 | terserOptions: { 473 | nameCache: {}, 474 | }, 475 | }).apply(compiler); 476 | 477 | const stats = await compile(compiler); 478 | 479 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 480 | expect(getErrors(stats)).toMatchSnapshot("errors"); 481 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 482 | }); 483 | 484 | it('should match snapshot for the "ie8" option with the "false" value', async () => { 485 | const compiler = getCompiler(); 486 | 487 | new TerserPlugin({ 488 | terserOptions: { 489 | ie8: false, 490 | }, 491 | }).apply(compiler); 492 | 493 | const stats = await compile(compiler); 494 | 495 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 496 | expect(getErrors(stats)).toMatchSnapshot("errors"); 497 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 498 | }); 499 | 500 | it('should match snapshot for the "ie8" option with the "true" value', async () => { 501 | const compiler = getCompiler(); 502 | 503 | new TerserPlugin({ 504 | terserOptions: { 505 | ie8: true, 506 | }, 507 | }).apply(compiler); 508 | 509 | const stats = await compile(compiler); 510 | 511 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 512 | expect(getErrors(stats)).toMatchSnapshot("errors"); 513 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 514 | }); 515 | 516 | it('should match snapshot for the "keep_classnames" option with the "false" value', async () => { 517 | const compiler = getCompiler(); 518 | 519 | new TerserPlugin({ 520 | terserOptions: { 521 | keep_classnames: false, 522 | }, 523 | }).apply(compiler); 524 | 525 | const stats = await compile(compiler); 526 | 527 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 528 | expect(getErrors(stats)).toMatchSnapshot("errors"); 529 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 530 | }); 531 | 532 | it('should match snapshot for the "keep_classnames" option with the "true" value', async () => { 533 | const compiler = getCompiler(); 534 | 535 | new TerserPlugin({ 536 | terserOptions: { 537 | keep_classnames: true, 538 | }, 539 | }).apply(compiler); 540 | 541 | const stats = await compile(compiler); 542 | 543 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 544 | expect(getErrors(stats)).toMatchSnapshot("errors"); 545 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 546 | }); 547 | 548 | it('should match snapshot for the "keep_fnames" option with the "false" value', async () => { 549 | const compiler = getCompiler(); 550 | 551 | new TerserPlugin({ 552 | terserOptions: { 553 | keep_fnames: false, 554 | }, 555 | }).apply(compiler); 556 | 557 | const stats = await compile(compiler); 558 | 559 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 560 | expect(getErrors(stats)).toMatchSnapshot("errors"); 561 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 562 | }); 563 | 564 | it('should match snapshot for the "keep_fnames" option with the "true" value', async () => { 565 | const compiler = getCompiler(); 566 | 567 | new TerserPlugin({ 568 | terserOptions: { 569 | keep_fnames: true, 570 | }, 571 | }).apply(compiler); 572 | 573 | const stats = await compile(compiler); 574 | 575 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 576 | expect(getErrors(stats)).toMatchSnapshot("errors"); 577 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 578 | }); 579 | 580 | it('should match snapshot for the "safari10" option with the "false" value', async () => { 581 | const compiler = getCompiler(); 582 | 583 | new TerserPlugin({ 584 | terserOptions: { 585 | safari10: false, 586 | }, 587 | }).apply(compiler); 588 | 589 | const stats = await compile(compiler); 590 | 591 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 592 | expect(getErrors(stats)).toMatchSnapshot("errors"); 593 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 594 | }); 595 | 596 | it('should match snapshot for the "safari10" option with the "true" value', async () => { 597 | const compiler = getCompiler(); 598 | 599 | new TerserPlugin({ 600 | terserOptions: { 601 | safari10: true, 602 | }, 603 | }).apply(compiler); 604 | 605 | const stats = await compile(compiler); 606 | 607 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 608 | expect(getErrors(stats)).toMatchSnapshot("errors"); 609 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 610 | }); 611 | 612 | it('should match snapshot for the "unknown" option', async () => { 613 | const compiler = getCompiler(); 614 | 615 | new TerserPlugin({ 616 | parallel: false, 617 | terserOptions: { 618 | output: { 619 | unknown: true, 620 | }, 621 | }, 622 | }).apply(compiler); 623 | 624 | const stats = await compile(compiler); 625 | 626 | expect(getErrors(stats)).toMatchSnapshot("errors"); 627 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 628 | }); 629 | }); 630 | -------------------------------------------------------------------------------- /test/extractComments-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | import webpack from "webpack"; 4 | 5 | import TerserPlugin from "../src/index"; 6 | 7 | import { 8 | ExistingCommentsFile, 9 | compile, 10 | getCompiler, 11 | getErrors, 12 | getWarnings, 13 | readsAssets, 14 | } from "./helpers"; 15 | 16 | function createFilenameFn() { 17 | return (fileData) => { 18 | expect(fileData).toBeDefined(); 19 | 20 | // A file can contain a query string (for example when you have `output.filename: '[name].js?[chunkhash]'`) 21 | // You must consider this 22 | return `${fileData.filename}.LICENSE.txt${fileData.query}`; 23 | }; 24 | } 25 | 26 | describe("extractComments option", () => { 27 | let compiler; 28 | 29 | beforeEach(() => { 30 | compiler = getCompiler({ 31 | entry: { 32 | one: path.resolve(__dirname, "./fixtures/comments.js"), 33 | two: path.resolve(__dirname, "./fixtures/comments-2.js"), 34 | three: path.resolve(__dirname, "./fixtures/comments-3.js"), 35 | four: path.resolve(__dirname, "./fixtures/comments-4.js"), 36 | }, 37 | output: { 38 | filename: "filename/[name].js", 39 | chunkFilename: "chunks/[id].[name].js", 40 | }, 41 | }); 42 | }); 43 | 44 | it("should match snapshot when a value is not specify", async () => { 45 | new TerserPlugin().apply(compiler); 46 | 47 | const stats = await compile(compiler); 48 | 49 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 50 | expect(getErrors(stats)).toMatchSnapshot("errors"); 51 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 52 | }); 53 | 54 | it('should match snapshot for the "false" value', async () => { 55 | new TerserPlugin({ extractComments: false }).apply(compiler); 56 | 57 | const stats = await compile(compiler); 58 | 59 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 60 | expect(getErrors(stats)).toMatchSnapshot("errors"); 61 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 62 | }); 63 | 64 | it('should match snapshot for the "true" value', async () => { 65 | new TerserPlugin({ extractComments: true }).apply(compiler); 66 | 67 | const stats = await compile(compiler); 68 | 69 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 70 | expect(getErrors(stats)).toMatchSnapshot("errors"); 71 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 72 | }); 73 | 74 | it('should match snapshot for the "/Foo/" value', async () => { 75 | new TerserPlugin({ extractComments: /Foo/ }).apply(compiler); 76 | 77 | const stats = await compile(compiler); 78 | 79 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 80 | expect(getErrors(stats)).toMatchSnapshot("errors"); 81 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 82 | }); 83 | 84 | it('should match snapshot for the "all" value', async () => { 85 | new TerserPlugin({ extractComments: "all" }).apply(compiler); 86 | 87 | const stats = await compile(compiler); 88 | 89 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 90 | expect(getErrors(stats)).toMatchSnapshot("errors"); 91 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 92 | }); 93 | 94 | it('should match snapshot for the "some" value', async () => { 95 | new TerserPlugin({ extractComments: "some" }).apply(compiler); 96 | 97 | const stats = await compile(compiler); 98 | 99 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 100 | expect(getErrors(stats)).toMatchSnapshot("errors"); 101 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 102 | }); 103 | 104 | it('should match snapshot for the "Foo" value', async () => { 105 | new TerserPlugin({ extractComments: "Foo" }).apply(compiler); 106 | 107 | const stats = await compile(compiler); 108 | 109 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 110 | expect(getErrors(stats)).toMatchSnapshot("errors"); 111 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 112 | }); 113 | 114 | it('should match snapshot for a "function" value', async () => { 115 | new TerserPlugin({ extractComments: () => true }).apply(compiler); 116 | 117 | const stats = await compile(compiler); 118 | 119 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 120 | expect(getErrors(stats)).toMatchSnapshot("errors"); 121 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 122 | }); 123 | 124 | it('should match snapshot for the "extractComments.condition" with the "true" value', async () => { 125 | new TerserPlugin({ 126 | extractComments: { 127 | condition: true, 128 | }, 129 | }).apply(compiler); 130 | 131 | const stats = await compile(compiler); 132 | 133 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 134 | expect(getErrors(stats)).toMatchSnapshot("errors"); 135 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 136 | }); 137 | 138 | it("should match snapshot when extracts comments to multiple files", async () => { 139 | expect.assertions(8); 140 | 141 | new TerserPlugin({ 142 | extractComments: { 143 | condition: true, 144 | filename: createFilenameFn(), 145 | banner: (licenseFile) => 146 | `License information can be found in ${licenseFile}`, 147 | }, 148 | }).apply(compiler); 149 | 150 | const stats = await compile(compiler); 151 | 152 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 153 | expect(getErrors(stats)).toMatchSnapshot("errors"); 154 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 155 | }); 156 | 157 | it("should match snapshot when extracts comments to a single file", async () => { 158 | new TerserPlugin({ 159 | extractComments: { 160 | condition: true, 161 | filename: "extracted-comments.js", 162 | banner(licenseFile) { 163 | return `License information can be found in ${licenseFile}`; 164 | }, 165 | }, 166 | }).apply(compiler); 167 | 168 | const stats = await compile(compiler); 169 | 170 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 171 | expect(getErrors(stats)).toMatchSnapshot("errors"); 172 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 173 | }); 174 | 175 | it("should match snapshot when extracts without condition", async () => { 176 | new TerserPlugin({ 177 | extractComments: { 178 | condition: true, 179 | filename: "extracted-comments.js", 180 | banner(licenseFile) { 181 | return `License information can be found in ${licenseFile}`; 182 | }, 183 | }, 184 | }).apply(compiler); 185 | 186 | const stats = await compile(compiler); 187 | 188 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 189 | expect(getErrors(stats)).toMatchSnapshot("errors"); 190 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 191 | }); 192 | 193 | it('should match snapshot for the `true` value and preserve "@license" comments', async () => { 194 | new TerserPlugin({ 195 | terserOptions: { 196 | output: { 197 | comments: /@license/i, 198 | }, 199 | }, 200 | extractComments: true, 201 | }).apply(compiler); 202 | 203 | const stats = await compile(compiler); 204 | 205 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 206 | expect(getErrors(stats)).toMatchSnapshot("errors"); 207 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 208 | }); 209 | 210 | it('should match snapshot when no condition, preserve only `/@license/i` comments and extract "some" comments', async () => { 211 | expect.assertions(8); 212 | 213 | new TerserPlugin({ 214 | terserOptions: { 215 | output: { 216 | comments: /@license/i, 217 | }, 218 | }, 219 | extractComments: { 220 | filename: createFilenameFn(), 221 | banner: (licenseFile) => 222 | `License information can be found in ${licenseFile}`, 223 | }, 224 | }).apply(compiler); 225 | 226 | const stats = await compile(compiler); 227 | 228 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 229 | expect(getErrors(stats)).toMatchSnapshot("errors"); 230 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 231 | }); 232 | 233 | it("should match snapshot for the `true` value and dedupe duplicate comments", async () => { 234 | new TerserPlugin({ extractComments: true }).apply(compiler); 235 | 236 | const stats = await compile(compiler); 237 | 238 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 239 | expect(getErrors(stats)).toMatchSnapshot("errors"); 240 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 241 | }); 242 | 243 | it("should match snapshot when extracts comments to a single file and dedupe duplicate comments", async () => { 244 | new TerserPlugin({ 245 | extractComments: { 246 | condition: true, 247 | filename: "extracted-comments.js", 248 | banner(licenseFile) { 249 | return `License information can be found in ${licenseFile}`; 250 | }, 251 | }, 252 | }).apply(compiler); 253 | 254 | const stats = await compile(compiler); 255 | 256 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 257 | expect(getErrors(stats)).toMatchSnapshot("errors"); 258 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 259 | }); 260 | 261 | it("should match snapshot when extracts comments to files with query string", async () => { 262 | compiler = getCompiler({ 263 | entry: { 264 | one: path.resolve(__dirname, "./fixtures/comments.js"), 265 | two: path.resolve(__dirname, "./fixtures/comments-2.js"), 266 | three: path.resolve(__dirname, "./fixtures/comments-3.js"), 267 | four: path.resolve(__dirname, "./fixtures/comments-4.js"), 268 | }, 269 | output: { 270 | filename: "filename/[name].js?[chunkhash]", 271 | chunkFilename: "chunks/[id].[name].js?[chunkhash]", 272 | }, 273 | }); 274 | 275 | new TerserPlugin().apply(compiler); 276 | 277 | const stats = await compile(compiler); 278 | 279 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 280 | expect(getErrors(stats)).toMatchSnapshot("errors"); 281 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 282 | }); 283 | 284 | it("should match snapshot when extracts comments to files with query string and with placeholders", async () => { 285 | compiler = getCompiler({ 286 | entry: { 287 | one: path.resolve(__dirname, "./fixtures/comments.js"), 288 | two: path.resolve(__dirname, "./fixtures/comments-2.js"), 289 | three: path.resolve(__dirname, "./fixtures/comments-3.js"), 290 | four: path.resolve(__dirname, "./fixtures/comments-4.js"), 291 | }, 292 | output: { 293 | filename: "filename/[name].js?[chunkhash]", 294 | chunkFilename: "chunks/[id].[name].js?[chunkhash]", 295 | }, 296 | }); 297 | 298 | new TerserPlugin({ 299 | extractComments: { 300 | condition: true, 301 | filename: "[file].LICENSE.txt?query=[query]&filebase=[base]", 302 | banner(licenseFile) { 303 | return `License information can be found in ${licenseFile}`; 304 | }, 305 | }, 306 | }).apply(compiler); 307 | 308 | const stats = await compile(compiler); 309 | 310 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 311 | expect(getErrors(stats)).toMatchSnapshot("errors"); 312 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 313 | }); 314 | 315 | it("should match snapshot when extracts comments to files with query string and when filename is a function", async () => { 316 | expect.assertions(8); 317 | 318 | compiler = getCompiler({ 319 | entry: { 320 | one: path.resolve(__dirname, "./fixtures/comments.js"), 321 | two: path.resolve(__dirname, "./fixtures/comments-2.js"), 322 | three: path.resolve(__dirname, "./fixtures/comments-3.js"), 323 | four: path.resolve(__dirname, "./fixtures/comments-4.js"), 324 | }, 325 | output: { 326 | filename: "filename/[name].js?[chunkhash]", 327 | chunkFilename: "chunks/[id].[name].js?[chunkhash]", 328 | }, 329 | }); 330 | 331 | new TerserPlugin({ 332 | extractComments: { 333 | condition: true, 334 | filename: createFilenameFn(), 335 | }, 336 | }).apply(compiler); 337 | 338 | const stats = await compile(compiler); 339 | 340 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 341 | expect(getErrors(stats)).toMatchSnapshot("errors"); 342 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 343 | }); 344 | 345 | it("should match snapshot for nested comment file", async () => { 346 | compiler = getCompiler({ 347 | entry: { 348 | one: path.resolve(__dirname, "./fixtures/comments.js"), 349 | }, 350 | }); 351 | 352 | new TerserPlugin({ 353 | extractComments: { 354 | condition: true, 355 | filename: "comments/directory/one.js", 356 | }, 357 | }).apply(compiler); 358 | 359 | const stats = await compile(compiler); 360 | 361 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 362 | expect(getErrors(stats)).toMatchSnapshot("errors"); 363 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 364 | }); 365 | 366 | it("should match snapshot for comment file when filename is nested", async () => { 367 | compiler = getCompiler({ 368 | entry: { 369 | one: path.resolve(__dirname, "./fixtures/comments.js"), 370 | }, 371 | output: { 372 | filename: "nested/directory/[name].js?[chunkhash]", 373 | }, 374 | }); 375 | 376 | new TerserPlugin({ 377 | extractComments: { 378 | condition: true, 379 | filename: "one.js", 380 | }, 381 | }).apply(compiler); 382 | 383 | const stats = await compile(compiler); 384 | 385 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 386 | expect(getErrors(stats)).toMatchSnapshot("errors"); 387 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 388 | }); 389 | 390 | it('should match snapshot and extract "some" comments', async () => { 391 | new TerserPlugin({ 392 | extractComments: true, 393 | }).apply(compiler); 394 | 395 | const stats = await compile(compiler); 396 | 397 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 398 | expect(getErrors(stats)).toMatchSnapshot("errors"); 399 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 400 | }); 401 | 402 | it('should match snapshot and preserve "all" and extract "some" comments', async () => { 403 | new TerserPlugin({ 404 | extractComments: true, 405 | terserOptions: { 406 | output: { 407 | comments: "all", 408 | }, 409 | }, 410 | }).apply(compiler); 411 | 412 | const stats = await compile(compiler); 413 | 414 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 415 | expect(getErrors(stats)).toMatchSnapshot("errors"); 416 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 417 | }); 418 | 419 | it('should match snapshot and do not preserve and extract "all" comments', async () => { 420 | new TerserPlugin({ 421 | extractComments: "all", 422 | }).apply(compiler); 423 | 424 | const stats = await compile(compiler); 425 | 426 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 427 | expect(getErrors(stats)).toMatchSnapshot("errors"); 428 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 429 | }); 430 | 431 | it('should match snapshot and preserve "all" and extract "all" comments', async () => { 432 | new TerserPlugin({ 433 | extractComments: "all", 434 | terserOptions: { 435 | output: { 436 | comments: "all", 437 | }, 438 | }, 439 | }).apply(compiler); 440 | 441 | const stats = await compile(compiler); 442 | 443 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 444 | expect(getErrors(stats)).toMatchSnapshot("errors"); 445 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 446 | }); 447 | 448 | it('should match snapshot and do not preserve and extract "all" comments when the option if a function', async () => { 449 | new TerserPlugin({ 450 | extractComments: () => true, 451 | }).apply(compiler); 452 | 453 | const stats = await compile(compiler); 454 | 455 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 456 | expect(getErrors(stats)).toMatchSnapshot("errors"); 457 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 458 | }); 459 | 460 | it('should match snapshot and preserve "all" and extract "all" comments with output.comments "all"', async () => { 461 | new TerserPlugin({ 462 | extractComments: () => true, 463 | terserOptions: { 464 | output: { 465 | comments: "all", 466 | }, 467 | }, 468 | }).apply(compiler); 469 | 470 | const stats = await compile(compiler); 471 | 472 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 473 | expect(getErrors(stats)).toMatchSnapshot("errors"); 474 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 475 | }); 476 | 477 | it('should match snapshot and do not preserve and extract "some" comments', async () => { 478 | new TerserPlugin({ 479 | extractComments: {}, 480 | }).apply(compiler); 481 | 482 | const stats = await compile(compiler); 483 | 484 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 485 | expect(getErrors(stats)).toMatchSnapshot("errors"); 486 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 487 | }); 488 | 489 | it('should match snapshot and preserve "all" and extract comments with output.comments "all"', async () => { 490 | new TerserPlugin({ 491 | extractComments: {}, 492 | terserOptions: { 493 | output: { 494 | comments: "all", 495 | }, 496 | }, 497 | }).apply(compiler); 498 | 499 | const stats = await compile(compiler); 500 | 501 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 502 | expect(getErrors(stats)).toMatchSnapshot("errors"); 503 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 504 | }); 505 | 506 | it('should match snapshot and preserve "all" and extract "some" comments with output.comments "all"', async () => { 507 | new TerserPlugin({ 508 | extractComments: { 509 | condition: "some", 510 | }, 511 | terserOptions: { 512 | output: { 513 | comments: "all", 514 | }, 515 | }, 516 | }).apply(compiler); 517 | 518 | const stats = await compile(compiler); 519 | 520 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 521 | expect(getErrors(stats)).toMatchSnapshot("errors"); 522 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 523 | }); 524 | 525 | it('should match snapshot and preserve "all" and do not extract comments', async () => { 526 | new TerserPlugin({ 527 | extractComments: { 528 | condition: false, 529 | }, 530 | terserOptions: { 531 | output: { 532 | comments: "all", 533 | }, 534 | }, 535 | }).apply(compiler); 536 | 537 | const stats = await compile(compiler); 538 | 539 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 540 | expect(getErrors(stats)).toMatchSnapshot("errors"); 541 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 542 | }); 543 | 544 | it('should match snapshot and preserve "some" do not extract comments', async () => { 545 | new TerserPlugin({ 546 | extractComments: false, 547 | }).apply(compiler); 548 | 549 | const stats = await compile(compiler); 550 | 551 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 552 | expect(getErrors(stats)).toMatchSnapshot("errors"); 553 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 554 | }); 555 | 556 | it('should match snapshot and preserve "all" do not extract comments', async () => { 557 | new TerserPlugin({ 558 | extractComments: false, 559 | terserOptions: { 560 | output: { 561 | comments: "all", 562 | }, 563 | }, 564 | }).apply(compiler); 565 | 566 | const stats = await compile(compiler); 567 | 568 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 569 | expect(getErrors(stats)).toMatchSnapshot("errors"); 570 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 571 | }); 572 | 573 | it("should match snapshot and do not preserve or extract comments", async () => { 574 | new TerserPlugin({ 575 | extractComments: false, 576 | terserOptions: { 577 | output: { 578 | comments: false, 579 | }, 580 | }, 581 | }).apply(compiler); 582 | 583 | const stats = await compile(compiler); 584 | 585 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 586 | expect(getErrors(stats)).toMatchSnapshot("errors"); 587 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 588 | }); 589 | 590 | it("should match snapshot and keep shebang", async () => { 591 | compiler = getCompiler({ 592 | entry: { 593 | shebang: path.resolve(__dirname, "./fixtures/shebang.js"), 594 | shebang1: path.resolve(__dirname, "./fixtures/shebang-1.js"), 595 | }, 596 | target: "node", 597 | plugins: [ 598 | new webpack.BannerPlugin({ banner: "#!/usr/bin/env node", raw: true }), 599 | ], 600 | }); 601 | 602 | new TerserPlugin().apply(compiler); 603 | 604 | const stats = await compile(compiler); 605 | 606 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 607 | expect(getErrors(stats)).toMatchSnapshot("errors"); 608 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 609 | }); 610 | 611 | it("should work with the existing licenses file", async () => { 612 | new ExistingCommentsFile().apply(compiler); 613 | new TerserPlugin({ 614 | extractComments: { 615 | filename: "licenses.txt", 616 | }, 617 | }).apply(compiler); 618 | 619 | const stats = await compile(compiler); 620 | 621 | expect(readsAssets(compiler, stats)).toMatchSnapshot("assets"); 622 | expect(getErrors(stats)).toMatchSnapshot("errors"); 623 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 624 | }); 625 | }); 626 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | 7 | [![npm][npm]][npm-url] 8 | [![node][node]][node-url] 9 | [![tests][tests]][tests-url] 10 | [![cover][cover]][cover-url] 11 | [![discussion][discussion]][discussion-url] 12 | [![size][size]][size-url] 13 | 14 | # terser-webpack-plugin 15 | 16 | This plugin uses [terser](https://github.com/terser/terser) to minify/minimize your JavaScript. 17 | 18 | ## Getting Started 19 | 20 | Webpack v5 comes with the latest `terser-webpack-plugin` out of the box. 21 | If you are using Webpack v5 or above and wish to customize the options, you will still need to install `terser-webpack-plugin`. 22 | Using Webpack v4, you have to install `terser-webpack-plugin` v4. 23 | 24 | To begin, you'll need to install `terser-webpack-plugin`: 25 | 26 | ```console 27 | npm install terser-webpack-plugin --save-dev 28 | ``` 29 | 30 | or 31 | 32 | ```console 33 | yarn add -D terser-webpack-plugin 34 | ``` 35 | 36 | or 37 | 38 | ```console 39 | pnpm add -D terser-webpack-plugin 40 | ``` 41 | 42 | Then add the plugin to your `webpack` configuration. For example: 43 | 44 | **webpack.config.js** 45 | 46 | ```js 47 | const TerserPlugin = require("terser-webpack-plugin"); 48 | 49 | module.exports = { 50 | optimization: { 51 | minimize: true, 52 | minimizer: [new TerserPlugin()], 53 | }, 54 | }; 55 | ``` 56 | 57 | Finally, run `webpack` using the method you normally use (e.g., via CLI or an npm script). 58 | 59 | ## Note about source maps 60 | 61 | **Works only with `source-map`, `inline-source-map`, `hidden-source-map` and `nosources-source-map` values for the [`devtool`](https://webpack.js.org/configuration/devtool/) option.** 62 | 63 | Why? 64 | 65 | - `eval` wraps modules in `eval("string")` and the minimizer does not handle strings. 66 | - `cheap` has no column information and the minimizer generates only a single line, which leaves only a single mapping. 67 | 68 | Using supported `devtool` values enable source map generation. 69 | 70 | ## Options 71 | 72 | - **[`test`](#test)** 73 | - **[`include`](#include)** 74 | - **[`exclude`](#exclude)** 75 | - **[`parallel`](#parallel)** 76 | - **[`minify`](#minify)** 77 | - **[`terserOptions`](#terseroptions)** 78 | - **[`extractComments`](#extractcomments)** 79 | 80 | ### `test` 81 | 82 | Type: 83 | 84 | ```ts 85 | type test = string | RegExp | (string | RegExp)[]; 86 | ``` 87 | 88 | Default: `/\.m?js(\?.*)?$/i` 89 | 90 | Test to match files against. 91 | 92 | **webpack.config.js** 93 | 94 | ```js 95 | module.exports = { 96 | optimization: { 97 | minimize: true, 98 | minimizer: [ 99 | new TerserPlugin({ 100 | test: /\.js(\?.*)?$/i, 101 | }), 102 | ], 103 | }, 104 | }; 105 | ``` 106 | 107 | ### `include` 108 | 109 | Type: 110 | 111 | ```ts 112 | type include = string | RegExp | (string | RegExp)[]; 113 | ``` 114 | 115 | Default: `undefined` 116 | 117 | Files to include. 118 | 119 | **webpack.config.js** 120 | 121 | ```js 122 | module.exports = { 123 | optimization: { 124 | minimize: true, 125 | minimizer: [ 126 | new TerserPlugin({ 127 | include: /\/includes/, 128 | }), 129 | ], 130 | }, 131 | }; 132 | ``` 133 | 134 | ### `exclude` 135 | 136 | Type: 137 | 138 | ```ts 139 | type exclude = string | RegExp | (string | RegExp)[]; 140 | ``` 141 | 142 | Default: `undefined` 143 | 144 | Files to exclude. 145 | 146 | **webpack.config.js** 147 | 148 | ```js 149 | module.exports = { 150 | optimization: { 151 | minimize: true, 152 | minimizer: [ 153 | new TerserPlugin({ 154 | exclude: /\/excludes/, 155 | }), 156 | ], 157 | }, 158 | }; 159 | ``` 160 | 161 | ### `parallel` 162 | 163 | Type: 164 | 165 | ```ts 166 | type parallel = boolean | number; 167 | ``` 168 | 169 | Default: `true` 170 | 171 | Use multi-process parallel running to improve the build speed. 172 | 173 | Default number of concurrent runs: `os.cpus().length - 1` or `os.availableParallelism() - 1` (if this function is supported). 174 | 175 | > **Note** 176 | > 177 | > Parallelization can speedup your build significantly and is therefore **highly recommended**. 178 | 179 | > **Warning** 180 | > 181 | > If you use **Circle CI** or any other environment that doesn't provide the real available count of CPUs then you need to explicitly set up the number of CPUs to avoid `Error: Call retries were exceeded` (see [#143](https://github.com/webpack/terser-webpack-plugin/issues/143), [#202](https://github.com/webpack/terser-webpack-plugin/issues/202)). 182 | 183 | #### `boolean` 184 | 185 | Enable/disable multi-process parallel running. 186 | 187 | **webpack.config.js** 188 | 189 | ```js 190 | module.exports = { 191 | optimization: { 192 | minimize: true, 193 | minimizer: [ 194 | new TerserPlugin({ 195 | parallel: true, 196 | }), 197 | ], 198 | }, 199 | }; 200 | ``` 201 | 202 | #### `number` 203 | 204 | Enable multi-process parallel running and set number of concurrent runs. 205 | 206 | **webpack.config.js** 207 | 208 | ```js 209 | module.exports = { 210 | optimization: { 211 | minimize: true, 212 | minimizer: [ 213 | new TerserPlugin({ 214 | parallel: 4, 215 | }), 216 | ], 217 | }, 218 | }; 219 | ``` 220 | 221 | ### `minify` 222 | 223 | Type: 224 | 225 | ```ts 226 | type minify = ( 227 | input: Record, 228 | sourceMap: import("@jridgewell/trace-mapping").SourceMapInput | undefined, 229 | minifyOptions: { 230 | module?: boolean | undefined; 231 | ecma?: import("terser").ECMA | undefined; 232 | }, 233 | extractComments: 234 | | boolean 235 | | "all" 236 | | "some" 237 | | RegExp 238 | | (( 239 | astNode: any, 240 | comment: { 241 | value: string; 242 | type: "comment1" | "comment2" | "comment3" | "comment4"; 243 | pos: number; 244 | line: number; 245 | col: number; 246 | }, 247 | ) => boolean) 248 | | { 249 | condition?: 250 | | boolean 251 | | "all" 252 | | "some" 253 | | RegExp 254 | | (( 255 | astNode: any, 256 | comment: { 257 | value: string; 258 | type: "comment1" | "comment2" | "comment3" | "comment4"; 259 | pos: number; 260 | line: number; 261 | col: number; 262 | }, 263 | ) => boolean) 264 | | undefined; 265 | filename?: string | ((fileData: any) => string) | undefined; 266 | banner?: 267 | | string 268 | | boolean 269 | | ((commentsFile: string) => string) 270 | | undefined; 271 | } 272 | | undefined, 273 | ) => Promise<{ 274 | code: string; 275 | map?: import("@jridgewell/trace-mapping").SourceMapInput | undefined; 276 | errors?: (string | Error)[] | undefined; 277 | warnings?: (string | Error)[] | undefined; 278 | extractedComments?: string[] | undefined; 279 | }>; 280 | ``` 281 | 282 | Default: `TerserPlugin.terserMinify` 283 | 284 | Allows you to override the default minify function. 285 | By default plugin uses [terser](https://github.com/terser/terser) package. 286 | Useful for using and testing unpublished versions or forks. 287 | 288 | > **Warning** 289 | > 290 | > **Always use `require` inside `minify` function when `parallel` option enabled**. 291 | 292 | **webpack.config.js** 293 | 294 | ```js 295 | // Can be async 296 | const minify = (input, sourceMap, minimizerOptions, extractsComments) => { 297 | // The `minimizerOptions` option contains option from the `terserOptions` option 298 | // You can use `minimizerOptions.myCustomOption` 299 | 300 | // Custom logic for extract comments 301 | const { map, code } = require("uglify-module") // Or require('./path/to/uglify-module') 302 | .minify(input, { 303 | /* Your options for minification */ 304 | }); 305 | 306 | return { map, code, warnings: [], errors: [], extractedComments: [] }; 307 | }; 308 | 309 | // Used to regenerate `fullhash`/`chunkhash` between different implementation 310 | // Example: you fix a bug in custom minimizer/custom function, but unfortunately webpack doesn't know about it, so you will get the same fullhash/chunkhash 311 | // to avoid this you can provide version of your custom minimizer 312 | // You don't need if you use only `contenthash` 313 | minify.getMinimizerVersion = () => { 314 | let packageJson; 315 | 316 | try { 317 | packageJson = require("uglify-module/package.json"); 318 | } catch (error) { 319 | // Ignore 320 | } 321 | 322 | return packageJson && packageJson.version; 323 | }; 324 | 325 | module.exports = { 326 | optimization: { 327 | minimize: true, 328 | minimizer: [ 329 | new TerserPlugin({ 330 | terserOptions: { 331 | myCustomOption: true, 332 | }, 333 | minify, 334 | }), 335 | ], 336 | }, 337 | }; 338 | ``` 339 | 340 | ### `terserOptions` 341 | 342 | Type: 343 | 344 | ```ts 345 | interface terserOptions { 346 | compress?: boolean | CompressOptions; 347 | ecma?: ECMA; 348 | enclose?: boolean | string; 349 | ie8?: boolean; 350 | keep_classnames?: boolean | RegExp; 351 | keep_fnames?: boolean | RegExp; 352 | mangle?: boolean | MangleOptions; 353 | module?: boolean; 354 | nameCache?: object; 355 | format?: FormatOptions; 356 | /** @deprecated */ 357 | output?: FormatOptions; 358 | parse?: ParseOptions; 359 | safari10?: boolean; 360 | sourceMap?: boolean | SourceMapOptions; 361 | toplevel?: boolean; 362 | } 363 | ``` 364 | 365 | Default: [default](https://github.com/terser/terser#minify-options) 366 | 367 | Terser [options](https://github.com/terser/terser#minify-options). 368 | 369 | **webpack.config.js** 370 | 371 | ```js 372 | module.exports = { 373 | optimization: { 374 | minimize: true, 375 | minimizer: [ 376 | new TerserPlugin({ 377 | terserOptions: { 378 | ecma: undefined, 379 | parse: {}, 380 | compress: {}, 381 | mangle: true, // Note `mangle.properties` is `false` by default. 382 | module: false, 383 | // Deprecated 384 | output: null, 385 | format: null, 386 | toplevel: false, 387 | nameCache: null, 388 | ie8: false, 389 | keep_classnames: undefined, 390 | keep_fnames: false, 391 | safari10: false, 392 | }, 393 | }), 394 | ], 395 | }, 396 | }; 397 | ``` 398 | 399 | ### `extractComments` 400 | 401 | Type: 402 | 403 | ```ts 404 | type extractComments = 405 | | boolean 406 | | string 407 | | RegExp 408 | | (( 409 | astNode: any, 410 | comment: { 411 | value: string; 412 | type: "comment1" | "comment2" | "comment3" | "comment4"; 413 | pos: number; 414 | line: number; 415 | col: number; 416 | }, 417 | ) => boolean) 418 | | { 419 | condition?: 420 | | boolean 421 | | "all" 422 | | "some" 423 | | RegExp 424 | | (( 425 | astNode: any, 426 | comment: { 427 | value: string; 428 | type: "comment1" | "comment2" | "comment3" | "comment4"; 429 | pos: number; 430 | line: number; 431 | col: number; 432 | }, 433 | ) => boolean) 434 | | undefined; 435 | filename?: string | ((fileData: any) => string) | undefined; 436 | banner?: 437 | | string 438 | | boolean 439 | | ((commentsFile: string) => string) 440 | | undefined; 441 | }; 442 | ``` 443 | 444 | Default: `true` 445 | 446 | Whether comments shall be extracted to a separate file, (see [details](https://github.com/webpack/webpack/commit/71933e979e51c533b432658d5e37917f9e71595a)). 447 | 448 | By default, extract only comments using `/^\**!|@preserve|@license|@cc_on/i` RegExp condition and remove remaining comments. 449 | 450 | If the original file is named `foo.js`, then the comments will be stored to `foo.js.LICENSE.txt`. 451 | 452 | The `terserOptions.format.comments` option specifies whether the comment will be preserved - i.e., it is possible to preserve some comments (e.g. annotations) while extracting others, or even preserve comments that have already been extracted. 453 | 454 | #### `boolean` 455 | 456 | Enable/disable extracting comments. 457 | 458 | **webpack.config.js** 459 | 460 | ```js 461 | module.exports = { 462 | optimization: { 463 | minimize: true, 464 | minimizer: [ 465 | new TerserPlugin({ 466 | extractComments: true, 467 | }), 468 | ], 469 | }, 470 | }; 471 | ``` 472 | 473 | #### `string` 474 | 475 | Extract `all` or `some` (use the `/^\**!|@preserve|@license|@cc_on/i` RegExp) comments. 476 | 477 | **webpack.config.js** 478 | 479 | ```js 480 | module.exports = { 481 | optimization: { 482 | minimize: true, 483 | minimizer: [ 484 | new TerserPlugin({ 485 | extractComments: "all", 486 | }), 487 | ], 488 | }, 489 | }; 490 | ``` 491 | 492 | #### `RegExp` 493 | 494 | All comments that match the given expression will be extracted to a separate file. 495 | 496 | **webpack.config.js** 497 | 498 | ```js 499 | module.exports = { 500 | optimization: { 501 | minimize: true, 502 | minimizer: [ 503 | new TerserPlugin({ 504 | extractComments: /@extract/i, 505 | }), 506 | ], 507 | }, 508 | }; 509 | ``` 510 | 511 | #### `function` 512 | 513 | All comments that match the given expression will be extracted to a separate file. 514 | 515 | **webpack.config.js** 516 | 517 | ```js 518 | module.exports = { 519 | optimization: { 520 | minimize: true, 521 | minimizer: [ 522 | new TerserPlugin({ 523 | extractComments: (astNode, comment) => { 524 | if (/@extract/i.test(comment.value)) { 525 | return true; 526 | } 527 | 528 | return false; 529 | }, 530 | }), 531 | ], 532 | }, 533 | }; 534 | ``` 535 | 536 | #### `object` 537 | 538 | Allows you to customize condition for extracting comments, and specify the extracted file name and banner. 539 | 540 | **webpack.config.js** 541 | 542 | ```js 543 | module.exports = { 544 | optimization: { 545 | minimize: true, 546 | minimizer: [ 547 | new TerserPlugin({ 548 | extractComments: { 549 | condition: /^\**!|@preserve|@license|@cc_on/i, 550 | filename: (fileData) => 551 | // The "fileData" argument contains object with "filename", "basename", "query" and "hash" 552 | `${fileData.filename}.LICENSE.txt${fileData.query}`, 553 | banner: (licenseFile) => 554 | `License information can be found in ${licenseFile}`, 555 | }, 556 | }), 557 | ], 558 | }, 559 | }; 560 | ``` 561 | 562 | ##### `condition` 563 | 564 | Type: 565 | 566 | ```ts 567 | type condition = 568 | | boolean 569 | | "all" 570 | | "some" 571 | | RegExp 572 | | (( 573 | astNode: any, 574 | comment: { 575 | value: string; 576 | type: "comment1" | "comment2" | "comment3" | "comment4"; 577 | pos: number; 578 | line: number; 579 | col: number; 580 | }, 581 | ) => boolean) 582 | | undefined; 583 | ``` 584 | 585 | The condition that determines which comments should be extracted. 586 | 587 | **webpack.config.js** 588 | 589 | ```js 590 | module.exports = { 591 | optimization: { 592 | minimize: true, 593 | minimizer: [ 594 | new TerserPlugin({ 595 | extractComments: { 596 | condition: "some", 597 | filename: (fileData) => 598 | // The "fileData" argument contains object with "filename", "basename", "query" and "hash" 599 | `${fileData.filename}.LICENSE.txt${fileData.query}`, 600 | banner: (licenseFile) => 601 | `License information can be found in ${licenseFile}`, 602 | }, 603 | }), 604 | ], 605 | }, 606 | }; 607 | ``` 608 | 609 | ##### `filename` 610 | 611 | Type: 612 | 613 | ```ts 614 | type filename = string | ((fileData: any) => string) | undefined; 615 | ``` 616 | 617 | Default: `[file].LICENSE.txt[query]` 618 | 619 | Available placeholders: `[file]`, `[query]` and `[filebase]` (`[base]` for webpack 5). 620 | 621 | The file where the extracted comments will be stored. 622 | 623 | Default is to append the suffix `.LICENSE.txt` to the original filename. 624 | 625 | > **Warning** 626 | > 627 | > We highly recommend using the `.txt` extension. Using `.js`/`.cjs`/`.mjs` extensions may conflict with existing assets, which leads to broken code. 628 | 629 | **webpack.config.js** 630 | 631 | ```js 632 | module.exports = { 633 | optimization: { 634 | minimize: true, 635 | minimizer: [ 636 | new TerserPlugin({ 637 | extractComments: { 638 | condition: /^\**!|@preserve|@license|@cc_on/i, 639 | filename: "extracted-comments.js", 640 | banner: (licenseFile) => 641 | `License information can be found in ${licenseFile}`, 642 | }, 643 | }), 644 | ], 645 | }, 646 | }; 647 | ``` 648 | 649 | ##### `banner` 650 | 651 | Type: 652 | 653 | ```ts 654 | type banner = string | boolean | ((commentsFile: string) => string) | undefined; 655 | ``` 656 | 657 | Default: `/*! For license information please see ${commentsFile} */` 658 | 659 | The banner text that points to the extracted file and will be added at the top of the original file. 660 | 661 | It can be `false` (no banner), a `String`, or a `function<(string) -> String>` that will be called with the filename where the extracted comments have been stored. 662 | 663 | The banner will be wrapped in a comment. 664 | 665 | **webpack.config.js** 666 | 667 | ```js 668 | module.exports = { 669 | optimization: { 670 | minimize: true, 671 | minimizer: [ 672 | new TerserPlugin({ 673 | extractComments: { 674 | condition: true, 675 | filename: (fileData) => 676 | // The "fileData" argument contains object with "filename", "basename", "query" and "hash" 677 | `${fileData.filename}.LICENSE.txt${fileData.query}`, 678 | banner: (commentsFile) => 679 | `My custom banner about license information ${commentsFile}`, 680 | }, 681 | }), 682 | ], 683 | }, 684 | }; 685 | ``` 686 | 687 | ## Examples 688 | 689 | ### Preserve Comments 690 | 691 | Extract all legal comments (i.e. `/^\**!|@preserve|@license|@cc_on/i`) and preserve `/@license/i` comments. 692 | 693 | **webpack.config.js** 694 | 695 | ```js 696 | module.exports = { 697 | optimization: { 698 | minimize: true, 699 | minimizer: [ 700 | new TerserPlugin({ 701 | terserOptions: { 702 | format: { 703 | comments: /@license/i, 704 | }, 705 | }, 706 | extractComments: true, 707 | }), 708 | ], 709 | }, 710 | }; 711 | ``` 712 | 713 | ### Remove Comments 714 | 715 | If you want to build without comments, use this config: 716 | 717 | **webpack.config.js** 718 | 719 | ```js 720 | module.exports = { 721 | optimization: { 722 | minimize: true, 723 | minimizer: [ 724 | new TerserPlugin({ 725 | terserOptions: { 726 | format: { 727 | comments: false, 728 | }, 729 | }, 730 | extractComments: false, 731 | }), 732 | ], 733 | }, 734 | }; 735 | ``` 736 | 737 | ### [`uglify-js`](https://github.com/mishoo/UglifyJS) 738 | 739 | [`UglifyJS`](https://github.com/mishoo/UglifyJS) is a JavaScript parser, minifier, compressor and beautifier toolkit. 740 | 741 | **webpack.config.js** 742 | 743 | ```js 744 | module.exports = { 745 | optimization: { 746 | minimize: true, 747 | minimizer: [ 748 | new TerserPlugin({ 749 | minify: TerserPlugin.uglifyJsMinify, 750 | // `terserOptions` options will be passed to `uglify-js` 751 | // Link to options - https://github.com/mishoo/UglifyJS#minify-options 752 | terserOptions: {}, 753 | }), 754 | ], 755 | }, 756 | }; 757 | ``` 758 | 759 | ### [`swc`](https://github.com/swc-project/swc) 760 | 761 | [`swc`](https://github.com/swc-project/swc) is a super-fast compiler written in `Rust`, producing widely supported JavaScript from modern standards and TypeScript. 762 | 763 | > **Warning** 764 | > 765 | > The `extractComments` option is not supported, and all comments will be removed by default. This will be fixed in future 766 | 767 | **webpack.config.js** 768 | 769 | ```js 770 | module.exports = { 771 | optimization: { 772 | minimize: true, 773 | minimizer: [ 774 | new TerserPlugin({ 775 | minify: TerserPlugin.swcMinify, 776 | // `terserOptions` options will be passed to `swc` (`@swc/core`) 777 | // Link to options - https://swc.rs/docs/config-js-minify 778 | terserOptions: {}, 779 | }), 780 | ], 781 | }, 782 | }; 783 | ``` 784 | 785 | ### [`esbuild`](https://github.com/evanw/esbuild) 786 | 787 | [`esbuild`](https://github.com/evanw/esbuild) is an extremely fast JavaScript bundler and minifier. 788 | 789 | > **Warning** 790 | > 791 | > The `extractComments` option is not supported, and all legal comments (i.e. copyright, licenses and etc) will be preserved. 792 | 793 | **webpack.config.js** 794 | 795 | ```js 796 | module.exports = { 797 | optimization: { 798 | minimize: true, 799 | minimizer: [ 800 | new TerserPlugin({ 801 | minify: TerserPlugin.esbuildMinify, 802 | // `terserOptions` options will be passed to `esbuild` 803 | // Link to options - https://esbuild.github.io/api/#minify 804 | // Note: the `minify` options is true by default (and override other `minify*` options), so if you want to disable the `minifyIdentifiers` option (or other `minify*` options) please use: 805 | // terserOptions: { 806 | // minify: false, 807 | // minifyWhitespace: true, 808 | // minifyIdentifiers: false, 809 | // minifySyntax: true, 810 | // }, 811 | terserOptions: {}, 812 | }), 813 | ], 814 | }, 815 | }; 816 | ``` 817 | 818 | ### Custom Minify Function 819 | 820 | Override the default minify function - use `uglify-js` for minification. 821 | 822 | **webpack.config.js** 823 | 824 | ```js 825 | module.exports = { 826 | optimization: { 827 | minimize: true, 828 | minimizer: [ 829 | new TerserPlugin({ 830 | minify: (file, sourceMap) => { 831 | // https://github.com/mishoo/UglifyJS2#minify-options 832 | const uglifyJsOptions = { 833 | /* your `uglify-js` package options */ 834 | }; 835 | 836 | if (sourceMap) { 837 | uglifyJsOptions.sourceMap = { 838 | content: sourceMap, 839 | }; 840 | } 841 | 842 | return require("uglify-js").minify(file, uglifyJsOptions); 843 | }, 844 | }), 845 | ], 846 | }, 847 | }; 848 | ``` 849 | 850 | ### Typescript 851 | 852 | With default Terser minify function: 853 | 854 | ```ts 855 | module.exports = { 856 | optimization: { 857 | minimize: true, 858 | minimizer: [ 859 | new TerserPlugin({ 860 | terserOptions: { 861 | compress: true, 862 | }, 863 | }), 864 | ], 865 | }, 866 | }; 867 | ``` 868 | 869 | With built-in minify functions: 870 | 871 | ```ts 872 | import { type JsMinifyOptions as SwcOptions } from "@swc/core"; 873 | import { type TransformOptions as EsbuildOptions } from "esbuild"; 874 | import { type MinifyOptions as TerserOptions } from "terser"; 875 | import { type MinifyOptions as UglifyJSOptions } from "uglify-js"; 876 | 877 | module.exports = { 878 | optimization: { 879 | minimize: true, 880 | minimizer: [ 881 | new TerserPlugin({ 882 | minify: TerserPlugin.swcMinify, 883 | terserOptions: { 884 | // `swc` options 885 | }, 886 | }), 887 | new TerserPlugin({ 888 | minify: TerserPlugin.uglifyJsMinify, 889 | terserOptions: { 890 | // `uglif-js` options 891 | }, 892 | }), 893 | new TerserPlugin({ 894 | minify: TerserPlugin.esbuildMinify, 895 | terserOptions: { 896 | // `esbuild` options 897 | }, 898 | }), 899 | 900 | // Alternative usage: 901 | new TerserPlugin({ 902 | minify: TerserPlugin.terserMinify, 903 | terserOptions: { 904 | // `terser` options 905 | }, 906 | }), 907 | ], 908 | }, 909 | }; 910 | ``` 911 | 912 | ## Contributing 913 | 914 | We welcome all contributions! 915 | If you're new here, please take a moment to review our contributing guidelines before submitting issues or pull requests. 916 | 917 | [CONTRIBUTING](https://github.com/webpack/terser-webpack-plugin?tab=contributing-ov-file#contributing) 918 | 919 | ## License 920 | 921 | [MIT](./LICENSE) 922 | 923 | [npm]: https://img.shields.io/npm/v/terser-webpack-plugin.svg 924 | [npm-url]: https://npmjs.com/package/terser-webpack-plugin 925 | [node]: https://img.shields.io/node/v/terser-webpack-plugin.svg 926 | [node-url]: https://nodejs.org 927 | [tests]: https://github.com/webpack/terser-webpack-plugin/workflows/terser-webpack-plugin/badge.svg 928 | [tests-url]: https://github.com/webpack/terser-webpack-plugin/actions 929 | [cover]: https://codecov.io/gh/webpack/terser-webpack-plugin/branch/main/graph/badge.svg 930 | [cover-url]: https://codecov.io/gh/webpack/terser-webpack-plugin 931 | [discussion]: https://img.shields.io/github/discussions/webpack/webpack 932 | [discussion-url]: https://github.com/webpack/webpack/discussions 933 | [size]: https://packagephobia.now.sh/badge?p=terser-webpack-plugin 934 | [size-url]: https://packagephobia.now.sh/result?p=terser-webpack-plugin 935 | --------------------------------------------------------------------------------