├── .husky ├── pre-commit └── commit-msg ├── test ├── fixtures │ ├── test │ │ ├── error.css │ │ ├── foo.css │ │ ├── bar2.css │ │ └── bar1.css │ ├── empty.css │ ├── empty.js │ ├── entry.js │ ├── cache-1.js │ ├── cache.js │ ├── sss │ │ └── index.sss │ ├── cache-2.js │ ├── cache-3.js │ ├── cache-4.js │ ├── excluded1.js │ ├── excluded2.js │ ├── included1.js │ ├── included2.js │ ├── simple-async.js │ ├── simple-emit-2.js │ ├── simple-emit.js │ ├── sugarss.js │ ├── wrong-calc.css │ ├── sourcemap │ │ ├── bar.css │ │ ├── bar.scss │ │ ├── foo.scss │ │ └── foo.css │ ├── foo.css │ ├── minimizerOptions │ │ ├── mergeRules.css │ │ ├── order.css │ │ ├── discardComments.css │ │ └── discardEmpty.css │ ├── simple.js │ └── nesting.css ├── helpers │ ├── getErrors.js │ ├── getWarnings.js │ ├── emitAssetLoader.js │ ├── emitAssetLoader2.js │ ├── compile.js │ ├── index.js │ ├── readAsset.js │ ├── ModifyExistingAsset.js │ ├── readAssets.js │ ├── normalizeErrors.js │ ├── EmitNewAsset.js │ ├── emitAssetInChildCompilationLoader.js │ └── getCompiler.js ├── __snapshots__ │ ├── warningsFilter-option.test.js.snap │ ├── worker.test.js.snap │ ├── test-option.test.js.snap │ ├── exclude-option.test.js.snap │ ├── include-option.test.js.snap │ ├── minimizerOptions-option.test.js.snap │ ├── parallel-option.test.js.snap │ ├── validate-options.test.js.snap │ ├── sourceMap-option.test.js.snap │ ├── cache-option.test.js.snap │ └── CssMinimizerPlugin.test.js.snap ├── warningsFilter-option.test.js ├── test-option.test.js ├── exclude-option.test.js ├── include-option.test.js ├── worker.test.js ├── validate-options.test.js ├── sourceMap-option.test.js ├── parallel-option.test.js ├── cache-option.test.js └── minimizerOptions-option.test.js ├── setupTest.js ├── .prettierignore ├── .gitattributes ├── jest.config.js ├── 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 └── utils.js ├── package.json └── CHANGELOG.md /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | lint-staged 2 | -------------------------------------------------------------------------------- /test/fixtures/test/error.css: -------------------------------------------------------------------------------- 1 | null 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | commitlint --edit $1 2 | -------------------------------------------------------------------------------- /test/fixtures/empty.css: -------------------------------------------------------------------------------- 1 | /* comment */ 2 | -------------------------------------------------------------------------------- /test/fixtures/empty.js: -------------------------------------------------------------------------------- 1 | import "./empty.css"; 2 | -------------------------------------------------------------------------------- /test/fixtures/entry.js: -------------------------------------------------------------------------------- 1 | import './foo.css'; 2 | -------------------------------------------------------------------------------- /test/fixtures/cache-1.js: -------------------------------------------------------------------------------- 1 | import './test/foo.css'; 2 | -------------------------------------------------------------------------------- /test/fixtures/cache.js: -------------------------------------------------------------------------------- 1 | import './test/foo.css'; 2 | -------------------------------------------------------------------------------- /test/fixtures/sss/index.sss: -------------------------------------------------------------------------------- 1 | a 2 | color: black 3 | -------------------------------------------------------------------------------- /test/fixtures/cache-2.js: -------------------------------------------------------------------------------- 1 | import './test/bar1.css'; 2 | -------------------------------------------------------------------------------- /test/fixtures/cache-3.js: -------------------------------------------------------------------------------- 1 | import './test/bar2.css'; 2 | -------------------------------------------------------------------------------- /test/fixtures/cache-4.js: -------------------------------------------------------------------------------- 1 | import './test/bar1.css'; 2 | -------------------------------------------------------------------------------- /test/fixtures/excluded1.js: -------------------------------------------------------------------------------- 1 | import './test/foo.css'; 2 | -------------------------------------------------------------------------------- /test/fixtures/excluded2.js: -------------------------------------------------------------------------------- 1 | import './test/bar1.css'; 2 | -------------------------------------------------------------------------------- /test/fixtures/included1.js: -------------------------------------------------------------------------------- 1 | import './test/foo.css'; 2 | -------------------------------------------------------------------------------- /test/fixtures/included2.js: -------------------------------------------------------------------------------- 1 | import './test/bar1.css'; 2 | -------------------------------------------------------------------------------- /test/fixtures/simple-async.js: -------------------------------------------------------------------------------- 1 | export default 'bar'; 2 | -------------------------------------------------------------------------------- /test/fixtures/simple-emit-2.js: -------------------------------------------------------------------------------- 1 | export default 'bar'; 2 | -------------------------------------------------------------------------------- /test/fixtures/simple-emit.js: -------------------------------------------------------------------------------- 1 | export default 'foo'; 2 | -------------------------------------------------------------------------------- /test/fixtures/sugarss.js: -------------------------------------------------------------------------------- 1 | export const foo = 'foo'; 2 | -------------------------------------------------------------------------------- /test/fixtures/wrong-calc.css: -------------------------------------------------------------------------------- 1 | width: calc(100px + 100); 2 | -------------------------------------------------------------------------------- /test/fixtures/sourcemap/bar.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-weight: bold; 3 | } -------------------------------------------------------------------------------- /test/fixtures/test/foo.css: -------------------------------------------------------------------------------- 1 | a { 2 | text-align: center; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/sourcemap/bar.scss: -------------------------------------------------------------------------------- 1 | body { 2 | font-weight: bold; 3 | } -------------------------------------------------------------------------------- /setupTest.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | jest.setTimeout(120000); 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist 3 | /node_modules 4 | /test/fixtures 5 | CHANGELOG.md -------------------------------------------------------------------------------- /test/fixtures/foo.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: red; 3 | } 4 | a { 5 | color: blue; 6 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | bin/* eol=lf 3 | yarn.lock -diff 4 | package-lock.json -diff -------------------------------------------------------------------------------- /test/fixtures/test/bar2.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: green; 3 | } 4 | h2 { 5 | color: yellow; 6 | } 7 | h3 { 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/minimizerOptions/mergeRules.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: red; 3 | } 4 | 5 | body { 6 | font-weight: bold; 7 | } -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: "node", 3 | setupFilesAfterEnv: ["/setupTest.js"], 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/sourcemap/foo.scss: -------------------------------------------------------------------------------- 1 | @use 'bar'; 2 | 3 | body { 4 | color: red; 5 | a { 6 | text-align: center; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/minimizerOptions/order.css: -------------------------------------------------------------------------------- 1 | p { 2 | border: 1px solid var(--test); 3 | border-radius: var(--test); 4 | border-width: 0; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/sourcemap/foo.css: -------------------------------------------------------------------------------- 1 | @import 'bar.css'; 2 | 3 | body { 4 | color: red; 5 | } 6 | 7 | body a { 8 | text-align: center; 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/minimizerOptions/discardComments.css: -------------------------------------------------------------------------------- 1 | body { 2 | /* this is a comment */ 3 | /*! this is a comment with exclamation mark */ 4 | color: red; 5 | } -------------------------------------------------------------------------------- /test/helpers/getErrors.js: -------------------------------------------------------------------------------- 1 | import normalizeErrors from "./normalizeErrors"; 2 | 3 | export default (stats) => normalizeErrors(stats.compilation.errors).sort(); 4 | -------------------------------------------------------------------------------- /test/fixtures/test/bar1.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: red; 3 | } 4 | body { 5 | color: red; 6 | font-size: 20px; 7 | } 8 | a { 9 | color: blue; 10 | } 11 | -------------------------------------------------------------------------------- /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 | "*": [ 3 | "prettier --cache --write --ignore-unknown", 4 | "cspell --cache --no-must-find-files", 5 | ], 6 | "*.js": ["eslint --cache --fix"], 7 | }; 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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/fixtures/minimizerOptions/discardEmpty.css: -------------------------------------------------------------------------------- 1 | @layer ui-components, foo-bar; 2 | 3 | body { 4 | color: red; 5 | } 6 | 7 | a { 8 | } 9 | 10 | .foo { 11 | color: black; 12 | 13 | & .class { 14 | color: white; 15 | } 16 | } 17 | 18 | @layer ui-components { 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/simple.js: -------------------------------------------------------------------------------- 1 | import foo from './simple-emit'; 2 | import bar from './simple-emit-2'; 3 | import './foo.css'; 4 | 5 | async function load() { 6 | return import('./simple-async'); 7 | } 8 | 9 | load(); 10 | 11 | export default [foo, bar, extracted, css, otherCss]; 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .vscode 4 | logs 5 | *.log 6 | npm-debug.log* 7 | .cspellcache 8 | .eslintcache 9 | /coverage 10 | /dist 11 | /local 12 | /reports 13 | /node_modules 14 | /test/outputs/ 15 | .DS_Store 16 | Thumbs.db 17 | .idea 18 | *.iml 19 | *.sublime-project 20 | *.sublime-workspace 21 | -------------------------------------------------------------------------------- /test/helpers/emitAssetLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} content Loader content 3 | * @returns {string} Processed content 4 | */ 5 | export default function loader(content) { 6 | this.emitFile("style.css", "a { color: red; }"); 7 | 8 | const callback = this.async(); 9 | 10 | return callback(null, content); 11 | } 12 | -------------------------------------------------------------------------------- /test/helpers/emitAssetLoader2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} content Loader content 3 | * @returns {string} Processed content 4 | */ 5 | export default function loader(content) { 6 | this.emitFile("style-2.css", "a { color: coral; }"); 7 | 8 | const callback = this.async(); 9 | 10 | return callback(null, content); 11 | } 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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: "18.12.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/compile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import("webpack").Compiler} compiler Webpack compiler 3 | * @returns {Promise} Promise with webpack stats 4 | */ 5 | export default function compile(compiler) { 6 | return new Promise((resolve, reject) => { 7 | compiler.run((err, stats) => { 8 | if (err) return reject(err); 9 | return resolve(stats); 10 | }); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/nesting.css: -------------------------------------------------------------------------------- 1 | table.colortable { 2 | & td { 3 | text-align:center; 4 | &.c { text-transform:uppercase } 5 | 6 | &:first-child, &:first-child + td { border:1px solid black } 7 | } 8 | 9 | & th { 10 | text-align:center; 11 | background:black; 12 | color:white; 13 | } 14 | } 15 | 16 | .example { 17 | display: grid; 18 | transition: all .5s; 19 | user-select: none; 20 | background: linear-gradient(to bottom, white, black); 21 | } 22 | -------------------------------------------------------------------------------- /test/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { default as getErrors } from "./getErrors"; 2 | export { default as getWarnings } from "./getWarnings"; 3 | export { default as EmitNewAsset } from "./EmitNewAsset"; 4 | export { default as compile } from "./compile"; 5 | export { default as ModifyExistingAsset } from "./ModifyExistingAsset"; 6 | export { default as getCompiler } from "./getCompiler"; 7 | export { default as readAsset } from "./readAsset"; 8 | export { default as normalizeErrors } from "./normalizeErrors"; 9 | export { default as readAssets } from "./readAssets"; 10 | -------------------------------------------------------------------------------- /test/helpers/readAsset.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | export default (asset, compiler, stats) => { 4 | const usedFs = compiler.outputFileSystem; 5 | const outputPath = stats.compilation.outputOptions.path; 6 | 7 | let data = ""; 8 | let targetFile = asset; 9 | 10 | const queryStringIdx = targetFile.indexOf("?"); 11 | 12 | if (queryStringIdx >= 0) { 13 | targetFile = targetFile.slice(0, queryStringIdx); 14 | } 15 | 16 | try { 17 | data = usedFs.readFileSync(path.join(outputPath, targetFile)).toString(); 18 | } catch (error) { 19 | data = error.toString(); 20 | } 21 | 22 | return data; 23 | }; 24 | -------------------------------------------------------------------------------- /test/helpers/ModifyExistingAsset.js: -------------------------------------------------------------------------------- 1 | export default class ExistingCommentsFile { 2 | constructor(options = {}) { 3 | this.options = options; 4 | } 5 | 6 | apply(compiler) { 7 | const plugin = { name: this.constructor.name }; 8 | const { ConcatSource } = compiler.webpack.sources; 9 | 10 | compiler.hooks.thisCompilation.tap(plugin, (compilation) => { 11 | compilation.hooks.additionalAssets.tap(plugin, () => { 12 | compilation.assets[this.options.name] = new ConcatSource( 13 | "a { color: red; }", 14 | compilation.assets[this.options.name], 15 | ); 16 | }); 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/helpers/readAssets.js: -------------------------------------------------------------------------------- 1 | import readAsset from "./readAsset"; 2 | 3 | /** 4 | * @param {import("webpack").Compiler} compiler Webpack compiler 5 | * @param {import("webpack").Stats} stats Webpack stats 6 | * @param {RegExp=} extension File extension filter 7 | * @returns {Record} Assets map 8 | */ 9 | export default function readAssets(compiler, stats, extension) { 10 | const assets = {}; 11 | 12 | for (const asset of Object.keys(stats.compilation.assets)) { 13 | if (typeof extension === "undefined") { 14 | assets[asset] = readAsset(asset, compiler, stats); 15 | } else if (extension.test(asset)) { 16 | assets[asset] = readAsset(asset, compiler, stats); 17 | } 18 | } 19 | 20 | return assets; 21 | } 22 | -------------------------------------------------------------------------------- /test/helpers/normalizeErrors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} str String to process 3 | * @returns {string} Processed string 4 | */ 5 | function removeCWD(str) { 6 | const isWin = process.platform === "win32"; 7 | let cwd = process.cwd(); 8 | let normalizedStr = str; 9 | 10 | if (isWin) { 11 | normalizedStr = normalizedStr.replaceAll("\\", "/"); 12 | cwd = cwd.replaceAll("\\", "/"); 13 | } 14 | 15 | return normalizedStr.replaceAll(new RegExp(cwd, "g"), ""); 16 | } 17 | 18 | /** 19 | * @param {Array} errors Array of errors 20 | * @returns {Array} Normalized error messages 21 | */ 22 | export default (errors) => 23 | errors.map((error) => 24 | removeCWD(error.toString().split("\n").slice(0, 2).join("\n")), 25 | ); 26 | -------------------------------------------------------------------------------- /types/minify.d.ts: -------------------------------------------------------------------------------- 1 | export type MinimizedResult = import("./index.js").MinimizedResult; 2 | export type InternalResult = import("./index.js").InternalResult; 3 | /** @typedef {import("./index.js").MinimizedResult} MinimizedResult */ 4 | /** @typedef {import("./index.js").InternalResult} InternalResult */ 5 | /** 6 | * @template T 7 | * @param {import("./index.js").InternalOptions} options Options 8 | * @returns {Promise} Promise with internal result 9 | */ 10 | export function minify( 11 | options: import("./index.js").InternalOptions, 12 | ): Promise; 13 | /** 14 | * @param {string} options Options string 15 | * @returns {Promise} Promise with internal 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 | .a { 22 | display: block; 23 | color: coral; 24 | } 25 | `), 26 | ); 27 | }, 28 | ); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/__snapshots__/warningsFilter-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`warningsFilter option should match snapshot for a "function" value: assets 1`] = ` 4 | { 5 | "bar1.css": "body { 6 | color: red; 7 | } 8 | body { 9 | color: red; 10 | font-size: 20px; 11 | } 12 | a { 13 | color: blue; 14 | } 15 | 16 | ", 17 | "bar2.css": "h1 { 18 | color: green; 19 | } 20 | h2 { 21 | color: yellow; 22 | } 23 | h3 { 24 | } 25 | 26 | ", 27 | "foo.css": "a { 28 | text-align: center; 29 | } 30 | 31 | ", 32 | } 33 | `; 34 | 35 | exports[`warningsFilter option should match snapshot for a "function" value: errors 1`] = `[]`; 36 | 37 | exports[`warningsFilter option should match snapshot for a "function" value: warnings 1`] = ` 38 | [ 39 | "Warning: bar1.css from Css Minimizer plugin 40 | [warning-plugin] Warning from bar1.css", 41 | "Warning: bar2.css from Css Minimizer plugin 42 | [warning-plugin] Warning from bar2.css", 43 | ] 44 | `; 45 | -------------------------------------------------------------------------------- /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "language": "en,en-gb", 4 | "words": [ 5 | "esbuild", 6 | "csso", 7 | "cssnano", 8 | "Stringifier", 9 | "stringifier", 10 | "KAAK", 11 | "UAAU", 12 | "OAAO", 13 | "colormin", 14 | "sugarss", 15 | "lightningcss", 16 | "minifiers", 17 | "pathinfo", 18 | "memfs", 19 | "colortable", 20 | "fullhash", 21 | "chunkhash", 22 | "OAAOA", 23 | "SAAUE", 24 | "IAAM", 25 | "IAAI", 26 | "IAAID", 27 | "OAAOC", 28 | "SAAUA", 29 | "CAAC", 30 | "minifier", 31 | "sourcemaps", 32 | "processoptions", 33 | "nosources", 34 | "pathinfo", 35 | "commitlint", 36 | "memfs", 37 | "colortable", 38 | "wagoid", 39 | "autocrlf", 40 | "codecov", 41 | "jridgewell" 42 | ], 43 | 44 | "ignorePaths": [ 45 | "CHANGELOG.md", 46 | "package.json", 47 | "dist/**", 48 | "**/__snapshots__/**", 49 | "package-lock.json", 50 | "*.pack", 51 | "node_modules", 52 | "coverage", 53 | "*.log" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /test/helpers/emitAssetInChildCompilationLoader.js: -------------------------------------------------------------------------------- 1 | class PreCopyPlugin { 2 | constructor(options = {}) { 3 | this.options = options.options || {}; 4 | } 5 | 6 | apply(compiler) { 7 | const plugin = { name: "PreCopyPlugin" }; 8 | const { RawSource } = compiler.webpack.sources; 9 | 10 | compiler.hooks.compilation.tap(plugin, (compilation) => { 11 | compilation.hooks.additionalAssets.tapAsync(plugin, (callback) => { 12 | compilation.emitAsset( 13 | "entry.css", 14 | new RawSource(".entry {\n text-align: center;\n}\n\n"), 15 | ); 16 | 17 | callback(); 18 | }); 19 | }); 20 | } 21 | } 22 | 23 | /** 24 | * @returns {string} Loader result 25 | */ 26 | export default function loader() { 27 | const callback = this.async(); 28 | 29 | const childCompiler = this._compilation.createChildCompiler( 30 | "preloader", 31 | this.options, 32 | ); 33 | 34 | new PreCopyPlugin().apply(childCompiler); 35 | 36 | childCompiler.runAsChild((error) => { 37 | if (error) { 38 | return callback(error); 39 | } 40 | 41 | return callback(null, "export default 1"); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /test/helpers/getCompiler.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import { Volume, createFsFromVolume } from "memfs"; 4 | import MiniCssExtractPlugin from "mini-css-extract-plugin"; 5 | import webpack from "webpack"; 6 | 7 | /** 8 | * @param {import("webpack").Configuration} config Webpack configuration 9 | * @returns {import("webpack").Compiler} Webpack compiler 10 | */ 11 | export default function getCompiler(config = {}) { 12 | const compiler = webpack({ 13 | mode: "development", 14 | devtool: config.devtool || false, 15 | context: path.resolve(__dirname, "../fixtures"), 16 | optimization: { 17 | minimize: false, 18 | }, 19 | output: { 20 | pathinfo: false, 21 | path: path.resolve(__dirname, "../outputs"), 22 | filename: "[name].js", 23 | chunkFilename: "[id].[name].js", 24 | }, 25 | plugins: [ 26 | new MiniCssExtractPlugin({ 27 | filename: "[name].css", 28 | chunkFilename: "[id].[name].css", 29 | }), 30 | ], 31 | module: { 32 | rules: [ 33 | { 34 | test: /.s?css$/i, 35 | use: [MiniCssExtractPlugin.loader, "css-loader"], 36 | }, 37 | ], 38 | }, 39 | ...config, 40 | }); 41 | 42 | if (!config.outputFileSystem) { 43 | compiler.outputFileSystem = createFsFromVolume(new Volume()); 44 | } 45 | 46 | return compiler; 47 | } 48 | -------------------------------------------------------------------------------- /test/__snapshots__/worker.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`worker should emit error: error 1`] = ` 4 | [ 5 | "/entry.css:1:1: Unknown word false", 6 | ] 7 | `; 8 | 9 | exports[`worker should minify css: result 1`] = ` 10 | { 11 | "errors": [], 12 | "outputs": [ 13 | { 14 | "code": ".foo{color:red}.bar{color:coral}", 15 | "map": { 16 | "file": "entry.css", 17 | "mappings": "AAAA,KAAK,SAAU,CACf,KAAK,WAAY", 18 | "names": [], 19 | "sources": [ 20 | "entry.css", 21 | ], 22 | "sourcesContent": [ 23 | ".foo{color:red;} 24 | .bar{color:coral;}", 25 | ], 26 | "version": 3, 27 | }, 28 | }, 29 | ], 30 | "warnings": [], 31 | } 32 | `; 33 | 34 | exports[`worker should work inputSourceMap as prev: result 1`] = ` 35 | { 36 | "errors": [], 37 | "outputs": [ 38 | { 39 | "code": ".foo{color:red}.bar{color:coral}", 40 | "map": { 41 | "file": "entry.css", 42 | "mappings": "AAAA,KAAK,SAAU,CACf,KAAK,WAAY", 43 | "names": [], 44 | "sources": [ 45 | "entry.css", 46 | ], 47 | "sourcesContent": [ 48 | ".foo{color:red;} 49 | .bar{color:coral;}", 50 | ], 51 | "version": 3, 52 | }, 53 | }, 54 | ], 55 | "warnings": [], 56 | } 57 | `; 58 | 59 | exports[`worker should work options.minify function: result 1`] = ` 60 | { 61 | "errors": [], 62 | "outputs": [ 63 | { 64 | "code": ".minify {};", 65 | "map": undefined, 66 | }, 67 | ], 68 | "warnings": [], 69 | } 70 | `; 71 | -------------------------------------------------------------------------------- /test/__snapshots__/test-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`when applied with "test" option matches snapshot for a single "test" value (RegExp): assets 1`] = ` 4 | { 5 | "bar1.css": "body{color:red;font-size:20px}a{color:blue}", 6 | "bar2.css": "h1{color:green}h2{color:#ff0}", 7 | "foo.css": "a { 8 | text-align: center; 9 | } 10 | 11 | ", 12 | } 13 | `; 14 | 15 | exports[`when applied with "test" option matches snapshot for a single "test" value (RegExp): errors 1`] = `[]`; 16 | 17 | exports[`when applied with "test" option matches snapshot for a single "test" value (RegExp): warnings 1`] = `[]`; 18 | 19 | exports[`when applied with "test" option matches snapshot for multiple "test" value (RegExp): assets 1`] = ` 20 | { 21 | "bar1.css": "body{color:red;font-size:20px}a{color:blue}", 22 | "bar2.css": "h1{color:green}h2{color:#ff0}", 23 | "foo.css": "a { 24 | text-align: center; 25 | } 26 | 27 | ", 28 | } 29 | `; 30 | 31 | exports[`when applied with "test" option matches snapshot for multiple "test" value (RegExp): errors 1`] = `[]`; 32 | 33 | exports[`when applied with "test" option matches snapshot for multiple "test" value (RegExp): warnings 1`] = `[]`; 34 | 35 | exports[`when applied with "test" option matches snapshot with empty value: assets 1`] = ` 36 | { 37 | "bar1.css": "body{color:red;font-size:20px}a{color:blue}", 38 | "bar2.css": "h1{color:green}h2{color:#ff0}", 39 | "foo.css": "a{text-align:center}", 40 | } 41 | `; 42 | 43 | exports[`when applied with "test" option matches snapshot with empty value: errors 1`] = `[]`; 44 | 45 | exports[`when applied with "test" option matches snapshot with empty value: warnings 1`] = `[]`; 46 | -------------------------------------------------------------------------------- /test/warningsFilter-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import postcss from "postcss"; 4 | 5 | import CssMinimizerPlugin from "../src/index"; 6 | 7 | import { 8 | compile, 9 | getCompiler, 10 | getErrors, 11 | getWarnings, 12 | readAssets, 13 | } from "./helpers"; 14 | 15 | describe("warningsFilter option", () => { 16 | it('should match snapshot for a "function" value', async () => { 17 | const plugin = () => ({ 18 | postcssPlugin: "warning-plugin", 19 | Once(root, { result }) { 20 | result.warn(`Warning from ${result.opts.from}`, { 21 | plugin: "warning-plugin", 22 | }); 23 | }, 24 | }); 25 | 26 | plugin.postcss = true; 27 | 28 | const compiler = getCompiler({ 29 | entry: { 30 | foo: path.join(__dirname, "fixtures", "test", "foo.css"), 31 | bar1: path.join(__dirname, "fixtures", "test", "bar1.css"), 32 | bar2: path.join(__dirname, "fixtures", "test", "bar2.css"), 33 | }, 34 | }); 35 | 36 | new CssMinimizerPlugin({ 37 | parallel: false, 38 | minify: (data) => { 39 | const [[fileName, input]] = Object.entries(data); 40 | 41 | return postcss([plugin]) 42 | .process(input, { from: fileName, to: fileName }) 43 | .then((result) => ({ 44 | code: result.css, 45 | map: result.map, 46 | error: result.error, 47 | warnings: result.warnings(), 48 | })); 49 | }, 50 | warningsFilter(warning, file) { 51 | if (/foo/.test(file)) { 52 | return false; 53 | } 54 | 55 | return true; 56 | }, 57 | }).apply(compiler); 58 | 59 | const stats = await compile(compiler); 60 | 61 | expect(readAssets(compiler, stats, /\.css$/)).toMatchSnapshot("assets"); 62 | expect(getErrors(stats)).toMatchSnapshot("errors"); 63 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/test-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import CssMinimizerPlugin from "../src/index"; 4 | 5 | import { 6 | compile, 7 | getCompiler, 8 | getErrors, 9 | getWarnings, 10 | readAssets, 11 | } from "./helpers"; 12 | 13 | describe('when applied with "test" option', () => { 14 | let compiler; 15 | 16 | beforeEach(() => { 17 | compiler = getCompiler({ 18 | entry: { 19 | bar1: path.join(__dirname, "fixtures", "test", "bar1.css"), 20 | bar2: path.join(__dirname, "fixtures", "test", "bar2.css"), 21 | foo: path.join(__dirname, "fixtures", "test", "foo.css"), 22 | }, 23 | }); 24 | }); 25 | 26 | it("matches snapshot with empty value", async () => { 27 | new CssMinimizerPlugin().apply(compiler); 28 | 29 | const stats = await compile(compiler); 30 | 31 | expect(readAssets(compiler, stats, /\.css$/)).toMatchSnapshot("assets"); 32 | expect(getErrors(stats)).toMatchSnapshot("errors"); 33 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 34 | }); 35 | 36 | it('matches snapshot for a single "test" value (RegExp)', async () => { 37 | new CssMinimizerPlugin({ 38 | test: /bar.*\.css$/, 39 | }).apply(compiler); 40 | 41 | const stats = await compile(compiler); 42 | 43 | expect(readAssets(compiler, stats, /\.css$/)).toMatchSnapshot("assets"); 44 | expect(getErrors(stats)).toMatchSnapshot("errors"); 45 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 46 | }); 47 | 48 | it('matches snapshot for multiple "test" value (RegExp)', async () => { 49 | new CssMinimizerPlugin({ 50 | test: [/bar1.*\.css$/, /bar2.*\.css$/], 51 | }).apply(compiler); 52 | 53 | const stats = await compile(compiler); 54 | 55 | expect(readAssets(compiler, stats, /\.css$/)).toMatchSnapshot("assets"); 56 | expect(getErrors(stats)).toMatchSnapshot("errors"); 57 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/exclude-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import CssMinimizerPlugin from "../src/index"; 4 | 5 | import { 6 | compile, 7 | getCompiler, 8 | getErrors, 9 | getWarnings, 10 | readAssets, 11 | } from "./helpers"; 12 | 13 | describe("exclude option", () => { 14 | let compiler; 15 | 16 | beforeEach(() => { 17 | compiler = getCompiler({ 18 | entry: { 19 | excluded1: path.join(__dirname, "fixtures", "excluded1.js"), 20 | excluded2: path.join(__dirname, "fixtures", "excluded2.js"), 21 | entry: path.join(__dirname, "fixtures", "entry.js"), 22 | }, 23 | }); 24 | }); 25 | 26 | it("should match snapshot for a single RegExp value excluded1", async () => { 27 | new CssMinimizerPlugin({ 28 | exclude: /excluded1/i, 29 | }).apply(compiler); 30 | 31 | const stats = await compile(compiler); 32 | 33 | expect(readAssets(compiler, stats, /\.css$/)).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 excluded1", async () => { 39 | new CssMinimizerPlugin({ 40 | exclude: "excluded1", 41 | }).apply(compiler); 42 | 43 | const stats = await compile(compiler); 44 | 45 | expect(readAssets(compiler, stats, /\.css$/)).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 excluded1 and excluded2", async () => { 51 | new CssMinimizerPlugin({ 52 | exclude: [/excluded1/i, /excluded2/i], 53 | }).apply(compiler); 54 | 55 | const stats = await compile(compiler); 56 | 57 | expect(readAssets(compiler, stats, /\.css$/)).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 excluded1 and excluded2", async () => { 63 | new CssMinimizerPlugin({ 64 | exclude: ["excluded1", "excluded2"], 65 | }).apply(compiler); 66 | 67 | const stats = await compile(compiler); 68 | 69 | expect(readAssets(compiler, stats, /\.css$/)).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 "node:path"; 2 | 3 | import CssMinimizerPlugin from "../src/index"; 4 | 5 | import { 6 | compile, 7 | getCompiler, 8 | getErrors, 9 | getWarnings, 10 | readAssets, 11 | } from "./helpers"; 12 | 13 | describe("include option", () => { 14 | let compiler; 15 | 16 | beforeEach(() => { 17 | compiler = getCompiler({ 18 | entry: { 19 | included1: path.join(__dirname, "fixtures", "included1.js"), 20 | included2: path.join(__dirname, "fixtures", "included2.js"), 21 | entry: path.join(__dirname, "fixtures", "entry.js"), 22 | }, 23 | }); 24 | }); 25 | 26 | it("should match snapshot for a single RegExp value included1", async () => { 27 | new CssMinimizerPlugin({ 28 | include: /included1/i, 29 | }).apply(compiler); 30 | 31 | const stats = await compile(compiler); 32 | 33 | expect(readAssets(compiler, stats, /\.css$/)).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 included1", async () => { 39 | new CssMinimizerPlugin({ 40 | include: "included1", 41 | }).apply(compiler); 42 | 43 | const stats = await compile(compiler); 44 | 45 | expect(readAssets(compiler, stats, /\.css$/)).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 included1 and included2", async () => { 51 | new CssMinimizerPlugin({ 52 | include: [/included1/i, /included2/i], 53 | }).apply(compiler); 54 | 55 | const stats = await compile(compiler); 56 | 57 | expect(readAssets(compiler, stats, /\.css$/)).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 included1 and included2", async () => { 63 | new CssMinimizerPlugin({ 64 | include: ["included1", "included2"], 65 | }).apply(compiler); 66 | 67 | const stats = await compile(compiler); 68 | 69 | expect(readAssets(compiler, stats, /\.css$/)).toMatchSnapshot("assets"); 70 | expect(getErrors(stats)).toMatchSnapshot("errors"); 71 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /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 excluded1: assets 1`] = ` 4 | { 5 | "entry.css": "body{color:red}a{color:blue}", 6 | "excluded1.css": "a { 7 | text-align: center; 8 | } 9 | 10 | ", 11 | "excluded2.css": "body{color:red;font-size:20px}a{color:blue}", 12 | } 13 | `; 14 | 15 | exports[`exclude option should match snapshot for a single RegExp value excluded1: errors 1`] = `[]`; 16 | 17 | exports[`exclude option should match snapshot for a single RegExp value excluded1: warnings 1`] = `[]`; 18 | 19 | exports[`exclude option should match snapshot for a single String value excluded1: assets 1`] = ` 20 | { 21 | "entry.css": "body{color:red}a{color:blue}", 22 | "excluded1.css": "a { 23 | text-align: center; 24 | } 25 | 26 | ", 27 | "excluded2.css": "body{color:red;font-size:20px}a{color:blue}", 28 | } 29 | `; 30 | 31 | exports[`exclude option should match snapshot for a single String value excluded1: errors 1`] = `[]`; 32 | 33 | exports[`exclude option should match snapshot for a single String value excluded1: warnings 1`] = `[]`; 34 | 35 | exports[`exclude option should match snapshot for multiple RegExp values excluded1 and excluded2: assets 1`] = ` 36 | { 37 | "entry.css": "body{color:red}a{color:blue}", 38 | "excluded1.css": "a { 39 | text-align: center; 40 | } 41 | 42 | ", 43 | "excluded2.css": "body { 44 | color: red; 45 | } 46 | body { 47 | color: red; 48 | font-size: 20px; 49 | } 50 | a { 51 | color: blue; 52 | } 53 | 54 | ", 55 | } 56 | `; 57 | 58 | exports[`exclude option should match snapshot for multiple RegExp values excluded1 and excluded2: errors 1`] = `[]`; 59 | 60 | exports[`exclude option should match snapshot for multiple RegExp values excluded1 and excluded2: warnings 1`] = `[]`; 61 | 62 | exports[`exclude option should match snapshot for multiple String values excluded1 and excluded2: assets 1`] = ` 63 | { 64 | "entry.css": "body{color:red}a{color:blue}", 65 | "excluded1.css": "a { 66 | text-align: center; 67 | } 68 | 69 | ", 70 | "excluded2.css": "body { 71 | color: red; 72 | } 73 | body { 74 | color: red; 75 | font-size: 20px; 76 | } 77 | a { 78 | color: blue; 79 | } 80 | 81 | ", 82 | } 83 | `; 84 | 85 | exports[`exclude option should match snapshot for multiple String values excluded1 and excluded2: errors 1`] = `[]`; 86 | 87 | exports[`exclude option should match snapshot for multiple String values excluded1 and excluded2: warnings 1`] = `[]`; 88 | -------------------------------------------------------------------------------- /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 included1: assets 1`] = ` 4 | { 5 | "entry.css": "body { 6 | color: red; 7 | } 8 | a { 9 | color: blue; 10 | } 11 | ", 12 | "included1.css": "a{text-align:center}", 13 | "included2.css": "body { 14 | color: red; 15 | } 16 | body { 17 | color: red; 18 | font-size: 20px; 19 | } 20 | a { 21 | color: blue; 22 | } 23 | 24 | ", 25 | } 26 | `; 27 | 28 | exports[`include option should match snapshot for a single RegExp value included1: errors 1`] = `[]`; 29 | 30 | exports[`include option should match snapshot for a single RegExp value included1: warnings 1`] = `[]`; 31 | 32 | exports[`include option should match snapshot for a single String value included1: assets 1`] = ` 33 | { 34 | "entry.css": "body { 35 | color: red; 36 | } 37 | a { 38 | color: blue; 39 | } 40 | ", 41 | "included1.css": "a{text-align:center}", 42 | "included2.css": "body { 43 | color: red; 44 | } 45 | body { 46 | color: red; 47 | font-size: 20px; 48 | } 49 | a { 50 | color: blue; 51 | } 52 | 53 | ", 54 | } 55 | `; 56 | 57 | exports[`include option should match snapshot for a single String value included1: errors 1`] = `[]`; 58 | 59 | exports[`include option should match snapshot for a single String value included1: warnings 1`] = `[]`; 60 | 61 | exports[`include option should match snapshot for multiple RegExp values included1 and included2: assets 1`] = ` 62 | { 63 | "entry.css": "body { 64 | color: red; 65 | } 66 | a { 67 | color: blue; 68 | } 69 | ", 70 | "included1.css": "a{text-align:center}", 71 | "included2.css": "body{color:red;font-size:20px}a{color:blue}", 72 | } 73 | `; 74 | 75 | exports[`include option should match snapshot for multiple RegExp values included1 and included2: errors 1`] = `[]`; 76 | 77 | exports[`include option should match snapshot for multiple RegExp values included1 and included2: warnings 1`] = `[]`; 78 | 79 | exports[`include option should match snapshot for multiple String values included1 and included2: assets 1`] = ` 80 | { 81 | "entry.css": "body { 82 | color: red; 83 | } 84 | a { 85 | color: blue; 86 | } 87 | ", 88 | "included1.css": "a{text-align:center}", 89 | "included2.css": "body{color:red;font-size:20px}a{color:blue}", 90 | } 91 | `; 92 | 93 | exports[`include option should match snapshot for multiple String values included1 and included2: errors 1`] = `[]`; 94 | 95 | exports[`include option should match snapshot for multiple String values included1 and included2: warnings 1`] = `[]`; 96 | -------------------------------------------------------------------------------- /test/worker.test.js: -------------------------------------------------------------------------------- 1 | import serialize from "serialize-javascript"; 2 | 3 | import CssMinimizerPlugin from "../src"; 4 | import { transform } from "../src/minify"; 5 | 6 | import { normalizeErrors } from "./helpers"; 7 | 8 | describe("worker", () => { 9 | it("should minify css", async () => { 10 | const options = { 11 | name: "entry.css", 12 | input: ".foo{color:red;}\n.bar{color:coral;}", 13 | inputSourceMap: { 14 | version: 3, 15 | sources: ["foo.css", "bar.css"], 16 | names: [], 17 | mappings: "AAAA,KAAK,iBAAiB,KAAK,UAAU,OAAO", 18 | file: "x", 19 | sourcesContent: [".foo{color:red;}", ".bar{color:coral;}"], 20 | }, 21 | minimizer: { 22 | implementation: CssMinimizerPlugin.cssnanoMinify, 23 | options: { discardComments: false }, 24 | }, 25 | }; 26 | const result = await transform(serialize(options)); 27 | 28 | expect(result).toMatchSnapshot("result"); 29 | }); 30 | 31 | it("should work inputSourceMap as prev", async () => { 32 | const options = { 33 | name: "entry.css", 34 | input: ".foo{color:red;}\n.bar{color:coral;}", 35 | inputSourceMap: { 36 | version: 3, 37 | sources: ["foo.css", "bar.css"], 38 | names: [], 39 | mappings: "AAAA,KAAK,iBAAiB,KAAK,UAAU,OAAO", 40 | file: "x", 41 | sourcesContent: [".foo{color:red;}", ".bar{color:coral;}"], 42 | }, 43 | minimizer: { 44 | implementation: CssMinimizerPlugin.cssnanoMinify, 45 | options: { discardComments: false }, 46 | }, 47 | }; 48 | const result = await transform(serialize(options)); 49 | 50 | expect(result).toMatchSnapshot("result"); 51 | }); 52 | 53 | it("should work options.minify function", async () => { 54 | const options = { 55 | name: "entry.css", 56 | input: ".foo{color:red;}\n.bar{color:coral;}", 57 | minimizer: { 58 | implementation: () => ({ code: ".minify {};" }), 59 | options: { discardComments: false }, 60 | }, 61 | }; 62 | const result = await transform(serialize(options)); 63 | 64 | expect(result).toMatchSnapshot("result"); 65 | }); 66 | 67 | it("should emit error", async () => { 68 | const options = { 69 | name: "entry.css", 70 | input: false, 71 | minimizer: { 72 | implementation: CssMinimizerPlugin.cssnanoMinify, 73 | options: { preset: "default" }, 74 | }, 75 | }; 76 | 77 | try { 78 | await transform(serialize(options)); 79 | } catch (error) { 80 | const normalizeError = { ...error }; 81 | 82 | normalizeError.message = [error.message.split("\n")]; 83 | 84 | expect(normalizeErrors(normalizeError.message)).toMatchSnapshot("error"); 85 | } 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /src/minify.js: -------------------------------------------------------------------------------- 1 | /** @typedef {import("./index.js").MinimizedResult} MinimizedResult */ 2 | /** @typedef {import("./index.js").InternalResult} InternalResult */ 3 | 4 | /** 5 | * @template T 6 | * @param {import("./index.js").InternalOptions} options Options 7 | * @returns {Promise} Promise with internal result 8 | */ 9 | async function minify(options) { 10 | const minifyFns = Array.isArray(options.minimizer.implementation) 11 | ? options.minimizer.implementation 12 | : [options.minimizer.implementation]; 13 | 14 | /** @type {InternalResult} */ 15 | const result = { outputs: [], warnings: [], errors: [] }; 16 | 17 | let needSourceMap = false; 18 | 19 | for (let i = 0; i <= minifyFns.length - 1; i++) { 20 | const minifyFn = minifyFns[i]; 21 | const minifyOptions = Array.isArray(options.minimizer.options) 22 | ? options.minimizer.options[i] 23 | : options.minimizer.options; 24 | const prevResult = 25 | result.outputs.length > 0 26 | ? result.outputs[result.outputs.length - 1] 27 | : { code: options.input, map: options.inputSourceMap }; 28 | const { code, map } = prevResult; 29 | 30 | const minifyResult = await minifyFn( 31 | { [options.name]: code }, 32 | map, 33 | minifyOptions, 34 | ); 35 | 36 | if (typeof minifyResult.code !== "string") { 37 | result.errors.push( 38 | new Error( 39 | "minimizer function doesn't return the 'code' property or result is not a string value", 40 | ), 41 | ); 42 | } 43 | 44 | if (minifyResult.map) { 45 | needSourceMap = true; 46 | } 47 | 48 | if (minifyResult.errors) { 49 | result.errors = [...result.errors, ...minifyResult.errors]; 50 | } 51 | 52 | if (minifyResult.warnings) { 53 | result.warnings = [...result.warnings, ...minifyResult.warnings]; 54 | } 55 | 56 | result.outputs.push({ code: minifyResult.code, map: minifyResult.map }); 57 | } 58 | 59 | if (!needSourceMap) { 60 | result.outputs = [result.outputs[result.outputs.length - 1]]; 61 | } 62 | 63 | return result; 64 | } 65 | 66 | /** 67 | * @param {string} options Options string 68 | * @returns {Promise} Promise with internal result 69 | */ 70 | async function transform(options) { 71 | // 'use strict' => this === undefined (Clean Scope) 72 | // Safer for possible security issues, albeit not critical at all here 73 | // eslint-disable-next-line no-new-func 74 | const evaluatedOptions = new Function( 75 | "exports", 76 | "require", 77 | "module", 78 | "__filename", 79 | "__dirname", 80 | `'use strict'\nreturn ${options}`, 81 | )(module.exports, require, module, __filename, __dirname); 82 | 83 | return minify(evaluatedOptions); 84 | } 85 | 86 | module.exports = { minify, transform }; 87 | -------------------------------------------------------------------------------- /test/__snapshots__/minimizerOptions-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`when applied with "minimizerOptions" option matches snapshot for "discardComments" option (disable): entry.css 1`] = ` 4 | "body{ 5 | /* this is a comment */ 6 | /*! this is a comment with exclamation mark */color:red}" 7 | `; 8 | 9 | exports[`when applied with "minimizerOptions" option matches snapshot for "discardComments" option (enable [default]): entry.css 1`] = ` 10 | "body{ 11 | /*! this is a comment with exclamation mark */color:red}" 12 | `; 13 | 14 | exports[`when applied with "minimizerOptions" option matches snapshot for "discardComments" option (enable, with "removeAll" option): entry.css 1`] = `"body{color:red}"`; 15 | 16 | exports[`when applied with "minimizerOptions" option matches snapshot for "discardEmpty" option (disable): entry.css 1`] = `"@layer ui-components, foo-bar;body{color:red}a{}.foo{color:#000;& .class{color:#fff}}@layer ui-components{}"`; 17 | 18 | exports[`when applied with "minimizerOptions" option matches snapshot for "discardEmpty" option (enable [default]): entry.css 1`] = `"@layer ui-components, foo-bar;body{color:red}.foo{color:#000;& .class{color:#fff}}@layer ui-components{}"`; 19 | 20 | exports[`when applied with "minimizerOptions" option matches snapshot for "mergeRules" option (disable): entry.css 1`] = `"body{color:red}body{font-weight:700}"`; 21 | 22 | exports[`when applied with "minimizerOptions" option matches snapshot for "mergeRules" option (enable [default]): entry.css 1`] = `"body{color:red;font-weight:700}"`; 23 | 24 | exports[`when applied with "minimizerOptions" option matches snapshot for "parser" option with "Function" value: index.sss 1`] = `"a{color:#000}"`; 25 | 26 | exports[`when applied with "minimizerOptions" option matches snapshot for "parser" option with "String" value: index.sss 1`] = `"a{color:#000}"`; 27 | 28 | exports[`when applied with "minimizerOptions" option matches snapshot for "preset" option with require.resolve "String" value: default-preset 1`] = `"p{border-radius:var(--test);border-width:1px;border:0 solid var(--test)}"`; 29 | 30 | exports[`when applied with "minimizerOptions" option matches snapshot for "preset" option with require.resolve "String" value: preset-simple 1`] = `"p{border-radius:var(--test);border-width:1px;border:0 solid var(--test)}"`; 31 | 32 | exports[`when applied with "minimizerOptions" option matches snapshot for "stringifier" option with "Function" value: entry.css 1`] = `"body color:reda color:blue"`; 33 | 34 | exports[`when applied with "minimizerOptions" option matches snapshot for "stringifier" option with "String" value: entry.css 1`] = `"body color:reda color:blue"`; 35 | 36 | exports[`when applied with "minimizerOptions" option matches snapshot for "syntax" option with "Function" value: index.sss 1`] = `"a color:#000"`; 37 | 38 | exports[`when applied with "minimizerOptions" option matches snapshot for "syntax" option with "String" value: index.sss 1`] = `"a color:#000"`; 39 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: css-minimizer-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: [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 | 86 | - name: Use Node.js ${{ matrix.node-version }} 87 | uses: actions/setup-node@v4 88 | with: 89 | node-version: ${{ matrix.node-version }} 90 | cache: "npm" 91 | 92 | - name: Install dependencies 93 | run: npm ci 94 | 95 | - name: Install webpack ${{ matrix.webpack-version }} 96 | if: matrix.webpack-version != 'latest' 97 | run: npm i webpack@${{ matrix.webpack-version }} 98 | 99 | - name: Run tests for webpack version ${{ matrix.webpack-version }} 100 | run: npm run test:coverage -- --ci 101 | 102 | - name: Submit coverage data to codecov 103 | uses: codecov/codecov-action@v5 104 | with: 105 | token: ${{ secrets.CODECOV_TOKEN }} 106 | -------------------------------------------------------------------------------- /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 | "MinimizerOptions": { 36 | "additionalProperties": true, 37 | "type": "object" 38 | } 39 | }, 40 | "title": "CssMinimizerWebpackPluginOptions", 41 | "type": "object", 42 | "properties": { 43 | "test": { 44 | "description": "Include all modules that pass test assertion.", 45 | "link": "https://github.com/webpack/css-minimizer-webpack-plugin/#test", 46 | "oneOf": [ 47 | { 48 | "$ref": "#/definitions/Rules" 49 | } 50 | ] 51 | }, 52 | "include": { 53 | "description": "Include all modules matching any of these conditions.", 54 | "link": "https://github.com/webpack/css-minimizer-webpack-plugin/#include", 55 | "oneOf": [ 56 | { 57 | "$ref": "#/definitions/Rules" 58 | } 59 | ] 60 | }, 61 | "exclude": { 62 | "description": "Exclude all modules matching any of these conditions.", 63 | "link": "https://github.com/webpack/css-minimizer-webpack-plugin/#exclude", 64 | "oneOf": [ 65 | { 66 | "$ref": "#/definitions/Rules" 67 | } 68 | ] 69 | }, 70 | "minimizerOptions": { 71 | "description": "Options for `cssMinimizerOptions`.", 72 | "link": "https://github.com/webpack/css-minimizer-webpack-plugin/#minimizeroptions", 73 | "anyOf": [ 74 | { 75 | "$ref": "#/definitions/MinimizerOptions" 76 | }, 77 | { 78 | "type": "array", 79 | "minItems": 1, 80 | "items": { 81 | "$ref": "#/definitions/MinimizerOptions" 82 | } 83 | } 84 | ] 85 | }, 86 | "parallel": { 87 | "description": "Use multi-process parallel running to improve the build speed.", 88 | "link": "https://github.com/webpack/css-minimizer-webpack-plugin/#parallel", 89 | "anyOf": [ 90 | { 91 | "type": "boolean" 92 | }, 93 | { 94 | "type": "integer" 95 | } 96 | ] 97 | }, 98 | "warningsFilter": { 99 | "description": "Allow to filter `css minimizer` warnings.", 100 | "link": "https://github.com/webpack/css-minimizer-webpack-plugin/#warningsfilter", 101 | "instanceof": "Function" 102 | }, 103 | "minify": { 104 | "description": "Allows you to override default minify function.", 105 | "link": "https://github.com/webpack/css-minimizer-webpack-plugin/#minify", 106 | "anyOf": [ 107 | { 108 | "instanceof": "Function" 109 | }, 110 | { 111 | "type": "array", 112 | "minItems": 1, 113 | "items": { 114 | "instanceof": "Function" 115 | } 116 | } 117 | ] 118 | } 119 | }, 120 | "additionalProperties": false 121 | } 122 | -------------------------------------------------------------------------------- /test/validate-options.test.js: -------------------------------------------------------------------------------- 1 | import CssMinimizerPlugin from "../src"; 2 | 3 | describe("validation", () => { 4 | it("validation", () => { 5 | /* eslint-disable no-new */ 6 | expect(() => { 7 | new CssMinimizerPlugin({ test: /foo/ }); 8 | }).not.toThrow(); 9 | 10 | expect(() => { 11 | new CssMinimizerPlugin({ test: "foo" }); 12 | }).not.toThrow(); 13 | 14 | expect(() => { 15 | new CssMinimizerPlugin({ test: [/foo/] }); 16 | }).not.toThrow(); 17 | 18 | expect(() => { 19 | new CssMinimizerPlugin({ test: [/foo/, /bar/] }); 20 | }).not.toThrow(); 21 | 22 | expect(() => { 23 | new CssMinimizerPlugin({ test: ["foo", "bar"] }); 24 | }).not.toThrow(); 25 | 26 | expect(() => { 27 | new CssMinimizerPlugin({ test: [/foo/, "bar"] }); 28 | }).not.toThrow(); 29 | 30 | expect(() => { 31 | new CssMinimizerPlugin({ test: true }); 32 | }).toThrowErrorMatchingSnapshot(); 33 | 34 | expect(() => { 35 | new CssMinimizerPlugin({ test: [true] }); 36 | }).toThrowErrorMatchingSnapshot(); 37 | 38 | expect(() => { 39 | new CssMinimizerPlugin({ include: /foo/ }); 40 | }).not.toThrow(); 41 | 42 | expect(() => { 43 | new CssMinimizerPlugin({ include: "foo" }); 44 | }).not.toThrow(); 45 | 46 | expect(() => { 47 | new CssMinimizerPlugin({ include: [/foo/] }); 48 | }).not.toThrow(); 49 | 50 | expect(() => { 51 | new CssMinimizerPlugin({ include: [/foo/, /bar/] }); 52 | }).not.toThrow(); 53 | 54 | expect(() => { 55 | new CssMinimizerPlugin({ include: ["foo", "bar"] }); 56 | }).not.toThrow(); 57 | 58 | expect(() => { 59 | new CssMinimizerPlugin({ include: [/foo/, "bar"] }); 60 | }).not.toThrow(); 61 | 62 | expect(() => { 63 | new CssMinimizerPlugin({ include: true }); 64 | }).toThrowErrorMatchingSnapshot(); 65 | 66 | expect(() => { 67 | new CssMinimizerPlugin({ include: [true] }); 68 | }).toThrowErrorMatchingSnapshot(); 69 | 70 | expect(() => { 71 | new CssMinimizerPlugin({ exclude: /foo/ }); 72 | }).not.toThrow(); 73 | 74 | expect(() => { 75 | new CssMinimizerPlugin({ exclude: "foo" }); 76 | }).not.toThrow(); 77 | 78 | expect(() => { 79 | new CssMinimizerPlugin({ exclude: [/foo/] }); 80 | }).not.toThrow(); 81 | 82 | expect(() => { 83 | new CssMinimizerPlugin({ exclude: [/foo/, /bar/] }); 84 | }).not.toThrow(); 85 | 86 | expect(() => { 87 | new CssMinimizerPlugin({ exclude: ["foo", "bar"] }); 88 | }).not.toThrow(); 89 | 90 | expect(() => { 91 | new CssMinimizerPlugin({ exclude: [/foo/, "bar"] }); 92 | }).not.toThrow(); 93 | 94 | expect(() => { 95 | new CssMinimizerPlugin({ exclude: true }); 96 | }).toThrowErrorMatchingSnapshot(); 97 | 98 | expect(() => { 99 | new CssMinimizerPlugin({ exclude: [true] }); 100 | }).toThrowErrorMatchingSnapshot(); 101 | 102 | expect(() => { 103 | new CssMinimizerPlugin({ minimizerOptions: {} }); 104 | }).not.toThrow(); 105 | 106 | expect(() => { 107 | new CssMinimizerPlugin({ minimizerOptions: [{}] }); 108 | }).not.toThrow(); 109 | 110 | expect(() => { 111 | new CssMinimizerPlugin({ minimizerOptions: null }); 112 | }).toThrowErrorMatchingSnapshot(); 113 | 114 | expect(() => { 115 | new CssMinimizerPlugin({ 116 | minimizerOptions: { colormin: true }, 117 | }); 118 | }).not.toThrow(); 119 | 120 | expect(() => { 121 | new CssMinimizerPlugin({ parallel: true }); 122 | }).not.toThrow(); 123 | 124 | expect(() => { 125 | new CssMinimizerPlugin({ parallel: false }); 126 | }).not.toThrow(); 127 | 128 | expect(() => { 129 | new CssMinimizerPlugin({ parallel: 2 }); 130 | }).not.toThrow(); 131 | 132 | expect(() => { 133 | new CssMinimizerPlugin({ parallel: "2" }); 134 | }).toThrowErrorMatchingSnapshot(); 135 | 136 | expect(() => { 137 | new CssMinimizerPlugin({ parallel: {} }); 138 | }).toThrowErrorMatchingSnapshot(); 139 | 140 | expect(() => { 141 | new CssMinimizerPlugin({ minify() {} }); 142 | }).not.toThrow(); 143 | 144 | expect(() => { 145 | new CssMinimizerPlugin({ minify: [() => {}] }); 146 | }).not.toThrow(); 147 | 148 | expect(() => { 149 | new CssMinimizerPlugin({ minify: true }); 150 | }).toThrowErrorMatchingSnapshot(); 151 | 152 | expect(() => { 153 | new CssMinimizerPlugin({ unknown: true }); 154 | }).toThrowErrorMatchingSnapshot(); 155 | /* eslint-enable no-new */ 156 | }); 157 | }); 158 | -------------------------------------------------------------------------------- /types/utils.d.ts: -------------------------------------------------------------------------------- 1 | export type Task = () => Promise; 2 | export type Input = import("./index.js").Input; 3 | export type RawSourceMap = import("./index.js").RawSourceMap; 4 | export type MinimizedResult = import("./index.js").MinimizedResult; 5 | export type CustomOptions = import("./index.js").CustomOptions; 6 | export type ProcessOptions = import("postcss").ProcessOptions; 7 | export type Postcss = import("postcss").Postcss; 8 | /** 9 | * @param {Input} input Input 10 | * @param {RawSourceMap=} sourceMap Source map 11 | * @param {CustomOptions=} minimizerOptions Minimizer options 12 | * @returns {Promise} Promise with minimized result 13 | */ 14 | export function cleanCssMinify( 15 | input: Input, 16 | sourceMap?: RawSourceMap | undefined, 17 | minimizerOptions?: CustomOptions | undefined, 18 | ): Promise; 19 | export namespace cleanCssMinify { 20 | function supportsWorkerThreads(): boolean; 21 | } 22 | /** 23 | * @param {Input} input Input 24 | * @param {RawSourceMap=} sourceMap Source map 25 | * @param {CustomOptions=} minimizerOptions Minimizer options 26 | * @returns {Promise} Promise with minimized result 27 | */ 28 | export function cssnanoMinify( 29 | input: Input, 30 | sourceMap?: RawSourceMap | undefined, 31 | minimizerOptions?: CustomOptions | undefined, 32 | ): Promise; 33 | export namespace cssnanoMinify { 34 | function supportsWorkerThreads(): boolean; 35 | } 36 | /** 37 | * @param {Input} input Input 38 | * @param {RawSourceMap=} sourceMap Source map 39 | * @param {CustomOptions=} minimizerOptions Minimizer options 40 | * @returns {Promise} Promise with minimized result 41 | */ 42 | export function cssoMinify( 43 | input: Input, 44 | sourceMap?: RawSourceMap | undefined, 45 | minimizerOptions?: CustomOptions | undefined, 46 | ): Promise; 47 | export namespace cssoMinify { 48 | function supportsWorkerThreads(): boolean; 49 | } 50 | /** 51 | * @param {Input} input Input 52 | * @param {RawSourceMap=} sourceMap Source map 53 | * @param {CustomOptions=} minimizerOptions Minimizer options 54 | * @returns {Promise} Promise with minimized result 55 | */ 56 | export function esbuildMinify( 57 | input: Input, 58 | sourceMap?: RawSourceMap | undefined, 59 | minimizerOptions?: CustomOptions | undefined, 60 | ): Promise; 61 | export namespace esbuildMinify { 62 | function supportsWorkerThreads(): boolean; 63 | } 64 | /** 65 | * @param {Input} input Input 66 | * @param {RawSourceMap=} sourceMap Source map 67 | * @param {CustomOptions=} minimizerOptions Minimizer options 68 | * @returns {Promise} Promise with minimized result 69 | */ 70 | export function lightningCssMinify( 71 | input: Input, 72 | sourceMap?: RawSourceMap | undefined, 73 | minimizerOptions?: CustomOptions | undefined, 74 | ): Promise; 75 | export namespace lightningCssMinify { 76 | function supportsWorkerThreads(): boolean; 77 | } 78 | /** 79 | * @template T 80 | * @param {(() => unknown) | undefined} fn Function to memoize 81 | * @returns {() => T} Memoized function 82 | */ 83 | export function memoize(fn: (() => unknown) | undefined): () => T; 84 | /** 85 | * @param {Input} input Input 86 | * @param {RawSourceMap=} sourceMap Source map 87 | * @param {CustomOptions=} minimizerOptions Minimizer options 88 | * @returns {Promise} Promise with minimized result 89 | */ 90 | export function parcelCssMinify( 91 | input: Input, 92 | sourceMap?: RawSourceMap | undefined, 93 | minimizerOptions?: CustomOptions | undefined, 94 | ): Promise; 95 | export namespace parcelCssMinify { 96 | function supportsWorkerThreads(): boolean; 97 | } 98 | /** 99 | * @param {Input} input Input 100 | * @param {RawSourceMap=} sourceMap Source map 101 | * @param {CustomOptions=} minimizerOptions Minimizer options 102 | * @returns {Promise} Promise with minimized result 103 | */ 104 | export function swcMinify( 105 | input: Input, 106 | sourceMap?: RawSourceMap | undefined, 107 | minimizerOptions?: CustomOptions | undefined, 108 | ): Promise; 109 | export namespace swcMinify { 110 | function supportsWorkerThreads(): boolean; 111 | } 112 | /** 113 | * @template T 114 | * @typedef {() => Promise} Task 115 | */ 116 | /** 117 | * Run tasks with limited concurrency. 118 | * @template T 119 | * @param {number} limit Limit of tasks that run at once. 120 | * @param {Task[]} tasks List of tasks to run. 121 | * @returns {Promise} A promise that fulfills to an array of the results 122 | */ 123 | export function throttleAll(limit: number, tasks: Task[]): Promise; 124 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-minimizer-webpack-plugin", 3 | "version": "7.0.4", 4 | "description": "CSS minimizer (minifier) plugin for Webpack", 5 | "keywords": [ 6 | "cssnano", 7 | "css", 8 | "csso", 9 | "clean-css", 10 | "swc", 11 | "esbuild", 12 | "webpack", 13 | "webpack-plugin", 14 | "minimize", 15 | "minimizer", 16 | "minify", 17 | "minifier", 18 | "optimize", 19 | "optimizer" 20 | ], 21 | "homepage": "https://github.com/webpack/css-minimizer-webpack-plugin", 22 | "bugs": "https://github.com/webpack/css-minimizer-webpack-plugin/issues", 23 | "repository": "webpack/css-minimizer-webpack-plugin", 24 | "funding": { 25 | "type": "opencollective", 26 | "url": "https://opencollective.com/webpack" 27 | }, 28 | "license": "MIT", 29 | "author": "Loann Neveu", 30 | "main": "dist/index.js", 31 | "types": "types/index.d.ts", 32 | "files": [ 33 | "dist", 34 | "types" 35 | ], 36 | "scripts": { 37 | "start": "npm run build -- -w", 38 | "clean": "del-cli dist", 39 | "prebuild": "npm run clean types", 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", 45 | "lint:prettier": "prettier --cache --list-different .", 46 | "lint:code": "eslint --cache .", 47 | "lint:spelling": "cspell --cache --no-must-find-files --quiet \"**/*.*\"", 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 NODE_OPTIONS=\"--experimental-vm-modules\" 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 && npm run build", 59 | "release": "standard-version" 60 | }, 61 | "dependencies": { 62 | "@jridgewell/trace-mapping": "^0.3.25", 63 | "cssnano": "^7.0.4", 64 | "jest-worker": "^30.0.5", 65 | "postcss": "^8.4.40", 66 | "schema-utils": "^4.2.0", 67 | "serialize-javascript": "^6.0.2" 68 | }, 69 | "devDependencies": { 70 | "@babel/cli": "^7.24.8", 71 | "@babel/core": "^7.25.2", 72 | "@babel/preset-env": "^7.25.3", 73 | "@commitlint/cli": "^19.3.0", 74 | "@commitlint/config-conventional": "^19.2.2", 75 | "@eslint/js": "^9.32.0", 76 | "@eslint/markdown": "^7.0.0", 77 | "@parcel/css": "^1.8.3", 78 | "@stylistic/eslint-plugin": "^5.2.2", 79 | "@swc/css": "^0.0.28", 80 | "@types/clean-css": "^4.2.11", 81 | "@types/csso": "^5.0.4", 82 | "@types/node": "^20.14.9", 83 | "@types/serialize-javascript": "^5.0.4", 84 | "babel-jest": "^30.0.5", 85 | "clean-css": "^5.3.3", 86 | "copy-webpack-plugin": "^13.0.0", 87 | "cross-env": "^7.0.3", 88 | "cspell": "^8.13.1", 89 | "css-loader": "^6.10.0", 90 | "cssnano-preset-simple": "^4.0.0", 91 | "csso": "^5.0.3", 92 | "del": "^6.1.0", 93 | "del-cli": "^5.1.0", 94 | "esbuild": "^0.25.0", 95 | "eslint": "^9.30.1", 96 | "eslint-config-prettier": "^10.1.5", 97 | "eslint-config-webpack": "^4.4.1", 98 | "eslint-plugin-import": "^2.32.0", 99 | "eslint-plugin-jest": "^29.0.1", 100 | "eslint-plugin-jsdoc": "^52.0.0", 101 | "eslint-plugin-n": "^17.21.0", 102 | "eslint-plugin-prettier": "^5.5.3", 103 | "eslint-plugin-unicorn": "^60.0.0", 104 | "globals": "^16.3.0", 105 | "husky": "^9.1.4", 106 | "jest": "^30.0.5", 107 | "lightningcss": "^1.25.1", 108 | "lint-staged": "^15.2.8", 109 | "memfs": "^4.11.1", 110 | "mini-css-extract-plugin": "^2.9.0", 111 | "npm-run-all": "^4.1.5", 112 | "prettier": "^3.6.2", 113 | "sass": "^1.77.8", 114 | "sass-loader": "^16.0.5", 115 | "standard-version": "^9.5.0", 116 | "sugarss": "^5.0.0", 117 | "typescript": "^5.5.4", 118 | "webpack": "^5.93.0" 119 | }, 120 | "peerDependencies": { 121 | "webpack": "^5.0.0" 122 | }, 123 | "peerDependenciesMeta": { 124 | "clean-css": { 125 | "optional": true 126 | }, 127 | "csso": { 128 | "optional": true 129 | }, 130 | "esbuild": { 131 | "optional": true 132 | }, 133 | "@parcel/css": { 134 | "optional": true 135 | }, 136 | "lightningcss": { 137 | "optional": true 138 | }, 139 | "@swc/css": { 140 | "optional": true 141 | } 142 | }, 143 | "engines": { 144 | "node": ">= 18.12.0" 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /test/__snapshots__/parallel-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`parallel option should match snapshot for the "2" value: assets 1`] = ` 4 | { 5 | "four.css": "body{color:red}a{color:blue}", 6 | "one.css": "body{color:red}a{color:blue}", 7 | "three.css": "body{color:red}a{color:blue}", 8 | "two.css": "body{color:red}a{color:blue}", 9 | } 10 | `; 11 | 12 | exports[`parallel option should match snapshot for the "2" value: errors 1`] = `[]`; 13 | 14 | exports[`parallel option should match snapshot for the "2" value: warnings 1`] = `[]`; 15 | 16 | exports[`parallel option should match snapshot for the "false" value: assets 1`] = ` 17 | { 18 | "four.css": "body{color:red}a{color:blue}", 19 | "one.css": "body{color:red}a{color:blue}", 20 | "three.css": "body{color:red}a{color:blue}", 21 | "two.css": "body{color:red}a{color:blue}", 22 | } 23 | `; 24 | 25 | exports[`parallel option should match snapshot for the "false" value: errors 1`] = `[]`; 26 | 27 | exports[`parallel option should match snapshot for the "false" value: warnings 1`] = `[]`; 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 | { 31 | "entry-0.css": "body{color:red}a{color:blue}", 32 | "entry-1.css": "body{color:red}a{color:blue}", 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`] = `[]`; 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`] = `[]`; 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 | { 42 | "eight.css": "body{color:red}a{color:blue}", 43 | "five.css": "body{color:red}a{color:blue}", 44 | "four.css": "body{color:red}a{color:blue}", 45 | "one.css": "body{color:red}a{color:blue}", 46 | "seven.css": "body{color:red}a{color:blue}", 47 | "six.css": "body{color:red}a{color:blue}", 48 | "three.css": "body{color:red}a{color:blue}", 49 | "two.css": "body{color:red}a{color:blue}", 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`] = `[]`; 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`] = `[]`; 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 | { 59 | "entry-0.css": "body{color:red}a{color:blue}", 60 | "entry-1.css": "body{color:red}a{color:blue}", 61 | "entry-2.css": "body{color:red}a{color:blue}", 62 | "entry-3.css": "body{color:red}a{color:blue}", 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`] = `[]`; 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`] = `[]`; 69 | 70 | exports[`parallel option should match snapshot for the "true" value when only one file passed: assets 1`] = ` 71 | { 72 | "main.css": "body{color:red}a{color:blue}", 73 | } 74 | `; 75 | 76 | exports[`parallel option should match snapshot for the "true" value when only one file passed: errors 1`] = `[]`; 77 | 78 | exports[`parallel option should match snapshot for the "true" value when only one file passed: warnings 1`] = `[]`; 79 | 80 | exports[`parallel option should match snapshot for the "true" value: assets 1`] = ` 81 | { 82 | "four.css": "body{color:red}a{color:blue}", 83 | "one.css": "body{color:red}a{color:blue}", 84 | "three.css": "body{color:red}a{color:blue}", 85 | "two.css": "body{color:red}a{color:blue}", 86 | } 87 | `; 88 | 89 | exports[`parallel option should match snapshot for the "true" value: errors 1`] = `[]`; 90 | 91 | exports[`parallel option should match snapshot for the "true" value: warnings 1`] = `[]`; 92 | 93 | exports[`parallel option should match snapshot for the "undefined" value: assets 1`] = ` 94 | { 95 | "four.css": "body{color:red}a{color:blue}", 96 | "one.css": "body{color:red}a{color:blue}", 97 | "three.css": "body{color:red}a{color:blue}", 98 | "two.css": "body{color:red}a{color:blue}", 99 | } 100 | `; 101 | 102 | exports[`parallel option should match snapshot for the "undefined" value: errors 1`] = `[]`; 103 | 104 | exports[`parallel option should match snapshot for the "undefined" value: warnings 1`] = `[]`; 105 | 106 | exports[`parallel option should match snapshot when a value is not specify: assets 1`] = ` 107 | { 108 | "four.css": "body{color:red}a{color:blue}", 109 | "one.css": "body{color:red}a{color:blue}", 110 | "three.css": "body{color:red}a{color:blue}", 111 | "two.css": "body{color:red}a{color:blue}", 112 | } 113 | `; 114 | 115 | exports[`parallel option should match snapshot when a value is not specify: errors 1`] = `[]`; 116 | 117 | exports[`parallel option should match snapshot when a value is not specify: warnings 1`] = `[]`; 118 | -------------------------------------------------------------------------------- /test/__snapshots__/validate-options.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`validation validation 1`] = ` 4 | "Invalid options object. Css Minimizer 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/css-minimizer-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 validation 2`] = ` 21 | "Invalid options object. Css Minimizer 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/css-minimizer-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 validation 3`] = ` 36 | "Invalid options object. Css Minimizer 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/css-minimizer-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 validation 4`] = ` 53 | "Invalid options object. Css Minimizer 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/css-minimizer-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 validation 5`] = ` 68 | "Invalid options object. Css Minimizer 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/css-minimizer-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 validation 6`] = ` 85 | "Invalid options object. Css Minimizer 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/css-minimizer-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 validation 7`] = ` 100 | "Invalid options object. Css Minimizer Plugin has been initialized using an options object that does not match the API schema. 101 | - options.minimizerOptions should be one of these: 102 | object { … } | [object { … }, ...] (should not have fewer than 1 item) 103 | -> Options for \`cssMinimizerOptions\`. 104 | -> Read more at https://github.com/webpack/css-minimizer-webpack-plugin/#minimizeroptions 105 | Details: 106 | * options.minimizerOptions should be an object: 107 | object { … } 108 | * options.minimizerOptions should be an array: 109 | [object { … }, ...] (should not have fewer than 1 item)" 110 | `; 111 | 112 | exports[`validation validation 8`] = ` 113 | "Invalid options object. Css Minimizer Plugin has been initialized using an options object that does not match the API schema. 114 | - options.parallel should be one of these: 115 | boolean | integer 116 | -> Use multi-process parallel running to improve the build speed. 117 | -> Read more at https://github.com/webpack/css-minimizer-webpack-plugin/#parallel 118 | Details: 119 | * options.parallel should be a boolean. 120 | * options.parallel should be an integer." 121 | `; 122 | 123 | exports[`validation validation 9`] = ` 124 | "Invalid options object. Css Minimizer Plugin has been initialized using an options object that does not match the API schema. 125 | - options.parallel should be one of these: 126 | boolean | integer 127 | -> Use multi-process parallel running to improve the build speed. 128 | -> Read more at https://github.com/webpack/css-minimizer-webpack-plugin/#parallel 129 | Details: 130 | * options.parallel should be a boolean. 131 | * options.parallel should be an integer." 132 | `; 133 | 134 | exports[`validation validation 10`] = ` 135 | "Invalid options object. Css Minimizer Plugin has been initialized using an options object that does not match the API schema. 136 | - options.minify should be one of these: 137 | function | [function, ...] (should not have fewer than 1 item) 138 | -> Allows you to override default minify function. 139 | -> Read more at https://github.com/webpack/css-minimizer-webpack-plugin/#minify 140 | Details: 141 | * options.minify should be an instance of function. 142 | * options.minify should be an array: 143 | [function, ...] (should not have fewer than 1 item)" 144 | `; 145 | 146 | exports[`validation validation 11`] = ` 147 | "Invalid options object. Css Minimizer Plugin has been initialized using an options object that does not match the API schema. 148 | - options has an unknown property 'unknown'. These properties are valid: 149 | object { test?, include?, exclude?, minimizerOptions?, parallel?, warningsFilter?, minify? }" 150 | `; 151 | -------------------------------------------------------------------------------- /test/__snapshots__/sourceMap-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`when applied with "sourceMap" option should work and emit warning on valid sourcemap and minimizer error: errors 1`] = ` 4 | [ 5 | "Error: broken-source-map.css from Css Minimizer plugin 6 | error-plugin: /broken-source-map.css:1:7: Postcss error [test:1,5][broken-source-map.css:1,7]", 7 | "Error: dist/entry2.css from Css Minimizer plugin 8 | error-plugin: /dist/entry2.css:9:3: Postcss error [webpack://./sourcemap/foo.css:8,2][dist/entry2.css:9,3]", 9 | ] 10 | `; 11 | 12 | exports[`when applied with "sourceMap" option should work and emit warning on valid sourcemap and minimizer error: warnings 1`] = `[]`; 13 | 14 | exports[`when applied with "sourceMap" option should work and emit warnings on broken sourcemaps: assets 1`] = ` 15 | { 16 | "broken-source-map.css": ".bar{color:red};", 17 | "dist/entry2.css": "body{color:red;font-weight:700}body a{text-align:center} 18 | /*# sourceMappingURL=entry2.css.map*/", 19 | "dist/entry2.css.map": { 20 | "version": 3, 21 | "file": "dist/entry2.css", 22 | "mappings": "AAAA,KCGE,SAAU,CDFV,eACF,CCIA,OACE,iBAAF", 23 | "sources": [ 24 | "webpack:///./sourcemap/bar.css", 25 | "webpack:///./sourcemap/foo.css" 26 | ], 27 | "sourcesContent": [ 28 | "body {\\n font-weight: bold;\\n}", 29 | "@import 'bar.css';\\n\\nbody {\\n color: red;\\n}\\n\\nbody a {\\n text-align: center;\\n}\\n" 30 | ], 31 | "names": [], 32 | "sourceRoot": "" 33 | }, 34 | } 35 | `; 36 | 37 | exports[`when applied with "sourceMap" option should work and emit warnings on broken sourcemaps: errors 1`] = `[]`; 38 | 39 | exports[`when applied with "sourceMap" option should work and emit warnings on broken sourcemaps: warnings 1`] = ` 40 | [ 41 | "Error: broken-source-map.css contains invalid source map", 42 | ] 43 | `; 44 | 45 | exports[`when applied with "sourceMap" option should work with SourceMapDevToolPlugin plugin): assets 1`] = ` 46 | { 47 | "dist/entry.css": "body{color:red;font-weight:700}body a{text-align:center} 48 | /*# sourceMappingURL=https://example.com/project/sourcemaps/entry.css.map*/", 49 | "dist/entry2.css": "body{color:red;font-weight:700}body a{text-align:center} 50 | /*# sourceMappingURL=https://example.com/project/sourcemaps/entry2.css.map*/", 51 | "sourcemaps/entry.css.map": { 52 | "version": 3, 53 | "file": "dist/entry.css", 54 | "mappings": "AAAA,KCGE,SAAU,CDFV,eCCF,CAEE,OACE,iBAIJ", 55 | "sources": [ 56 | "webpack:///./sourcemap/bar.scss", 57 | "webpack:///./sourcemap/foo.scss" 58 | ], 59 | "sourcesContent": [ 60 | "body {\\n font-weight: bold;\\n}", 61 | "@use 'bar';\\n\\nbody {\\n color: red;\\n a {\\n text-align: center;\\n }\\n}\\n" 62 | ], 63 | "names": [], 64 | "sourceRoot": "" 65 | }, 66 | "sourcemaps/entry2.css.map": { 67 | "version": 3, 68 | "file": "dist/entry2.css", 69 | "mappings": "AAAA,KCGE,SAAU,CDFV,eACF,CCIA,OACE,iBAAF", 70 | "sources": [ 71 | "webpack:///./sourcemap/bar.css", 72 | "webpack:///./sourcemap/foo.css" 73 | ], 74 | "sourcesContent": [ 75 | "body {\\n font-weight: bold;\\n}", 76 | "@import 'bar.css';\\n\\nbody {\\n color: red;\\n}\\n\\nbody a {\\n text-align: center;\\n}\\n" 77 | ], 78 | "names": [], 79 | "sourceRoot": "" 80 | }, 81 | } 82 | `; 83 | 84 | exports[`when applied with "sourceMap" option should work with SourceMapDevToolPlugin plugin): errors 1`] = `[]`; 85 | 86 | exports[`when applied with "sourceMap" option should work with SourceMapDevToolPlugin plugin): warnings 1`] = `[]`; 87 | 88 | exports[`when applied with "sourceMap" option should work with the "devtool" option and the "parallel" option with "false" value: assets 1`] = ` 89 | { 90 | "entry.css": "body{color:red;font-weight:700}body a{text-align:center} 91 | /*# sourceMappingURL=entry.css.map*/", 92 | "entry.css.map": { 93 | "version": 3, 94 | "file": "entry.css", 95 | "mappings": "AAAA,KCGE,SAAU,CDFV,eCCF,CAEE,OACE,iBAIJ", 96 | "sources": [ 97 | "webpack:///./sourcemap/bar.scss", 98 | "webpack:///./sourcemap/foo.scss" 99 | ], 100 | "sourcesContent": [ 101 | "body {\\n font-weight: bold;\\n}", 102 | "@use 'bar';\\n\\nbody {\\n color: red;\\n a {\\n text-align: center;\\n }\\n}\\n" 103 | ], 104 | "names": [], 105 | "sourceRoot": "" 106 | }, 107 | "entry2.css": "body{color:red;font-weight:700}body a{text-align:center} 108 | /*# sourceMappingURL=entry2.css.map*/", 109 | "entry2.css.map": { 110 | "version": 3, 111 | "file": "entry2.css", 112 | "mappings": "AAAA,KCGE,SAAU,CDFV,eACF,CCIA,OACE,iBAAF", 113 | "sources": [ 114 | "webpack:///./sourcemap/bar.css", 115 | "webpack:///./sourcemap/foo.css" 116 | ], 117 | "sourcesContent": [ 118 | "body {\\n font-weight: bold;\\n}", 119 | "@import 'bar.css';\\n\\nbody {\\n color: red;\\n}\\n\\nbody a {\\n text-align: center;\\n}\\n" 120 | ], 121 | "names": [], 122 | "sourceRoot": "" 123 | }, 124 | } 125 | `; 126 | 127 | exports[`when applied with "sourceMap" option should work with the "devtool" option and the "parallel" option with "false" value: errors 1`] = `[]`; 128 | 129 | exports[`when applied with "sourceMap" option should work with the "devtool" option and the "parallel" option with "false" value: warnings 1`] = `[]`; 130 | 131 | exports[`when applied with "sourceMap" option should work with the "devtool" option and the "parallel" option with "true" value: assets 1`] = ` 132 | { 133 | "entry.css": "body{color:red;font-weight:700}body a{text-align:center} 134 | /*# sourceMappingURL=entry.css.map*/", 135 | "entry.css.map": { 136 | "version": 3, 137 | "file": "entry.css", 138 | "mappings": "AAAA,KCGE,SAAU,CDFV,eCCF,CAEE,OACE,iBAIJ", 139 | "sources": [ 140 | "webpack:///./sourcemap/bar.scss", 141 | "webpack:///./sourcemap/foo.scss" 142 | ], 143 | "sourcesContent": [ 144 | "body {\\n font-weight: bold;\\n}", 145 | "@use 'bar';\\n\\nbody {\\n color: red;\\n a {\\n text-align: center;\\n }\\n}\\n" 146 | ], 147 | "names": [], 148 | "sourceRoot": "" 149 | }, 150 | "entry2.css": "body{color:red;font-weight:700}body a{text-align:center} 151 | /*# sourceMappingURL=entry2.css.map*/", 152 | "entry2.css.map": { 153 | "version": 3, 154 | "file": "entry2.css", 155 | "mappings": "AAAA,KCGE,SAAU,CDFV,eACF,CCIA,OACE,iBAAF", 156 | "sources": [ 157 | "webpack:///./sourcemap/bar.css", 158 | "webpack:///./sourcemap/foo.css" 159 | ], 160 | "sourcesContent": [ 161 | "body {\\n font-weight: bold;\\n}", 162 | "@import 'bar.css';\\n\\nbody {\\n color: red;\\n}\\n\\nbody a {\\n text-align: center;\\n}\\n" 163 | ], 164 | "names": [], 165 | "sourceRoot": "" 166 | }, 167 | } 168 | `; 169 | 170 | exports[`when applied with "sourceMap" option should work with the "devtool" option and the "parallel" option with "true" value: errors 1`] = `[]`; 171 | 172 | exports[`when applied with "sourceMap" option should work with the "devtool" option and the "parallel" option with "true" value: warnings 1`] = `[]`; 173 | 174 | exports[`when applied with "sourceMap" option should work with the "devtool" option: assets 1`] = ` 175 | { 176 | "entry.css": "body{color:red;font-weight:700}body a{text-align:center} 177 | /*# sourceMappingURL=entry.css.map*/", 178 | "entry.css.map": { 179 | "version": 3, 180 | "file": "entry.css", 181 | "mappings": "AAAA,KCGE,SAAU,CDFV,eCCF,CAEE,OACE,iBAIJ", 182 | "sources": [ 183 | "webpack:///./sourcemap/bar.scss", 184 | "webpack:///./sourcemap/foo.scss" 185 | ], 186 | "sourcesContent": [ 187 | "body {\\n font-weight: bold;\\n}", 188 | "@use 'bar';\\n\\nbody {\\n color: red;\\n a {\\n text-align: center;\\n }\\n}\\n" 189 | ], 190 | "names": [], 191 | "sourceRoot": "" 192 | }, 193 | "entry2.css": "body{color:red;font-weight:700}body a{text-align:center} 194 | /*# sourceMappingURL=entry2.css.map*/", 195 | "entry2.css.map": { 196 | "version": 3, 197 | "file": "entry2.css", 198 | "mappings": "AAAA,KCGE,SAAU,CDFV,eACF,CCIA,OACE,iBAAF", 199 | "sources": [ 200 | "webpack:///./sourcemap/bar.css", 201 | "webpack:///./sourcemap/foo.css" 202 | ], 203 | "sourcesContent": [ 204 | "body {\\n font-weight: bold;\\n}", 205 | "@import 'bar.css';\\n\\nbody {\\n color: red;\\n}\\n\\nbody a {\\n text-align: center;\\n}\\n" 206 | ], 207 | "names": [], 208 | "sourceRoot": "" 209 | }, 210 | } 211 | `; 212 | 213 | exports[`when applied with "sourceMap" option should work with the "devtool" option: errors 1`] = `[]`; 214 | 215 | exports[`when applied with "sourceMap" option should work with the "devtool" option: warnings 1`] = `[]`; 216 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | export = CssMinimizerPlugin; 2 | /** 3 | * @template [T=CssNanoOptionsExtended] 4 | */ 5 | declare class CssMinimizerPlugin { 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 {Warning | WarningObject | string} warning Warning 15 | * @param {string} file File name 16 | * @param {WarningsFilter=} warningsFilter Warnings filter 17 | * @param {TraceMap=} sourceMap Source map 18 | * @param {Compilation["requestShortener"]=} requestShortener Request shortener 19 | * @returns {Error & { hideStack?: boolean, file?: string } | undefined} Built warning 20 | */ 21 | private static buildWarning; 22 | /** 23 | * @private 24 | * @param {Error | ErrorObject | string} error Error 25 | * @param {string} file File name 26 | * @param {TraceMap=} sourceMap Source map 27 | * @param {Compilation["requestShortener"]=} requestShortener Request shortener 28 | * @returns {Error} Built error 29 | */ 30 | private static buildError; 31 | /** 32 | * @private 33 | * @param {Parallel} parallel Parallel option 34 | * @returns {number} Available number of cores 35 | */ 36 | private static getAvailableNumberOfCores; 37 | /** 38 | * @private 39 | * @template T 40 | * @param {BasicMinimizerImplementation & MinimizeFunctionHelpers} implementation Implementation 41 | * @returns {boolean} Whether worker threads are supported 42 | */ 43 | private static isSupportsWorkerThreads; 44 | /** 45 | * @param {BasePluginOptions & DefinedDefaultMinimizerAndOptions=} options Plugin options 46 | */ 47 | constructor( 48 | options?: 49 | | (BasePluginOptions & DefinedDefaultMinimizerAndOptions) 50 | | undefined, 51 | ); 52 | /** 53 | * @private 54 | * @type {InternalPluginOptions} 55 | */ 56 | private options; 57 | /** 58 | * @private 59 | * @param {Compiler} compiler Compiler 60 | * @param {Compilation} compilation Compilation 61 | * @param {Record} assets Assets 62 | * @param {{availableNumberOfCores: number}} optimizeOptions Optimize options 63 | * @returns {Promise} Promise 64 | */ 65 | private optimize; 66 | /** 67 | * @param {Compiler} compiler Compiler 68 | * @returns {void} Void 69 | */ 70 | apply(compiler: Compiler): void; 71 | } 72 | declare namespace CssMinimizerPlugin { 73 | export { 74 | cssnanoMinify, 75 | cssoMinify, 76 | cleanCssMinify, 77 | esbuildMinify, 78 | parcelCssMinify, 79 | lightningCssMinify, 80 | swcMinify, 81 | Schema, 82 | Compiler, 83 | Compilation, 84 | WebpackError, 85 | JestWorker, 86 | RawSourceMap, 87 | Asset, 88 | ProcessOptions, 89 | Syntax, 90 | Parser, 91 | Stringifier, 92 | TraceMap, 93 | CssNanoOptions, 94 | Warning, 95 | WarningObject, 96 | ErrorObject, 97 | MinimizedResult, 98 | Input, 99 | CustomOptions, 100 | InferDefaultType, 101 | MinimizerOptions, 102 | BasicMinimizerImplementation, 103 | MinimizeFunctionHelpers, 104 | MinimizerImplementation, 105 | InternalOptions, 106 | InternalResult, 107 | Parallel, 108 | Rule, 109 | Rules, 110 | WarningsFilter, 111 | BasePluginOptions, 112 | MinimizerWorker, 113 | ProcessOptionsExtender, 114 | CssNanoOptionsExtended, 115 | DefinedDefaultMinimizerAndOptions, 116 | InternalPluginOptions, 117 | }; 118 | } 119 | import { cssnanoMinify } from "./utils"; 120 | import { cssoMinify } from "./utils"; 121 | import { cleanCssMinify } from "./utils"; 122 | import { esbuildMinify } from "./utils"; 123 | import { parcelCssMinify } from "./utils"; 124 | import { lightningCssMinify } from "./utils"; 125 | import { swcMinify } from "./utils"; 126 | type Schema = import("schema-utils/declarations/validate").Schema; 127 | type Compiler = import("webpack").Compiler; 128 | type Compilation = import("webpack").Compilation; 129 | type WebpackError = import("webpack").WebpackError; 130 | type JestWorker = import("jest-worker").Worker; 131 | type RawSourceMap = import("@jridgewell/trace-mapping").EncodedSourceMap & { 132 | sources: string[]; 133 | sourcesContent?: string[]; 134 | file: string; 135 | }; 136 | type Asset = import("webpack").Asset; 137 | type ProcessOptions = import("postcss").ProcessOptions; 138 | type Syntax = import("postcss").Syntax; 139 | type Parser = import("postcss").Parser; 140 | type Stringifier = import("postcss").Stringifier; 141 | type TraceMap = import("@jridgewell/trace-mapping").TraceMap; 142 | type CssNanoOptions = Record; 143 | type Warning = 144 | | (Error & { 145 | plugin?: string; 146 | text?: string; 147 | source?: string; 148 | }) 149 | | string; 150 | type WarningObject = { 151 | /** 152 | * Warning message 153 | */ 154 | message: string; 155 | /** 156 | * Plugin name 157 | */ 158 | plugin?: string | undefined; 159 | /** 160 | * Warning text 161 | */ 162 | text?: string | undefined; 163 | /** 164 | * Line number 165 | */ 166 | line?: number | undefined; 167 | /** 168 | * Column number 169 | */ 170 | column?: number | undefined; 171 | }; 172 | type ErrorObject = { 173 | /** 174 | * Error message 175 | */ 176 | message: string; 177 | /** 178 | * Line number 179 | */ 180 | line?: number | undefined; 181 | /** 182 | * Column number 183 | */ 184 | column?: number | undefined; 185 | /** 186 | * Error stack trace 187 | */ 188 | stack?: string | undefined; 189 | }; 190 | type MinimizedResult = { 191 | /** 192 | * Minimized code 193 | */ 194 | code: string; 195 | /** 196 | * Source map 197 | */ 198 | map?: RawSourceMap | undefined; 199 | /** 200 | * Errors 201 | */ 202 | errors?: Array | undefined; 203 | /** 204 | * Warnings 205 | */ 206 | warnings?: Array | undefined; 207 | }; 208 | type Input = { 209 | [file: string]: string; 210 | }; 211 | type CustomOptions = { 212 | [key: string]: unknown; 213 | }; 214 | type InferDefaultType = T extends infer U ? U : CustomOptions; 215 | type MinimizerOptions = T extends any[] 216 | ? { [P in keyof T]?: InferDefaultType } 217 | : InferDefaultType; 218 | type BasicMinimizerImplementation = ( 219 | input: Input, 220 | sourceMap: RawSourceMap | undefined, 221 | minifyOptions: InferDefaultType, 222 | ) => Promise | MinimizedResult; 223 | type MinimizeFunctionHelpers = { 224 | /** 225 | * Check if worker threads are supported 226 | */ 227 | supportsWorkerThreads?: (() => boolean | undefined) | undefined; 228 | }; 229 | type MinimizerImplementation = T extends any[] 230 | ? { 231 | [P in keyof T]: BasicMinimizerImplementation & 232 | MinimizeFunctionHelpers; 233 | } 234 | : BasicMinimizerImplementation & MinimizeFunctionHelpers; 235 | type InternalOptions = { 236 | /** 237 | * Name 238 | */ 239 | name: string; 240 | /** 241 | * Input 242 | */ 243 | input: string; 244 | /** 245 | * Input source map 246 | */ 247 | inputSourceMap: RawSourceMap | undefined; 248 | /** 249 | * Minimizer 250 | */ 251 | minimizer: { 252 | implementation: MinimizerImplementation; 253 | options: MinimizerOptions; 254 | }; 255 | }; 256 | type InternalResult = { 257 | /** 258 | * - Outputs 259 | */ 260 | outputs: Array<{ 261 | code: string; 262 | map: RawSourceMap | undefined; 263 | }>; 264 | /** 265 | * - Warnings 266 | */ 267 | warnings: Array; 268 | /** 269 | * - Errors 270 | */ 271 | errors: Array; 272 | }; 273 | type Parallel = undefined | boolean | number; 274 | type Rule = RegExp | string; 275 | type Rules = Rule[] | Rule; 276 | type WarningsFilter = ( 277 | warning: Warning | WarningObject | string, 278 | file: string, 279 | source?: string, 280 | ) => boolean; 281 | type BasePluginOptions = { 282 | /** 283 | * Test rule 284 | */ 285 | test?: Rule | undefined; 286 | /** 287 | * Include rule 288 | */ 289 | include?: Rule | undefined; 290 | /** 291 | * Exclude rule 292 | */ 293 | exclude?: Rule | undefined; 294 | /** 295 | * Warnings filter 296 | */ 297 | warningsFilter?: WarningsFilter | undefined; 298 | /** 299 | * Parallel option 300 | */ 301 | parallel?: Parallel | undefined; 302 | }; 303 | type MinimizerWorker = JestWorker & { 304 | transform: (options: string) => Promise; 305 | minify: (options: InternalOptions) => Promise; 306 | }; 307 | type ProcessOptionsExtender = 308 | | ProcessOptions 309 | | { 310 | from?: string; 311 | to?: string; 312 | parser?: string | Syntax | Parser; 313 | stringifier?: string | Syntax | Stringifier; 314 | syntax?: string | Syntax; 315 | }; 316 | type CssNanoOptionsExtended = CssNanoOptions & { 317 | processorOptions?: ProcessOptionsExtender; 318 | }; 319 | type DefinedDefaultMinimizerAndOptions = T extends CssNanoOptionsExtended 320 | ? { 321 | minify?: MinimizerImplementation | undefined; 322 | minimizerOptions?: MinimizerOptions | undefined; 323 | } 324 | : { 325 | minify: MinimizerImplementation; 326 | minimizerOptions?: MinimizerOptions | undefined; 327 | }; 328 | type InternalPluginOptions = BasePluginOptions & { 329 | minimizer: { 330 | implementation: MinimizerImplementation; 331 | options: MinimizerOptions; 332 | }; 333 | }; 334 | -------------------------------------------------------------------------------- /test/sourceMap-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import MiniCssExtractPlugin from "mini-css-extract-plugin"; 4 | import webpack from "webpack"; 5 | 6 | import CssMinimizerPlugin from "../src/index"; 7 | 8 | import { 9 | compile, 10 | getCompiler, 11 | getErrors, 12 | getWarnings, 13 | readAssets, 14 | } from "./helpers"; 15 | 16 | expect.addSnapshotSerializer({ 17 | test: (value) => { 18 | // For string that are valid JSON 19 | if (typeof value !== "string") { 20 | return false; 21 | } 22 | 23 | try { 24 | return typeof JSON.parse(value) === "object"; 25 | } catch { 26 | return false; 27 | } 28 | }, 29 | print: (value) => JSON.stringify(JSON.parse(value), null, 4), 30 | }); 31 | 32 | describe('when applied with "sourceMap" option', () => { 33 | const baseConfig = { 34 | devtool: "source-map", 35 | entry: { 36 | entry: path.join(__dirname, "fixtures", "sourcemap", "foo.scss"), 37 | entry2: path.join(__dirname, "fixtures", "sourcemap", "foo.css"), 38 | }, 39 | module: { 40 | rules: [ 41 | { 42 | test: /.s?css$/i, 43 | use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"], 44 | }, 45 | ], 46 | }, 47 | plugins: [ 48 | new MiniCssExtractPlugin({ 49 | filename: "[name].css", 50 | chunkFilename: "[id].[name].css", 51 | }), 52 | ], 53 | }; 54 | 55 | it('should work with the "devtool" option', async () => { 56 | const compiler = getCompiler(baseConfig); 57 | 58 | new CssMinimizerPlugin().apply(compiler); 59 | 60 | const stats = await compile(compiler); 61 | 62 | expect(readAssets(compiler, stats, /\.css(\.map)?$/)).toMatchSnapshot( 63 | "assets", 64 | ); 65 | expect(getErrors(stats)).toMatchSnapshot("errors"); 66 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 67 | }); 68 | 69 | it('should work with the "devtool" option and the "parallel" option with "false" value', async () => { 70 | const compiler = getCompiler(baseConfig); 71 | 72 | new CssMinimizerPlugin({ 73 | parallel: false, 74 | }).apply(compiler); 75 | 76 | const stats = await compile(compiler); 77 | 78 | expect(readAssets(compiler, stats, /\.css(\.map)?$/)).toMatchSnapshot( 79 | "assets", 80 | ); 81 | expect(getErrors(stats)).toMatchSnapshot("errors"); 82 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 83 | }); 84 | 85 | it('should work with the "devtool" option and the "parallel" option with "true" value', async () => { 86 | const compiler = getCompiler(baseConfig); 87 | 88 | new CssMinimizerPlugin({ 89 | parallel: true, 90 | }).apply(compiler); 91 | 92 | const stats = await compile(compiler); 93 | 94 | expect(readAssets(compiler, stats, /\.css(\.map)?$/)).toMatchSnapshot( 95 | "assets", 96 | ); 97 | expect(getErrors(stats)).toMatchSnapshot("errors"); 98 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 99 | }); 100 | 101 | it("should work with SourceMapDevToolPlugin plugin)", async () => { 102 | const config = Object.assign(baseConfig, { 103 | devtool: false, 104 | module: { 105 | rules: [ 106 | { 107 | test: /.s?css$/i, 108 | use: [ 109 | MiniCssExtractPlugin.loader, 110 | { loader: "css-loader", options: { sourceMap: true } }, 111 | { loader: "sass-loader", options: { sourceMap: true } }, 112 | ], 113 | }, 114 | ], 115 | }, 116 | plugins: [ 117 | new MiniCssExtractPlugin({ 118 | filename: "dist/[name].css", 119 | chunkFilename: "dist/[id].[name].css", 120 | }), 121 | new webpack.SourceMapDevToolPlugin({ 122 | filename: "sourcemaps/[file].map", 123 | publicPath: "https://example.com/project/", 124 | fileContext: "dist", 125 | }), 126 | ], 127 | }); 128 | 129 | const compiler = getCompiler(config); 130 | 131 | new CssMinimizerPlugin().apply(compiler); 132 | 133 | const stats = await compile(compiler); 134 | 135 | expect(readAssets(compiler, stats, /\.css(\.map)?$/)).toMatchSnapshot( 136 | "assets", 137 | ); 138 | expect(getErrors(stats)).toMatchSnapshot("errors"); 139 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 140 | }); 141 | 142 | it("should work and emit warnings on broken sourcemaps", async () => { 143 | const emitBrokenSourceMapPlugin = new (class EmitBrokenSourceMapPlugin { 144 | apply(pluginCompiler) { 145 | pluginCompiler.hooks.compilation.tap( 146 | { name: this.constructor.name }, 147 | (compilation) => { 148 | compilation.hooks.processAssets.tap( 149 | { 150 | name: this.constructor.name, 151 | stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, 152 | }, 153 | () => { 154 | compilation.additionalChunkAssets.push("broken-source-map.css"); 155 | 156 | const assetContent = ".bar {color: red};"; 157 | 158 | compilation.assets["broken-source-map.css"] = { 159 | size() { 160 | return assetContent.length; 161 | }, 162 | source() { 163 | return assetContent; 164 | }, 165 | sourceAndMap() { 166 | return { 167 | source: this.source(), 168 | map: { 169 | sources: [], 170 | names: [], 171 | mappings: "AAAA,KAAK,iBAAiB,KAAK,UAAU,OAAO", 172 | file: "x", 173 | sourcesContent: [], 174 | }, 175 | }; 176 | }, 177 | }; 178 | }, 179 | ); 180 | }, 181 | ); 182 | } 183 | })(); 184 | 185 | const config = Object.assign(baseConfig, { 186 | devtool: "source-map", 187 | entry: { 188 | entry2: path.join(__dirname, "fixtures", "sourcemap", "foo.css"), 189 | }, 190 | plugins: [ 191 | emitBrokenSourceMapPlugin, 192 | new MiniCssExtractPlugin({ 193 | filename: "dist/[name].css", 194 | chunkFilename: "dist/[id].[name].css", 195 | }), 196 | ], 197 | }); 198 | 199 | const compiler = getCompiler(config); 200 | 201 | new CssMinimizerPlugin().apply(compiler); 202 | 203 | const stats = await compile(compiler); 204 | 205 | expect(readAssets(compiler, stats, /\.css(\.map)?$/)).toMatchSnapshot( 206 | "assets", 207 | ); 208 | expect(getErrors(stats)).toMatchSnapshot("errors"); 209 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 210 | expect(stats.compilation.warnings[0].toString()).toMatch( 211 | "Error: broken-source-map.css contains invalid source map", 212 | ); 213 | }); 214 | 215 | it("should work and emit warning on valid sourcemap and minimizer error", async () => { 216 | const emitBrokenSourceMapPlugin = new (class EmitBrokenSourceMapPlugin { 217 | apply(pluginCompiler) { 218 | pluginCompiler.hooks.compilation.tap( 219 | { name: this.constructor.name }, 220 | (compilation) => { 221 | compilation.hooks.processAssets.tap( 222 | { 223 | name: this.constructor.name, 224 | stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, 225 | }, 226 | () => { 227 | compilation.additionalChunkAssets.push("broken-source-map.css"); 228 | 229 | const assetContent = ".bar {color: red};"; 230 | 231 | compilation.assets["broken-source-map.css"] = { 232 | size() { 233 | return assetContent.length; 234 | }, 235 | source() { 236 | return assetContent; 237 | }, 238 | sourceAndMap() { 239 | return { 240 | source: this.source(), 241 | map: { 242 | version: 3, 243 | sources: ["test", "test2"], 244 | names: [], 245 | mappings: "AAAA,KAAK,iBAAiB,KAAK,UAAU,OAAO", 246 | file: "x", 247 | sourcesContent: [], 248 | }, 249 | }; 250 | }, 251 | }; 252 | }, 253 | ); 254 | }, 255 | ); 256 | } 257 | })(); 258 | 259 | const config = Object.assign(baseConfig, { 260 | devtool: "source-map", 261 | entry: { 262 | entry2: path.join(__dirname, "fixtures", "sourcemap", "foo.css"), 263 | }, 264 | plugins: [ 265 | emitBrokenSourceMapPlugin, 266 | new MiniCssExtractPlugin({ 267 | filename: "dist/[name].css", 268 | chunkFilename: "dist/[id].[name].css", 269 | }), 270 | ], 271 | }); 272 | 273 | const compiler = getCompiler(config); 274 | 275 | new CssMinimizerPlugin({ 276 | minify: (data) => { 277 | const postcss = require("postcss"); 278 | 279 | const plugin = () => { 280 | let erroredDecl; 281 | 282 | return { 283 | postcssPlugin: "error-plugin", 284 | Declaration(decl) { 285 | erroredDecl = decl; 286 | }, 287 | OnceExit() { 288 | throw erroredDecl.error("Postcss error"); 289 | }, 290 | }; 291 | }; 292 | 293 | plugin.postcss = true; 294 | 295 | const [[filename, input]] = Object.entries(data); 296 | 297 | return postcss([plugin]) 298 | .process(input, { from: filename, to: filename }) 299 | .then((result) => result); 300 | }, 301 | }).apply(compiler); 302 | 303 | const stats = await compile(compiler); 304 | 305 | expect(getErrors(stats)).toMatchSnapshot("errors"); 306 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 307 | }); 308 | 309 | it("should work and do not contain sourcemap link in minified source", async () => { 310 | const compiler = getCompiler({ 311 | devtool: false, 312 | entry: { 313 | foo: path.join(__dirname, "fixtures", "foo.css"), 314 | }, 315 | }); 316 | 317 | new CssMinimizerPlugin().apply(compiler); 318 | 319 | const stats = await compile(compiler); 320 | const assets = readAssets(compiler, stats, /\.css$/); 321 | const [[, input]] = Object.entries(assets); 322 | 323 | expect(/sourceMappingURL/i.test(input)).toBe(false); 324 | }); 325 | }); 326 | -------------------------------------------------------------------------------- /test/parallel-option.test.js: -------------------------------------------------------------------------------- 1 | import os from "node:os"; 2 | import path from "node:path"; 3 | 4 | import { Worker } from "jest-worker"; 5 | 6 | import CssMinimizerPlugin from "../src/index"; 7 | 8 | import { 9 | compile, 10 | getCompiler, 11 | getErrors, 12 | getWarnings, 13 | readAssets, 14 | } from "./helpers"; 15 | 16 | jest.mock("node: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 === "function") { 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.join(__dirname, "fixtures", "entry.js"), 68 | two: path.join(__dirname, "fixtures", "entry.js"), 69 | three: path.join(__dirname, "fixtures", "entry.js"), 70 | four: path.join(__dirname, "fixtures", "entry.js"), 71 | }, 72 | }); 73 | }); 74 | 75 | it("should match snapshot when a value is not specify", async () => { 76 | new CssMinimizerPlugin().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(readAssets(compiler, stats, /\.css$/)).length, 87 | ); 88 | expect(workerEnd).toHaveBeenCalledTimes(1); 89 | 90 | expect(readAssets(compiler, stats, /\.css$/)).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 CssMinimizerPlugin({ parallel: false }).apply(compiler); 97 | 98 | const stats = await compile(compiler); 99 | 100 | expect(Worker).toHaveBeenCalledTimes(0); 101 | 102 | expect(readAssets(compiler, stats, /\.css$/)).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 CssMinimizerPlugin({ 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: Math.min(4, getParallelism() - 1), 116 | }); 117 | expect(workerTransform).toHaveBeenCalledTimes( 118 | Object.keys(readAssets(compiler, stats, /\.css$/)).length, 119 | ); 120 | expect(workerEnd).toHaveBeenCalledTimes(1); 121 | 122 | expect(readAssets(compiler, stats, /\.css$/)).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 CssMinimizerPlugin({ 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: Math.min(4, getParallelism() - 1), 136 | }); 137 | expect(workerTransform).toHaveBeenCalledTimes( 138 | Object.keys(readAssets(compiler, stats, /\.css$/)).length, 139 | ); 140 | expect(workerEnd).toHaveBeenCalledTimes(1); 141 | 142 | expect(readAssets(compiler, stats, /\.css$/)).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 CssMinimizerPlugin({ 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(readAssets(compiler, stats, /\.css$/)).length, 159 | ); 160 | expect(workerEnd).toHaveBeenCalledTimes(1); 161 | 162 | expect(readAssets(compiler, stats, /\.css$/)).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.join(__dirname, "fixtures", "entry.js"), 170 | }); 171 | 172 | new CssMinimizerPlugin({ 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, getParallelism() - 1), 180 | }); 181 | expect(workerTransform).toHaveBeenCalledTimes( 182 | Object.keys(readAssets(compiler, stats, /\.css$/)).length, 183 | ); 184 | expect(workerEnd).toHaveBeenCalledTimes(1); 185 | 186 | expect(readAssets(compiler, stats, /\.css$/)).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.join(__dirname, "fixtures", "entry.js"); 196 | } 197 | 198 | compiler = getCompiler({ entry: entries }); 199 | 200 | new CssMinimizerPlugin({ 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(readAssets(compiler, stats, /\.css$/)).length, 211 | ); 212 | expect(workerEnd).toHaveBeenCalledTimes(1); 213 | 214 | expect(readAssets(compiler, stats, /\.css$/)).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.join(__dirname, "fixtures", "entry.js"); 224 | } 225 | 226 | compiler = getCompiler({ entry: entries }); 227 | 228 | new CssMinimizerPlugin({ 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(readAssets(compiler, stats, /\.css$/)).length, 239 | ); 240 | expect(workerEnd).toHaveBeenCalledTimes(1); 241 | 242 | expect(readAssets(compiler, stats, /\.css$/)).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.join(__dirname, "fixtures", "entry.js"); 252 | } 253 | 254 | compiler = getCompiler({ 255 | entry: { 256 | one: path.join(__dirname, "fixtures", "entry.js"), 257 | two: path.join(__dirname, "fixtures", "entry.js"), 258 | three: path.join(__dirname, "fixtures", "entry.js"), 259 | four: path.join(__dirname, "fixtures", "entry.js"), 260 | five: path.join(__dirname, "fixtures", "entry.js"), 261 | six: path.join(__dirname, "fixtures", "entry.js"), 262 | seven: path.join(__dirname, "fixtures", "entry.js"), 263 | eight: path.join(__dirname, "fixtures", "entry.js"), 264 | }, 265 | }); 266 | 267 | new CssMinimizerPlugin({ 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(readAssets(compiler, stats, /\.css$/)).length, 278 | ); 279 | expect(workerEnd).toHaveBeenCalledTimes(1); 280 | 281 | expect(readAssets(compiler, stats, /\.css$/)).toMatchSnapshot("assets"); 282 | expect(getErrors(stats)).toMatchSnapshot("errors"); 283 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 284 | }); 285 | }); 286 | -------------------------------------------------------------------------------- /test/__snapshots__/cache-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`"cache" option should work with the "false" value for the "cache" option: assets 1`] = ` 4 | { 5 | "five.css": "body{color:red;font-size:20px}a{color:blue}", 6 | "four.css": "h1{color:green}h2{color:#ff0}", 7 | "one.css": "a{text-align:center}", 8 | "three.css": "body{color:red;font-size:20px}a{color:blue}", 9 | "two.css": "a{text-align:center}", 10 | } 11 | `; 12 | 13 | exports[`"cache" option should work with the "false" value for the "cache" option: assets 2`] = ` 14 | { 15 | "five.css": "body{color:red;font-size:20px}a{color:blue}", 16 | "four.css": "h1{color:green}h2{color:#ff0}", 17 | "one.css": "a{text-align:center}", 18 | "three.css": "body{color:red;font-size:20px}a{color:blue}", 19 | "two.css": "a{text-align:center}", 20 | } 21 | `; 22 | 23 | exports[`"cache" option should work with the "false" value for the "cache" option: errors 1`] = `[]`; 24 | 25 | exports[`"cache" option should work with the "false" value for the "cache" option: errors 2`] = `[]`; 26 | 27 | exports[`"cache" option should work with the "false" value for the "cache" option: warnings 1`] = `[]`; 28 | 29 | exports[`"cache" option should work with the "false" value for the "cache" option: warnings 2`] = `[]`; 30 | 31 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option and output warnings: assets 1`] = ` 32 | { 33 | "five.css": "body { 34 | color: red; 35 | } 36 | body { 37 | color: red; 38 | font-size: 20px; 39 | } 40 | a { 41 | color: blue; 42 | } 43 | 44 | ", 45 | "four.css": "h1 { 46 | color: green; 47 | } 48 | h2 { 49 | color: yellow; 50 | } 51 | h3 { 52 | } 53 | 54 | ", 55 | "one.css": "a { 56 | text-align: center; 57 | } 58 | 59 | ", 60 | "three.css": "body { 61 | color: red; 62 | } 63 | body { 64 | color: red; 65 | font-size: 20px; 66 | } 67 | a { 68 | color: blue; 69 | } 70 | 71 | ", 72 | "two.css": "a { 73 | text-align: center; 74 | } 75 | 76 | ", 77 | } 78 | `; 79 | 80 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option and output warnings: assets 2`] = ` 81 | { 82 | "five.css": "body { 83 | color: red; 84 | } 85 | body { 86 | color: red; 87 | font-size: 20px; 88 | } 89 | a { 90 | color: blue; 91 | } 92 | 93 | ", 94 | "four.css": "h1 { 95 | color: green; 96 | } 97 | h2 { 98 | color: yellow; 99 | } 100 | h3 { 101 | } 102 | 103 | ", 104 | "one.css": "a { 105 | text-align: center; 106 | } 107 | 108 | ", 109 | "three.css": "body { 110 | color: red; 111 | } 112 | body { 113 | color: red; 114 | font-size: 20px; 115 | } 116 | a { 117 | color: blue; 118 | } 119 | 120 | ", 121 | "two.css": "a { 122 | text-align: center; 123 | } 124 | 125 | ", 126 | } 127 | `; 128 | 129 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option and output warnings: errors 1`] = `[]`; 130 | 131 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option and output warnings: errors 2`] = `[]`; 132 | 133 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option and output warnings: warnings 1`] = ` 134 | [ 135 | "Warning: five.css from Css Minimizer plugin 136 | [warning-plugin] Warning from five.css", 137 | "Warning: four.css from Css Minimizer plugin 138 | [warning-plugin] Warning from four.css", 139 | "Warning: one.css from Css Minimizer plugin 140 | [warning-plugin] Warning from one.css", 141 | "Warning: three.css from Css Minimizer plugin 142 | [warning-plugin] Warning from three.css", 143 | "Warning: two.css from Css Minimizer plugin 144 | [warning-plugin] Warning from two.css", 145 | ] 146 | `; 147 | 148 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option and output warnings: warnings 2`] = ` 149 | [ 150 | "Warning: five.css from Css Minimizer plugin 151 | [warning-plugin] Warning from five.css", 152 | "Warning: four.css from Css Minimizer plugin 153 | [warning-plugin] Warning from four.css", 154 | "Warning: one.css from Css Minimizer plugin 155 | [warning-plugin] Warning from one.css", 156 | "Warning: three.css from Css Minimizer plugin 157 | [warning-plugin] Warning from three.css", 158 | "Warning: two.css from Css Minimizer plugin 159 | [warning-plugin] Warning from two.css", 160 | ] 161 | `; 162 | 163 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option and source maps: assets 1`] = ` 164 | { 165 | "five.css": "body{color:red;font-size:20px}a{color:blue} 166 | /*# sourceMappingURL=five.css.map*/", 167 | "five.css.map": "{"version":3,"file":"five.css","mappings":"AAGA,KACE,SAAU,CACV,cACF,CACA,EACE,UACF","sources":["webpack:///./test/bar1.css"],"sourcesContent":["body {\\n color: red;\\n}\\nbody {\\n color: red;\\n font-size: 20px;\\n}\\na {\\n color: blue;\\n}\\n"],"names":[],"sourceRoot":""}", 168 | "four.css": "h1{color:green}h2{color:#ff0} 169 | /*# sourceMappingURL=four.css.map*/", 170 | "four.css.map": "{"version":3,"file":"four.css","mappings":"AAAA,GACE,WACF,CACA,GACE,UACF","sources":["webpack:///./test/bar2.css"],"sourcesContent":["h1 {\\n color: green;\\n}\\nh2 {\\n color: yellow;\\n}\\nh3 {\\n}\\n"],"names":[],"sourceRoot":""}", 171 | "one.css": "a{text-align:center} 172 | /*# sourceMappingURL=one.css.map*/", 173 | "one.css.map": "{"version":3,"file":"one.css","mappings":"AAAA,EACE,iBACF","sources":["webpack:///./test/foo.css"],"sourcesContent":["a {\\n text-align: center;\\n}\\n"],"names":[],"sourceRoot":""}", 174 | "three.css": "body{color:red;font-size:20px}a{color:blue} 175 | /*# sourceMappingURL=three.css.map*/", 176 | "three.css.map": "{"version":3,"file":"three.css","mappings":"AAGA,KACE,SAAU,CACV,cACF,CACA,EACE,UACF","sources":["webpack:///./test/bar1.css"],"sourcesContent":["body {\\n color: red;\\n}\\nbody {\\n color: red;\\n font-size: 20px;\\n}\\na {\\n color: blue;\\n}\\n"],"names":[],"sourceRoot":""}", 177 | "two.css": "a{text-align:center} 178 | /*# sourceMappingURL=two.css.map*/", 179 | "two.css.map": "{"version":3,"file":"two.css","mappings":"AAAA,EACE,iBACF","sources":["webpack:///./test/foo.css"],"sourcesContent":["a {\\n text-align: center;\\n}\\n"],"names":[],"sourceRoot":""}", 180 | } 181 | `; 182 | 183 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option and source maps: assets 2`] = ` 184 | { 185 | "five.css": "body{color:red;font-size:20px}a{color:blue} 186 | /*# sourceMappingURL=five.css.map*/", 187 | "five.css.map": "{"version":3,"file":"five.css","mappings":"AAGA,KACE,SAAU,CACV,cACF,CACA,EACE,UACF","sources":["webpack:///./test/bar1.css"],"sourcesContent":["body {\\n color: red;\\n}\\nbody {\\n color: red;\\n font-size: 20px;\\n}\\na {\\n color: blue;\\n}\\n"],"names":[],"sourceRoot":""}", 188 | "four.css": "h1{color:green}h2{color:#ff0} 189 | /*# sourceMappingURL=four.css.map*/", 190 | "four.css.map": "{"version":3,"file":"four.css","mappings":"AAAA,GACE,WACF,CACA,GACE,UACF","sources":["webpack:///./test/bar2.css"],"sourcesContent":["h1 {\\n color: green;\\n}\\nh2 {\\n color: yellow;\\n}\\nh3 {\\n}\\n"],"names":[],"sourceRoot":""}", 191 | "one.css": "a{text-align:center} 192 | /*# sourceMappingURL=one.css.map*/", 193 | "one.css.map": "{"version":3,"file":"one.css","mappings":"AAAA,EACE,iBACF","sources":["webpack:///./test/foo.css"],"sourcesContent":["a {\\n text-align: center;\\n}\\n"],"names":[],"sourceRoot":""}", 194 | "three.css": "body{color:red;font-size:20px}a{color:blue} 195 | /*# sourceMappingURL=three.css.map*/", 196 | "three.css.map": "{"version":3,"file":"three.css","mappings":"AAGA,KACE,SAAU,CACV,cACF,CACA,EACE,UACF","sources":["webpack:///./test/bar1.css"],"sourcesContent":["body {\\n color: red;\\n}\\nbody {\\n color: red;\\n font-size: 20px;\\n}\\na {\\n color: blue;\\n}\\n"],"names":[],"sourceRoot":""}", 197 | "two.css": "a{text-align:center} 198 | /*# sourceMappingURL=two.css.map*/", 199 | "two.css.map": "{"version":3,"file":"two.css","mappings":"AAAA,EACE,iBACF","sources":["webpack:///./test/foo.css"],"sourcesContent":["a {\\n text-align: center;\\n}\\n"],"names":[],"sourceRoot":""}", 200 | } 201 | `; 202 | 203 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option and source maps: errors 1`] = `[]`; 204 | 205 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option and source maps: errors 2`] = `[]`; 206 | 207 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option and source maps: warnings 1`] = `[]`; 208 | 209 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option and source maps: warnings 2`] = `[]`; 210 | 211 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option: assets 1`] = ` 212 | { 213 | "five.css": "body{color:red;font-size:20px}a{color:blue}", 214 | "four.css": "h1{color:green}h2{color:#ff0}", 215 | "one.css": "a{text-align:center}", 216 | "three.css": "body{color:red;font-size:20px}a{color:blue}", 217 | "two.css": "a{text-align:center}", 218 | } 219 | `; 220 | 221 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option: assets 2`] = ` 222 | { 223 | "five.css": "body{color:red;font-size:20px}a{color:blue}", 224 | "four.css": "h1{color:green}h2{color:#ff0}", 225 | "one.css": "a{text-align:center}", 226 | "three.css": "body{color:red;font-size:20px}a{color:blue}", 227 | "two.css": "a{text-align:center}", 228 | } 229 | `; 230 | 231 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option: errors 1`] = `[]`; 232 | 233 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option: errors 2`] = `[]`; 234 | 235 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option: warnings 1`] = `[]`; 236 | 237 | exports[`"cache" option should work with the "filesystem" value for the "cache.type" option: warnings 2`] = `[]`; 238 | 239 | exports[`"cache" option should work with the "memory" value for the "cache.type" option: assets 1`] = ` 240 | { 241 | "five.css": "body{color:red;font-size:20px}a{color:blue}", 242 | "four.css": "h1{color:green}h2{color:#ff0}", 243 | "one.css": "a{text-align:center}", 244 | "three.css": "body{color:red;font-size:20px}a{color:blue}", 245 | "two.css": "a{text-align:center}", 246 | } 247 | `; 248 | 249 | exports[`"cache" option should work with the "memory" value for the "cache.type" option: assets 2`] = ` 250 | { 251 | "five.css": "body{color:red;font-size:20px}a{color:blue}", 252 | "four.css": "h1{color:green}h2{color:#ff0}", 253 | "one.css": "a{text-align:center}", 254 | "three.css": "body{color:red;font-size:20px}a{color:blue}", 255 | "two.css": "a{text-align:center}", 256 | } 257 | `; 258 | 259 | exports[`"cache" option should work with the "memory" value for the "cache.type" option: errors 1`] = `[]`; 260 | 261 | exports[`"cache" option should work with the "memory" value for the "cache.type" option: errors 2`] = `[]`; 262 | 263 | exports[`"cache" option should work with the "memory" value for the "cache.type" option: warnings 1`] = `[]`; 264 | 265 | exports[`"cache" option should work with the "memory" value for the "cache.type" option: warnings 2`] = `[]`; 266 | -------------------------------------------------------------------------------- /test/cache-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import del from "del"; 4 | 5 | import CssMinimizerPlugin from "../src/index"; 6 | 7 | import { 8 | compile, 9 | getCompiler, 10 | getErrors, 11 | getWarnings, 12 | readAssets, 13 | } from "./helpers"; 14 | 15 | describe('"cache" option', () => { 16 | const fileSystemCacheDirectory = path.resolve( 17 | __dirname, 18 | "./outputs/type-filesystem", 19 | ); 20 | const fileSystemCacheDirectory1 = path.resolve( 21 | __dirname, 22 | "./outputs/type-filesystem-1", 23 | ); 24 | const fileSystemCacheDirectory2 = path.resolve( 25 | __dirname, 26 | "./outputs/type-filesystem-2", 27 | ); 28 | 29 | beforeAll(() => 30 | Promise.all([ 31 | del(fileSystemCacheDirectory), 32 | del(fileSystemCacheDirectory1), 33 | del(fileSystemCacheDirectory2), 34 | ]), 35 | ); 36 | 37 | it('should work with the "false" value for the "cache" option', async () => { 38 | const compiler = getCompiler({ 39 | entry: { 40 | one: path.join(__dirname, "fixtures", "cache.js"), 41 | two: path.join(__dirname, "fixtures", "cache-1.js"), 42 | three: path.join(__dirname, "fixtures", "cache-2.js"), 43 | four: path.join(__dirname, "fixtures", "cache-3.js"), 44 | five: path.join(__dirname, "fixtures", "cache-4.js"), 45 | }, 46 | cache: false, 47 | }); 48 | 49 | new CssMinimizerPlugin().apply(compiler); 50 | 51 | let getCounter = 0; 52 | 53 | compiler.cache.hooks.get.tap( 54 | { name: "TestCache", stage: -100 }, 55 | (identifier) => { 56 | if (identifier.includes("CssMinimizerWebpackPlugin")) { 57 | getCounter += 1; 58 | } 59 | }, 60 | ); 61 | 62 | let storeCounter = 0; 63 | 64 | compiler.cache.hooks.store.tap( 65 | { name: "TestCache", stage: -100 }, 66 | (identifier) => { 67 | if (identifier.includes("CssMinimizerWebpackPlugin")) { 68 | storeCounter += 1; 69 | } 70 | }, 71 | ); 72 | 73 | const stats = await compile(compiler); 74 | 75 | // Without cache webpack always try to get 76 | expect(getCounter).toBe(5); 77 | // Without cache webpack always try to store 78 | expect(storeCounter).toBe(5); 79 | expect(readAssets(compiler, stats, /\.css$/)).toMatchSnapshot("assets"); 80 | expect(getErrors(stats)).toMatchSnapshot("errors"); 81 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 82 | 83 | getCounter = 0; 84 | storeCounter = 0; 85 | 86 | const newStats = await compile(compiler); 87 | 88 | // Without cache webpack always try to get 89 | expect(getCounter).toBe(5); 90 | // Without cache webpack always try to store 91 | expect(storeCounter).toBe(5); 92 | expect(readAssets(compiler, newStats, /\.css$/)).toMatchSnapshot("assets"); 93 | expect(getErrors(newStats)).toMatchSnapshot("errors"); 94 | expect(getWarnings(newStats)).toMatchSnapshot("warnings"); 95 | 96 | await new Promise((resolve) => { 97 | compiler.close(() => { 98 | resolve(); 99 | }); 100 | }); 101 | }); 102 | 103 | it('should work with the "memory" value for the "cache.type" option', async () => { 104 | const compiler = getCompiler({ 105 | entry: { 106 | one: path.join(__dirname, "fixtures", "cache.js"), 107 | two: path.join(__dirname, "fixtures", "cache-1.js"), 108 | three: path.join(__dirname, "fixtures", "cache-2.js"), 109 | four: path.join(__dirname, "fixtures", "cache-3.js"), 110 | five: path.join(__dirname, "fixtures", "cache-4.js"), 111 | }, 112 | cache: { 113 | type: "memory", 114 | }, 115 | }); 116 | 117 | new CssMinimizerPlugin().apply(compiler); 118 | 119 | let getCounter = 0; 120 | 121 | compiler.cache.hooks.get.tap( 122 | { name: "TestCache", stage: -100 }, 123 | (identifier) => { 124 | if (identifier.includes("CssMinimizerWebpackPlugin")) { 125 | getCounter += 1; 126 | } 127 | }, 128 | ); 129 | 130 | let storeCounter = 0; 131 | 132 | compiler.cache.hooks.store.tap( 133 | { name: "TestCache", stage: -100 }, 134 | (identifier) => { 135 | if (identifier.includes("CssMinimizerWebpackPlugin")) { 136 | storeCounter += 1; 137 | } 138 | }, 139 | ); 140 | 141 | const stats = await compile(compiler); 142 | 143 | // Get cache for assets 144 | expect(getCounter).toBe(5); 145 | // Store cached assets 146 | expect(storeCounter).toBe(5); 147 | expect(readAssets(compiler, stats, /\.css$/)).toMatchSnapshot("assets"); 148 | expect(getErrors(stats)).toMatchSnapshot("errors"); 149 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 150 | 151 | getCounter = 0; 152 | storeCounter = 0; 153 | 154 | const newStats = await compile(compiler); 155 | 156 | // Get cache for assets 157 | expect(getCounter).toBe(5); 158 | // No need to store, we got cached assets 159 | expect(storeCounter).toBe(0); 160 | expect(readAssets(compiler, newStats, /\.css$/)).toMatchSnapshot("assets"); 161 | expect(getErrors(newStats)).toMatchSnapshot("errors"); 162 | expect(getWarnings(newStats)).toMatchSnapshot("warnings"); 163 | 164 | await new Promise((resolve) => { 165 | compiler.close(() => { 166 | resolve(); 167 | }); 168 | }); 169 | }); 170 | 171 | it('should work with the "filesystem" value for the "cache.type" option', async () => { 172 | const compiler = getCompiler({ 173 | entry: { 174 | one: path.join(__dirname, "fixtures", "cache.js"), 175 | two: path.join(__dirname, "fixtures", "cache-1.js"), 176 | three: path.join(__dirname, "fixtures", "cache-2.js"), 177 | four: path.join(__dirname, "fixtures", "cache-3.js"), 178 | five: path.join(__dirname, "fixtures", "cache-4.js"), 179 | }, 180 | cache: { 181 | type: "filesystem", 182 | cacheDirectory: fileSystemCacheDirectory, 183 | }, 184 | }); 185 | 186 | new CssMinimizerPlugin().apply(compiler); 187 | 188 | let getCounter = 0; 189 | 190 | compiler.cache.hooks.get.tap( 191 | { name: "TestCache", stage: -100 }, 192 | (identifier) => { 193 | if (identifier.includes("CssMinimizerWebpackPlugin")) { 194 | getCounter += 1; 195 | } 196 | }, 197 | ); 198 | 199 | let storeCounter = 0; 200 | 201 | compiler.cache.hooks.store.tap( 202 | { name: "TestCache", stage: -100 }, 203 | (identifier) => { 204 | if (identifier.includes("CssMinimizerWebpackPlugin")) { 205 | storeCounter += 1; 206 | } 207 | }, 208 | ); 209 | 210 | const stats = await compile(compiler); 211 | 212 | // Get cache for assets 213 | expect(getCounter).toBe(5); 214 | // Store cached assets 215 | expect(storeCounter).toBe(5); 216 | expect(readAssets(compiler, stats, /\.css$/)).toMatchSnapshot("assets"); 217 | expect(getErrors(stats)).toMatchSnapshot("errors"); 218 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 219 | 220 | getCounter = 0; 221 | storeCounter = 0; 222 | 223 | const newStats = await compile(compiler); 224 | 225 | // Get cache for assets 226 | expect(getCounter).toBe(5); 227 | // No need to store, we got cached assets 228 | expect(storeCounter).toBe(0); 229 | expect(readAssets(compiler, newStats, /\.css$/)).toMatchSnapshot("assets"); 230 | expect(getErrors(newStats)).toMatchSnapshot("errors"); 231 | expect(getWarnings(newStats)).toMatchSnapshot("warnings"); 232 | 233 | await new Promise((resolve) => { 234 | compiler.close(() => { 235 | resolve(); 236 | }); 237 | }); 238 | }); 239 | 240 | it('should work with the "filesystem" value for the "cache.type" option and source maps', async () => { 241 | const compiler = getCompiler({ 242 | devtool: "source-map", 243 | entry: { 244 | one: path.resolve(__dirname, "./fixtures/cache.js"), 245 | two: path.resolve(__dirname, "./fixtures/cache-1.js"), 246 | three: path.resolve(__dirname, "./fixtures/cache-2.js"), 247 | four: path.resolve(__dirname, "./fixtures/cache-3.js"), 248 | five: path.resolve(__dirname, "./fixtures/cache-4.js"), 249 | }, 250 | cache: { 251 | type: "filesystem", 252 | cacheDirectory: fileSystemCacheDirectory1, 253 | }, 254 | }); 255 | 256 | new CssMinimizerPlugin().apply(compiler); 257 | 258 | let getCounter = 0; 259 | 260 | compiler.cache.hooks.get.tap( 261 | { name: "TestCache", stage: -100 }, 262 | (identifier) => { 263 | if (identifier.includes("CssMinimizerWebpackPlugin")) { 264 | getCounter += 1; 265 | } 266 | }, 267 | ); 268 | 269 | let storeCounter = 0; 270 | 271 | compiler.cache.hooks.store.tap( 272 | { name: "TestCache", stage: -100 }, 273 | (identifier) => { 274 | if (identifier.includes("CssMinimizerWebpackPlugin")) { 275 | storeCounter += 1; 276 | } 277 | }, 278 | ); 279 | 280 | const stats = await compile(compiler); 281 | 282 | // Get cache for assets 283 | expect(getCounter).toBe(5); 284 | // Store cached assets 285 | expect(storeCounter).toBe(5); 286 | expect(readAssets(compiler, stats, /\.css(\.map)?$/)).toMatchSnapshot( 287 | "assets", 288 | ); 289 | expect(getErrors(stats)).toMatchSnapshot("errors"); 290 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 291 | 292 | getCounter = 0; 293 | storeCounter = 0; 294 | 295 | const newStats = await compile(compiler); 296 | 297 | // Get cache for assets 298 | expect(getCounter).toBe(5); 299 | // No need to store, we got cached assets 300 | expect(storeCounter).toBe(0); 301 | expect(readAssets(compiler, newStats, /\.css(\.map)?$/)).toMatchSnapshot( 302 | "assets", 303 | ); 304 | expect(getErrors(newStats)).toMatchSnapshot("errors"); 305 | expect(getWarnings(newStats)).toMatchSnapshot("warnings"); 306 | 307 | await new Promise((resolve) => { 308 | compiler.close(() => { 309 | resolve(); 310 | }); 311 | }); 312 | }); 313 | 314 | it('should work with the "filesystem" value for the "cache.type" option and output warnings', async () => { 315 | const compiler = getCompiler({ 316 | entry: { 317 | one: path.resolve(__dirname, "./fixtures/cache.js"), 318 | two: path.resolve(__dirname, "./fixtures/cache-1.js"), 319 | three: path.resolve(__dirname, "./fixtures/cache-2.js"), 320 | four: path.resolve(__dirname, "./fixtures/cache-3.js"), 321 | five: path.resolve(__dirname, "./fixtures/cache-4.js"), 322 | }, 323 | cache: { 324 | type: "filesystem", 325 | cacheDirectory: fileSystemCacheDirectory2, 326 | }, 327 | }); 328 | 329 | new CssMinimizerPlugin({ 330 | minify: (data) => { 331 | const postcss = require("postcss"); 332 | 333 | const [[fileName, input]] = Object.entries(data); 334 | const plugin = () => ({ 335 | postcssPlugin: "warning-plugin", 336 | OnceExit(decl, { result }) { 337 | result.warn(`Warning from ${result.opts.from}`, { 338 | plugin: "warning-plugin", 339 | }); 340 | }, 341 | }); 342 | 343 | plugin.postcss = true; 344 | 345 | return postcss([plugin]) 346 | .process(input, { from: fileName, to: fileName }) 347 | .then((result) => ({ 348 | code: result.css, 349 | map: result.map, 350 | error: result.error, 351 | warnings: result.warnings(), 352 | })); 353 | }, 354 | }).apply(compiler); 355 | 356 | let getCounter = 0; 357 | 358 | compiler.cache.hooks.get.tap( 359 | { name: "TestCache", stage: -100 }, 360 | (identifier) => { 361 | if (identifier.includes("CssMinimizerWebpackPlugin")) { 362 | getCounter += 1; 363 | } 364 | }, 365 | ); 366 | 367 | let storeCounter = 0; 368 | 369 | compiler.cache.hooks.store.tap( 370 | { name: "TestCache", stage: -100 }, 371 | (identifier) => { 372 | if (identifier.includes("CssMinimizerWebpackPlugin")) { 373 | storeCounter += 1; 374 | } 375 | }, 376 | ); 377 | 378 | const stats = await compile(compiler); 379 | 380 | // Get cache for assets 381 | expect(getCounter).toBe(5); 382 | // Store cached assets 383 | expect(storeCounter).toBe(5); 384 | expect(readAssets(compiler, stats, /\.css$/)).toMatchSnapshot("assets"); 385 | expect(getErrors(stats)).toMatchSnapshot("errors"); 386 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 387 | 388 | getCounter = 0; 389 | storeCounter = 0; 390 | 391 | const newStats = await compile(compiler); 392 | 393 | // Get cache for assets 394 | expect(getCounter).toBe(5); 395 | // No need to store, we got cached assets 396 | expect(storeCounter).toBe(0); 397 | expect(readAssets(compiler, newStats, /\.css$/)).toMatchSnapshot("assets"); 398 | expect(getErrors(newStats)).toMatchSnapshot("errors"); 399 | expect(getWarnings(newStats)).toMatchSnapshot("warnings"); 400 | 401 | await new Promise((resolve) => { 402 | compiler.close(() => { 403 | resolve(); 404 | }); 405 | }); 406 | }); 407 | }); 408 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [7.0.4](https://github.com/webpack/css-minimizer-webpack-plugin/compare/v7.0.3...v7.0.4) (2025-12-11) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * handle empty files ([#295](https://github.com/webpack/css-minimizer-webpack-plugin/issues/295)) ([8b273d1](https://github.com/webpack/css-minimizer-webpack-plugin/commit/8b273d15f9ca2baf7cd0e3c03817e74be7221e5b)) 11 | 12 | ### [7.0.3](https://github.com/webpack/css-minimizer-webpack-plugin/compare/v7.0.2...v7.0.3) (2025-12-05) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * respect errors and warnings from minimizer without code ([933fb49](https://github.com/webpack/css-minimizer-webpack-plugin/commit/933fb49e66c17509a11c2888189a8c300db8e4aa)) 18 | 19 | ### [7.0.2](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v7.0.1...v7.0.2) (2025-03-06) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * better types ([9d009b3](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/9d009b366a6f45b44d3120bda8450c28ceee6427)) 25 | 26 | ### [7.0.1](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v7.0.0...v7.0.1) (2025-03-06) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * better support worker threads ([eeaa5e1](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/eeaa5e16861614c54568db35c17a2e8cad0d3c26)) 32 | * use os.availableParallelism() for parallelism when it is available ([b07feeb](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/b07feeb3e46e6a3d6ab9a32ca19711559b3afb3a)) 33 | 34 | ## [7.0.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v6.0.0...v7.0.0) (2024-05-07) 35 | 36 | 37 | ### ⚠ BREAKING CHANGES 38 | 39 | * update cssnano to 7.0 ([#263](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/263)) ([a252ce9](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/a252ce91aad42181af0e4c71f1a00990c1efeeeb)) 40 | 41 | ## [6.0.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v5.0.1...v6.0.0) (2024-01-17) 42 | 43 | 44 | ### ⚠ BREAKING CHANGES 45 | 46 | * minimum supported Node.js version is `18.12.0` ([#252](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/252)) ([f7f74c0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/f7f74c02c8a53e517d9942807c198d1daff0c4b5)) 47 | 48 | ### [5.0.1](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v5.0.0...v5.0.1) (2023-06-13) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * improve perf ([#235](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/235)) ([959eb89](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/959eb89aa1e37df88d2decbe6f40c096f10a3745)) 54 | 55 | ## [5.0.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v4.2.2...v5.0.0) (2023-03-27) 56 | 57 | 58 | ### ⚠ BREAKING CHANGES 59 | 60 | * update `cssnano` to v6 (migration guide - https://github.com/cssnano/cssnano/releases/tag/cssnano%406.0.0) (#224) 61 | 62 | ### Features 63 | 64 | * update `cssnano` to v6 ([#224](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/224)) ([cfcae0c](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/cfcae0c9e2b7afd740774104a8e5eb675fe984db)) 65 | 66 | ### [4.2.2](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v4.2.1...v4.2.2) (2022-10-13) 67 | 68 | 69 | ### Bug Fixes 70 | 71 | * handle `swc` errors ([#202](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/202)) ([b1ce195](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/b1ce195a0de508217ea1a91d7f8e2683e77b495a)) 72 | 73 | ### [4.2.1](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v4.2.0...v4.2.1) (2022-10-06) 74 | 75 | 76 | ### Bug Fixes 77 | 78 | * crash ([#200](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/200)) ([c50b2b2](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/c50b2b25348a78a6bfaff248e0fb356a70a4241c)) 79 | 80 | ## [4.2.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v4.1.0...v4.2.0) (2022-09-29) 81 | 82 | 83 | ### Features 84 | 85 | * added `swc` minimizer ([#197](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/197)) ([5461421](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/546142104cb30e4711ba800170961822de7d0097)) 86 | 87 | ## [4.1.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v4.0.0...v4.1.0) (2022-09-09) 88 | 89 | 90 | ### Features 91 | 92 | * added `lightningcss` support ([#192](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/192)) ([04a3347](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/04a3347610a0044116bfebfd3f3be50d3ada4e04)) 93 | 94 | ## [4.0.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v3.4.1...v4.0.0) (2022-05-18) 95 | 96 | 97 | ### ⚠ BREAKING CHANGES 98 | 99 | * minimum supported `Node.js` version is `14.15.0` 100 | 101 | ### [3.4.1](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v3.4.0...v3.4.1) (2022-01-18) 102 | 103 | 104 | ### Bug Fixes 105 | 106 | * types ([9c8b0f3](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/9c8b0f35cc6fc8816c387c48c535d71891d8aa65)) 107 | 108 | ## [3.4.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v3.3.1...v3.4.0) (2022-01-18) 109 | 110 | 111 | ### Features 112 | 113 | * added `@parcel/css` support ([#154](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/154)) ([5e5fe51](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/5e5fe512c7209f0ad4c7f9c55f47cf9763adfc74)) 114 | 115 | ### [3.3.1](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v3.3.0...v3.3.1) (2021-12-21) 116 | 117 | 118 | ### Bug Fixes 119 | 120 | * cssnano types ([#147](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/147)) ([a51026e](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/a51026e3b948fa14d2334d823b3bbb6ea482c57b)) 121 | 122 | ## [3.3.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v3.2.0...v3.3.0) (2021-12-16) 123 | 124 | 125 | ### Features 126 | 127 | * removed cjs wrapper and generated types in commonjs format (`export =` and `namespaces` used in types), now you can directly use exported types ([3262a9a](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/3262a9aee2c89e6aed00c4f76443e0d1e07489f0)) 128 | 129 | ## [3.2.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v3.1.4...v3.2.0) (2021-11-23) 130 | 131 | 132 | ### Features 133 | 134 | * added types ([#142](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/142)) ([fb91610](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/fb91610730166b8fb4fb09cee5da781f0ec98e70)) 135 | 136 | ### [3.1.4](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v3.1.3...v3.1.4) (2021-11-17) 137 | 138 | 139 | ### Chore 140 | 141 | * update `schema-utils` package to `4.0.0` version 142 | 143 | ### [3.1.3](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v3.1.2...v3.1.3) (2021-11-10) 144 | 145 | 146 | ### Bug Fixes 147 | 148 | * source map generation for `cssnano` and `clean-css` ([#135](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/135)) ([a9dd43e](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/a9dd43e1a7284bb713e19eac6fa3f3724fb9d48b)) 149 | 150 | ### [3.1.2](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v3.1.1...v3.1.2) (2021-11-08) 151 | 152 | 153 | ### Bug Fixes 154 | 155 | * handle `esbuild` warnings ([f427f41](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/f427f41d319b340262c9b5061cf5427bdf5a5f78)) 156 | 157 | ### [3.1.1](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v3.1.0...v3.1.1) (2021-10-05) 158 | 159 | 160 | ### Bug Fixes 161 | 162 | * minified result can be empty ([#127](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/127)) ([0a8ad71](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/0a8ad71fa0da86c5f76381f13ddf013db02a60ea)) 163 | 164 | ## [3.1.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v3.0.2...v3.1.0) (2021-10-04) 165 | 166 | 167 | ### Features 168 | 169 | * added `esbuild` minimizer ([#122](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/122)) ([987d454](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/987d45402af81d88fbd1489021f8d14e5ff26a74)) 170 | * allow returning errors from custom minimize function ([#121](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/121)) ([c9a11b2](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/c9a11b2349d24206bc03cf3670f50a822ef7a52b)) 171 | * output documentation links on errors ([4e8afba](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/4e8afbae5c89b0fe52b83b69fb2c5ab8a6382750)) 172 | 173 | 174 | ### Bug Fixes 175 | 176 | * source map generation for multiple `minify` functions ([b736099](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/b7360993a50a4d998bb92f0a3d491030452fcb0b)) 177 | 178 | ### [3.0.2](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v3.0.1...v3.0.2) (2021-06-25) 179 | 180 | ### Chore 181 | 182 | * update `serialize-javascript` 183 | 184 | ### [3.0.1](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v3.0.0...v3.0.1) (2021-05-31) 185 | 186 | ### Chore 187 | 188 | * update `jest-worker` 189 | 190 | ## [3.0.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v2.0.0...v3.0.0) (2021-05-12) 191 | 192 | 193 | ### ⚠ BREAKING CHANGES 194 | 195 | * minimum supported `Node.js` version is `12.13.0` 196 | 197 | ## [2.0.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v1.3.0...v2.0.0) (2021-04-10) 198 | 199 | 200 | ### ⚠ BREAKING CHANGES 201 | 202 | * update `cssnano` to `5.0.0` version 203 | * drop `webpack` v4 support, 204 | * removed the `cache` option (respect the [`cache`](https://webpack.js.org/configuration/other-options/#cache) option from `webpack`) 205 | * removed the `cacheKeys` option respect the [`cache`](https://webpack.js.org/configuration/other-options/#cache) option from `webpack`) 206 | * removed the sourceMap option (respect the [`devtool`](https://webpack.js.org/configuration/devtool/) option from `webpack`) 207 | 208 | ### Features 209 | 210 | * added defaults functions for `clean-css` and `csso`, please look at [here](https://github.com/webpack-contrib/css-minimizer-webpack-plugin#minify) ([5211eed](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/5211eed3c816212a30715ebf0798393c886343d0)) 211 | * added the ability to pass an array of functions to the minify ([91f9977](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/91f9977e9fdd1447cb45bc6f5d4b56fe7c26fb04)) 212 | * update `cssnano` to `5.0.0` version ([4d2a8fd](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/4d2a8fd275ab17f6d98e4581b6c2adf09c73c91c)) 213 | 214 | ## [1.3.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v1.2.0...v1.3.0) (2021-03-15) 215 | 216 | 217 | ### Features 218 | 219 | * added support `processorOptions` for `cssnano` ([8865423](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/886542375dc37411e271d151b7060be574fc5898)) 220 | 221 | ## [1.2.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v1.1.5...v1.2.0) (2021-01-08) 222 | 223 | 224 | ### Features 225 | 226 | * optimize CSS assets added later by plugins (webpack@5 only) ([#47](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/47)) ([bdb3f52](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/bdb3f524289a2c131e2ec3728cf10b4c9a0010c7)) 227 | 228 | 229 | ### Bug Fixes 230 | 231 | * crash with source maps when the `parallel` option is `false` ([#53](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/53)) ([4fe95f9](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/4fe95f9b63764d06ffc4e6d6d77a45ba2dd1b76b)) 232 | 233 | ### [1.1.5](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v1.1.4...v1.1.5) (2020-10-07) 234 | 235 | ### Chore 236 | 237 | * update `schema-utils` 238 | 239 | ### [1.1.4](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v1.1.3...v1.1.4) (2020-09-18) 240 | 241 | 242 | ### Bug Fixes 243 | 244 | * weak cache 245 | * source map generation 246 | * cache warnings between builds 247 | 248 | ### [1.1.3](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v1.1.2...v1.1.3) (2020-09-03) 249 | 250 | 251 | ### Bug Fixes 252 | 253 | * do not crash on the `minify` option ([fd9abac](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/fd9abac59dc82561ef1db603165526caba6e9abe)) 254 | 255 | ### [1.1.2](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v1.1.1...v1.1.2) (2020-08-24) 256 | 257 | 258 | ### Bug Fixes 259 | 260 | * compatibility with webpack 5 ([6232829](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/62328298d6a81f31e773d11ced8811ee41cd0470)) 261 | 262 | ### [1.1.1](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v1.1.0...v1.1.1) (2020-08-10) 263 | 264 | 265 | ### Bug Fixes 266 | 267 | * compatibility with `10.13` version of `Node.js` ([d38ea79](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/d38ea793bc2cefc6289d2fe8f2e5df8c31e487e0)) 268 | 269 | ## [1.1.0](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v1.0.0...v1.1.0) (2020-08-04) 270 | 271 | 272 | ### Features 273 | 274 | * show minimized assets in stats for webpack@5 ([#19](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/19)) ([cb038b9](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/cb038b91b15e934a56c260635506df4f02efd747)) 275 | 276 | 277 | ### Bug Fixes 278 | 279 | * compatibility cache feature with webpack@5 ([#16](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/16)) ([997e00f](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/997e00f66298219dccfdff8c01c71bebc973df49)) 280 | * skip double compression for child compilation ([#18](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/issues/18)) ([ffc71c2](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/commit/ffc71c2c5269ba12c794be87c3257390fdd9c926)) 281 | 282 | ## 1.0.0 - 2020-08-01 283 | 284 | Initial release 285 | -------------------------------------------------------------------------------- /test/minimizerOptions-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import CopyPlugin from "copy-webpack-plugin"; 4 | import MiniCssExtractPlugin from "mini-css-extract-plugin"; 5 | import sugarss from "sugarss"; 6 | 7 | import CssMinimizerPlugin from "../src/index"; 8 | 9 | import { compile, getCompiler, readAsset } from "./helpers"; 10 | 11 | describe('when applied with "minimizerOptions" option', () => { 12 | it('matches snapshot for "discardComments" option (enable [default])', () => { 13 | const compiler = getCompiler({ 14 | entry: { 15 | entry: path.join( 16 | __dirname, 17 | "fixtures", 18 | "minimizerOptions", 19 | "discardComments.css", 20 | ), 21 | }, 22 | }); 23 | new CssMinimizerPlugin().apply(compiler); 24 | 25 | return compile(compiler).then((stats) => { 26 | expect(stats.compilation.errors).toEqual([]); 27 | expect(stats.compilation.warnings).toEqual([]); 28 | 29 | for (const file in stats.compilation.assets) { 30 | if (/\.js$/.test(file)) continue; 31 | expect(readAsset(file, compiler, stats)).toMatchSnapshot(file); 32 | } 33 | }); 34 | }); 35 | 36 | it('matches snapshot for "discardComments" option (disable)', () => { 37 | const compiler = getCompiler({ 38 | entry: { 39 | entry: path.join( 40 | __dirname, 41 | "fixtures", 42 | "minimizerOptions", 43 | "discardComments.css", 44 | ), 45 | }, 46 | }); 47 | new CssMinimizerPlugin({ 48 | minimizerOptions: { 49 | preset: ["default", { discardComments: false }], 50 | }, 51 | }).apply(compiler); 52 | 53 | return compile(compiler).then((stats) => { 54 | expect(stats.compilation.errors).toEqual([]); 55 | expect(stats.compilation.warnings).toEqual([]); 56 | 57 | for (const file in stats.compilation.assets) { 58 | if (/\.js$/.test(file)) continue; 59 | expect(readAsset(file, compiler, stats)).toMatchSnapshot(file); 60 | } 61 | }); 62 | }); 63 | 64 | it('matches snapshot for "discardComments" option (enable, with "removeAll" option)', () => { 65 | const compiler = getCompiler({ 66 | entry: { 67 | entry: path.join( 68 | __dirname, 69 | "fixtures", 70 | "minimizerOptions", 71 | "discardComments.css", 72 | ), 73 | }, 74 | }); 75 | new CssMinimizerPlugin({ 76 | minimizerOptions: { 77 | preset: ["default", { discardComments: { removeAll: true } }], 78 | }, 79 | }).apply(compiler); 80 | 81 | return compile(compiler).then((stats) => { 82 | expect(stats.compilation.errors).toEqual([]); 83 | expect(stats.compilation.warnings).toEqual([]); 84 | 85 | for (const file in stats.compilation.assets) { 86 | if (/\.js$/.test(file)) continue; 87 | expect(readAsset(file, compiler, stats)).toMatchSnapshot(file); 88 | } 89 | }); 90 | }); 91 | 92 | it('matches snapshot for "preset" option with require.resolve "String" value', () => { 93 | const compiler = getCompiler({ 94 | entry: { 95 | entry: path.join( 96 | __dirname, 97 | "fixtures", 98 | "minimizerOptions", 99 | "order.css", 100 | ), 101 | }, 102 | }); 103 | new CssMinimizerPlugin({ 104 | minimizerOptions: { 105 | // eslint-disable-next-line n/no-extraneous-require 106 | preset: require.resolve("cssnano-preset-default"), 107 | }, 108 | }).apply(compiler); 109 | 110 | const compiler2 = getCompiler({ 111 | entry: { 112 | entry: path.join( 113 | __dirname, 114 | "fixtures", 115 | "minimizerOptions", 116 | "order.css", 117 | ), 118 | }, 119 | }); 120 | new CssMinimizerPlugin({ 121 | minimizerOptions: { 122 | // eslint-disable-next-line n/no-extraneous-require 123 | preset: require.resolve("cssnano-preset-default"), 124 | }, 125 | }).apply(compiler2); 126 | 127 | const result1 = compile(compiler).then((stats) => { 128 | expect(stats.compilation.errors).toEqual([]); 129 | expect(stats.compilation.warnings).toEqual([]); 130 | 131 | for (const file in stats.compilation.assets) { 132 | if (/\.js$/.test(file)) continue; 133 | expect(readAsset(file, compiler, stats)).toMatchSnapshot( 134 | "default-preset", 135 | ); 136 | } 137 | }); 138 | 139 | const result2 = compile(compiler2).then((stats) => { 140 | expect(stats.compilation.errors).toEqual([]); 141 | expect(stats.compilation.warnings).toEqual([]); 142 | 143 | for (const file in stats.compilation.assets) { 144 | if (/\.js$/.test(file)) continue; 145 | expect(readAsset(file, compiler2, stats)).toMatchSnapshot( 146 | "preset-simple", 147 | ); 148 | } 149 | }); 150 | 151 | return Promise.all([result1, result2]); 152 | }); 153 | 154 | it('matches snapshot for "mergeRules" option (enable [default])', () => { 155 | const compiler = getCompiler({ 156 | entry: { 157 | entry: path.join( 158 | __dirname, 159 | "fixtures", 160 | "minimizerOptions", 161 | "mergeRules.css", 162 | ), 163 | }, 164 | }); 165 | new CssMinimizerPlugin().apply(compiler); 166 | 167 | return compile(compiler).then((stats) => { 168 | expect(stats.compilation.errors).toEqual([]); 169 | expect(stats.compilation.warnings).toEqual([]); 170 | 171 | for (const file in stats.compilation.assets) { 172 | if (/\.js$/.test(file)) continue; 173 | expect(readAsset(file, compiler, stats)).toMatchSnapshot(file); 174 | } 175 | }); 176 | }); 177 | 178 | it('matches snapshot for "mergeRules" option (disable)', () => { 179 | const compiler = getCompiler({ 180 | entry: { 181 | entry: path.join( 182 | __dirname, 183 | "fixtures", 184 | "minimizerOptions", 185 | "mergeRules.css", 186 | ), 187 | }, 188 | }); 189 | new CssMinimizerPlugin({ 190 | minimizerOptions: { 191 | preset: ["default", { mergeRules: false }], 192 | }, 193 | }).apply(compiler); 194 | 195 | return compile(compiler).then((stats) => { 196 | expect(stats.compilation.errors).toEqual([]); 197 | expect(stats.compilation.warnings).toEqual([]); 198 | 199 | for (const file in stats.compilation.assets) { 200 | if (/\.js$/.test(file)) continue; 201 | expect(readAsset(file, compiler, stats)).toMatchSnapshot(file); 202 | } 203 | }); 204 | }); 205 | 206 | it('matches snapshot for "discardEmpty" option (enable [default])', () => { 207 | const compiler = getCompiler({ 208 | entry: { 209 | entry: path.join( 210 | __dirname, 211 | "fixtures", 212 | "minimizerOptions", 213 | "discardEmpty.css", 214 | ), 215 | }, 216 | }); 217 | new CssMinimizerPlugin().apply(compiler); 218 | 219 | return compile(compiler).then((stats) => { 220 | expect(stats.compilation.errors).toEqual([]); 221 | expect(stats.compilation.warnings).toEqual([]); 222 | 223 | for (const file in stats.compilation.assets) { 224 | if (/\.js$/.test(file)) continue; 225 | expect(readAsset(file, compiler, stats)).toMatchSnapshot(file); 226 | } 227 | }); 228 | }); 229 | 230 | it('matches snapshot for "discardEmpty" option (disable)', () => { 231 | const compiler = getCompiler({ 232 | entry: { 233 | entry: path.join( 234 | __dirname, 235 | "fixtures", 236 | "minimizerOptions", 237 | "discardEmpty.css", 238 | ), 239 | }, 240 | }); 241 | new CssMinimizerPlugin({ 242 | minimizerOptions: { 243 | preset: ["default", { discardEmpty: false }], 244 | }, 245 | }).apply(compiler); 246 | 247 | return compile(compiler).then((stats) => { 248 | expect(stats.compilation.errors).toEqual([]); 249 | expect(stats.compilation.warnings).toEqual([]); 250 | 251 | for (const file in stats.compilation.assets) { 252 | if (/\.js$/.test(file)) continue; 253 | expect(readAsset(file, compiler, stats)).toMatchSnapshot(file); 254 | } 255 | }); 256 | }); 257 | 258 | it('matches snapshot for "parser" option with "Function" value', () => { 259 | const compiler = getCompiler({ 260 | entry: { 261 | entry: path.join(__dirname, "fixtures", "sugarss.js"), 262 | }, 263 | module: {}, 264 | plugins: [ 265 | new CopyPlugin({ 266 | patterns: [ 267 | { 268 | context: path.join(__dirname, "fixtures", "sss"), 269 | from: "index.sss", 270 | }, 271 | ], 272 | }), 273 | new MiniCssExtractPlugin({ 274 | filename: "[name].css", 275 | }), 276 | ], 277 | }); 278 | new CssMinimizerPlugin({ 279 | test: /\.(css|sss)$/i, 280 | parallel: false, 281 | minimizerOptions: { 282 | processorOptions: { 283 | parser: sugarss, 284 | }, 285 | }, 286 | }).apply(compiler); 287 | 288 | return compile(compiler).then((stats) => { 289 | expect(stats.compilation.errors).toEqual([]); 290 | expect(stats.compilation.warnings).toEqual([]); 291 | 292 | for (const file in stats.compilation.assets) { 293 | if (/\.js$/.test(file)) continue; 294 | expect(readAsset(file, compiler, stats)).toMatchSnapshot(file); 295 | } 296 | }); 297 | }); 298 | 299 | it('matches snapshot for "parser" option with "String" value', () => { 300 | const compiler = getCompiler({ 301 | entry: { 302 | entry: path.join(__dirname, "fixtures", "sugarss.js"), 303 | }, 304 | module: {}, 305 | plugins: [ 306 | new CopyPlugin({ 307 | patterns: [ 308 | { 309 | context: path.join(__dirname, "fixtures", "sss"), 310 | from: "index.sss", 311 | }, 312 | ], 313 | }), 314 | new MiniCssExtractPlugin({ 315 | filename: "[name].css", 316 | }), 317 | ], 318 | }); 319 | new CssMinimizerPlugin({ 320 | test: /\.(css|sss)$/i, 321 | minimizerOptions: { 322 | processorOptions: { 323 | parser: "sugarss", 324 | }, 325 | }, 326 | }).apply(compiler); 327 | 328 | return compile(compiler).then((stats) => { 329 | expect(stats.compilation.errors).toEqual([]); 330 | expect(stats.compilation.warnings).toEqual([]); 331 | 332 | for (const file in stats.compilation.assets) { 333 | if (/\.js$/.test(file)) continue; 334 | expect(readAsset(file, compiler, stats)).toMatchSnapshot(file); 335 | } 336 | }); 337 | }); 338 | 339 | it('matches snapshot for "stringifier" option with "Function" value', () => { 340 | const compiler = getCompiler({ 341 | entry: { 342 | entry: path.join(__dirname, "fixtures", "entry.js"), 343 | }, 344 | }); 345 | new CssMinimizerPlugin({ 346 | parallel: false, 347 | minimizerOptions: { 348 | processorOptions: { 349 | stringifier: sugarss, 350 | }, 351 | }, 352 | }).apply(compiler); 353 | 354 | return compile(compiler).then((stats) => { 355 | expect(stats.compilation.errors).toEqual([]); 356 | expect(stats.compilation.warnings).toEqual([]); 357 | 358 | for (const file in stats.compilation.assets) { 359 | if (/\.js$/.test(file)) continue; 360 | expect(readAsset(file, compiler, stats)).toMatchSnapshot(file); 361 | } 362 | }); 363 | }); 364 | 365 | it('matches snapshot for "stringifier" option with "String" value', () => { 366 | const compiler = getCompiler({ 367 | entry: { 368 | entry: path.join(__dirname, "fixtures", "entry.js"), 369 | }, 370 | }); 371 | new CssMinimizerPlugin({ 372 | minimizerOptions: { 373 | processorOptions: { 374 | stringifier: "sugarss", 375 | }, 376 | }, 377 | }).apply(compiler); 378 | 379 | return compile(compiler).then((stats) => { 380 | expect(stats.compilation.errors).toEqual([]); 381 | expect(stats.compilation.warnings).toEqual([]); 382 | 383 | for (const file in stats.compilation.assets) { 384 | if (/\.js$/.test(file)) continue; 385 | expect(readAsset(file, compiler, stats)).toMatchSnapshot(file); 386 | } 387 | }); 388 | }); 389 | 390 | it('matches snapshot for "syntax" option with "Function" value', () => { 391 | const compiler = getCompiler({ 392 | entry: { 393 | entry: path.join(__dirname, "fixtures", "sugarss.js"), 394 | }, 395 | module: {}, 396 | plugins: [ 397 | new CopyPlugin({ 398 | patterns: [ 399 | { 400 | context: path.join(__dirname, "fixtures", "sss"), 401 | from: "index.sss", 402 | }, 403 | ], 404 | }), 405 | new MiniCssExtractPlugin({ 406 | filename: "[name].css", 407 | }), 408 | ], 409 | }); 410 | new CssMinimizerPlugin({ 411 | test: /\.(css|sss)$/i, 412 | parallel: false, 413 | minimizerOptions: { 414 | processorOptions: { 415 | syntax: sugarss, 416 | }, 417 | }, 418 | }).apply(compiler); 419 | 420 | return compile(compiler).then((stats) => { 421 | expect(stats.compilation.errors).toEqual([]); 422 | expect(stats.compilation.warnings).toEqual([]); 423 | 424 | for (const file in stats.compilation.assets) { 425 | if (/\.js$/.test(file)) continue; 426 | expect(readAsset(file, compiler, stats)).toMatchSnapshot(file); 427 | } 428 | }); 429 | }); 430 | 431 | it('matches snapshot for "syntax" option with "String" value', () => { 432 | const compiler = getCompiler({ 433 | entry: { 434 | entry: path.join(__dirname, "fixtures", "sugarss.js"), 435 | }, 436 | module: {}, 437 | plugins: [ 438 | new CopyPlugin({ 439 | patterns: [ 440 | { 441 | context: path.join(__dirname, "fixtures", "sss"), 442 | from: "index.sss", 443 | }, 444 | ], 445 | }), 446 | new MiniCssExtractPlugin({ 447 | filename: "[name].css", 448 | }), 449 | ], 450 | }); 451 | new CssMinimizerPlugin({ 452 | test: /\.(css|sss)$/i, 453 | minimizerOptions: { 454 | processorOptions: { 455 | syntax: "sugarss", 456 | }, 457 | }, 458 | }).apply(compiler); 459 | 460 | return compile(compiler).then((stats) => { 461 | expect(stats.compilation.errors).toEqual([]); 462 | expect(stats.compilation.warnings).toEqual([]); 463 | 464 | for (const file in stats.compilation.assets) { 465 | if (/\.js$/.test(file)) continue; 466 | expect(readAsset(file, compiler, stats)).toMatchSnapshot(file); 467 | } 468 | }); 469 | }); 470 | }); 471 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** @typedef {import("./index.js").Input} Input */ 2 | /** @typedef {import("./index.js").RawSourceMap} RawSourceMap */ 3 | /** @typedef {import("./index.js").MinimizedResult} MinimizedResult */ 4 | /** @typedef {import("./index.js").CustomOptions} CustomOptions */ 5 | /** @typedef {import("postcss").ProcessOptions} ProcessOptions */ 6 | /** @typedef {import("postcss").Postcss} Postcss */ 7 | 8 | const notSettled = Symbol("not-settled"); 9 | 10 | /** 11 | * @template T 12 | * @typedef {() => Promise} Task 13 | */ 14 | 15 | /** 16 | * Run tasks with limited concurrency. 17 | * @template T 18 | * @param {number} limit Limit of tasks that run at once. 19 | * @param {Task[]} tasks List of tasks to run. 20 | * @returns {Promise} A promise that fulfills to an array of the results 21 | */ 22 | function throttleAll(limit, tasks) { 23 | return new Promise((resolve, reject) => { 24 | const result = Array.from({ length: tasks.length }).fill(notSettled); 25 | const entries = tasks.entries(); 26 | const next = () => { 27 | const { done, value } = entries.next(); 28 | 29 | if (done) { 30 | const isLast = !result.includes(notSettled); 31 | 32 | if (isLast) resolve(result); 33 | 34 | return; 35 | } 36 | 37 | const [index, task] = value; 38 | 39 | /** 40 | * @param {T} resultValue Result value 41 | */ 42 | const onFulfilled = (resultValue) => { 43 | result[index] = resultValue; 44 | next(); 45 | }; 46 | 47 | task().then(onFulfilled, reject); 48 | }; 49 | 50 | for (let i = 0; i < limit; i++) { 51 | next(); 52 | } 53 | }); 54 | } 55 | 56 | /* istanbul ignore next */ 57 | /** 58 | * @param {Input} input Input 59 | * @param {RawSourceMap=} sourceMap Source map 60 | * @param {CustomOptions=} minimizerOptions Minimizer options 61 | * @returns {Promise} Promise with minimized result 62 | */ 63 | async function cssnanoMinify( 64 | input, 65 | sourceMap, 66 | minimizerOptions = { preset: "default" }, 67 | ) { 68 | /** 69 | * @template T 70 | * @param {string} module Module to load 71 | * @returns {Promise} Promise with loaded module 72 | */ 73 | const load = async (module) => { 74 | let exports; 75 | 76 | try { 77 | exports = require(module); 78 | 79 | return exports; 80 | } catch (err) { 81 | let importESM; 82 | 83 | try { 84 | // eslint-disable-next-line no-new-func 85 | importESM = new Function("id", "return import(id);"); 86 | } catch { 87 | importESM = null; 88 | } 89 | 90 | if ( 91 | /** @type {Error & {code: string}} */ 92 | (err).code === "ERR_REQUIRE_ESM" && 93 | importESM 94 | ) { 95 | exports = await importESM(module); 96 | 97 | return exports.default; 98 | } 99 | 100 | throw err; 101 | } 102 | }; 103 | 104 | const [[name, code]] = Object.entries(input); 105 | /** @type {ProcessOptions} */ 106 | const postcssOptions = { 107 | from: name, 108 | ...(minimizerOptions.processorOptions && 109 | typeof minimizerOptions.processorOptions === "object" 110 | ? minimizerOptions.processorOptions 111 | : {}), 112 | }; 113 | 114 | if (typeof postcssOptions.parser === "string") { 115 | try { 116 | postcssOptions.parser = await load(postcssOptions.parser); 117 | } catch (error) { 118 | throw new Error( 119 | `Loading PostCSS "${postcssOptions.parser}" parser failed: ${ 120 | /** @type {Error} */ (error).message 121 | }\n\n(@${name})`, 122 | ); 123 | } 124 | } 125 | 126 | if (typeof postcssOptions.stringifier === "string") { 127 | try { 128 | postcssOptions.stringifier = await load(postcssOptions.stringifier); 129 | } catch (error) { 130 | throw new Error( 131 | `Loading PostCSS "${postcssOptions.stringifier}" stringifier failed: ${ 132 | /** @type {Error} */ (error).message 133 | }\n\n(@${name})`, 134 | ); 135 | } 136 | } 137 | 138 | if (typeof postcssOptions.syntax === "string") { 139 | try { 140 | postcssOptions.syntax = await load(postcssOptions.syntax); 141 | } catch (error) { 142 | throw new Error( 143 | `Loading PostCSS "${postcssOptions.syntax}" syntax failed: ${ 144 | /** @type {Error} */ (error).message 145 | }\n\n(@${name})`, 146 | ); 147 | } 148 | } 149 | 150 | if (sourceMap) { 151 | postcssOptions.map = { annotation: false }; 152 | } 153 | 154 | /** @type {Postcss} */ 155 | 156 | const postcss = require("postcss").default; 157 | 158 | const cssnano = require("cssnano"); 159 | 160 | // Types are broken 161 | const result = await postcss([cssnano(minimizerOptions)]).process( 162 | code, 163 | postcssOptions, 164 | ); 165 | 166 | return { 167 | code: result.css, 168 | map: result.map 169 | ? /** @type {RawSourceMap} */ ( 170 | /** @type {unknown} */ (result.map.toJSON()) 171 | ) 172 | : undefined, 173 | warnings: result.warnings().map(String), 174 | }; 175 | } 176 | 177 | cssnanoMinify.supportsWorkerThreads = () => true; 178 | 179 | /* istanbul ignore next */ 180 | /** 181 | * @param {Input} input Input 182 | * @param {RawSourceMap=} sourceMap Source map 183 | * @param {CustomOptions=} minimizerOptions Minimizer options 184 | * @returns {Promise} Promise with minimized result 185 | */ 186 | async function cssoMinify(input, sourceMap, minimizerOptions) { 187 | const csso = require("csso"); 188 | 189 | const [[filename, code]] = Object.entries(input); 190 | const result = csso.minify(code, { 191 | filename, 192 | sourceMap: Boolean(sourceMap), 193 | ...minimizerOptions, 194 | }); 195 | 196 | return { 197 | code: result.css, 198 | map: result.map 199 | ? /** @type {RawSourceMap} */ ( 200 | /** @type {{ toJSON(): RawSourceMap }} */ (result.map).toJSON() 201 | ) 202 | : undefined, 203 | }; 204 | } 205 | 206 | cssoMinify.supportsWorkerThreads = () => true; 207 | 208 | /* istanbul ignore next */ 209 | /** 210 | * @param {Input} input Input 211 | * @param {RawSourceMap=} sourceMap Source map 212 | * @param {CustomOptions=} minimizerOptions Minimizer options 213 | * @returns {Promise} Promise with minimized result 214 | */ 215 | async function cleanCssMinify(input, sourceMap, minimizerOptions) { 216 | const CleanCSS = require("clean-css"); 217 | 218 | const [[name, code]] = Object.entries(input); 219 | const result = await new CleanCSS({ 220 | sourceMap: Boolean(sourceMap), 221 | ...minimizerOptions, 222 | returnPromise: true, 223 | }).minify({ [name]: { styles: code } }); 224 | 225 | const generatedSourceMap = result.sourceMap 226 | ? /** @type {RawSourceMap} */ ( 227 | // eslint-disable-next-line jsdoc/no-restricted-syntax 228 | /** @type {any} */ (result.sourceMap).toJSON() 229 | ) 230 | : undefined; 231 | 232 | // workaround for source maps on windows 233 | if (generatedSourceMap) { 234 | const isWindowsPathSep = require("node:path").sep === "\\"; 235 | 236 | generatedSourceMap.sources = generatedSourceMap.sources.map( 237 | /** 238 | * @param {string | null} item Path item 239 | * @returns {string} Normalized path 240 | */ 241 | (item) => 242 | isWindowsPathSep ? (item || "").replaceAll("\\", "/") : item || "", 243 | ); 244 | } 245 | 246 | return { 247 | code: result.styles, 248 | map: generatedSourceMap, 249 | warnings: result.warnings, 250 | }; 251 | } 252 | 253 | cleanCssMinify.supportsWorkerThreads = () => true; 254 | 255 | /* istanbul ignore next */ 256 | /** 257 | * @param {Input} input Input 258 | * @param {RawSourceMap=} sourceMap Source map 259 | * @param {CustomOptions=} minimizerOptions Minimizer options 260 | * @returns {Promise} Promise with minimized result 261 | */ 262 | async function esbuildMinify(input, sourceMap, minimizerOptions) { 263 | /** 264 | * @param {import("esbuild").TransformOptions=} esbuildOptions ESBuild options 265 | * @returns {import("esbuild").TransformOptions} Built ESBuild options 266 | */ 267 | const buildEsbuildOptions = (esbuildOptions = {}) => 268 | // Need deep copy objects to avoid https://github.com/terser/terser/issues/366 269 | ({ 270 | loader: "css", 271 | minify: true, 272 | legalComments: "inline", 273 | ...esbuildOptions, 274 | sourcemap: false, 275 | }); 276 | 277 | const esbuild = require("esbuild"); 278 | 279 | // Copy `esbuild` options 280 | const esbuildOptions = buildEsbuildOptions(minimizerOptions); 281 | 282 | // Let `esbuild` generate a SourceMap 283 | if (sourceMap) { 284 | esbuildOptions.sourcemap = true; 285 | esbuildOptions.sourcesContent = false; 286 | } 287 | 288 | const [[filename, code]] = Object.entries(input); 289 | 290 | esbuildOptions.sourcefile = filename; 291 | 292 | const result = await esbuild.transform(code, esbuildOptions); 293 | 294 | return { 295 | code: result.code, 296 | 297 | map: result.map ? JSON.parse(result.map) : undefined, 298 | warnings: 299 | result.warnings.length > 0 300 | ? result.warnings.map((item) => ({ 301 | source: item.location && item.location.file, 302 | line: 303 | item.location && item.location.line 304 | ? item.location.line 305 | : undefined, 306 | column: 307 | item.location && item.location.column 308 | ? item.location.column 309 | : undefined, 310 | plugin: item.pluginName, 311 | message: `${item.text}${ 312 | item.detail ? `\nDetails:\n${item.detail}` : "" 313 | }${ 314 | item.notes.length > 0 315 | ? `\n\nNotes:\n${item.notes 316 | .map( 317 | (note) => 318 | `${ 319 | note.location 320 | ? `[${note.location.file}:${note.location.line}:${note.location.column}] ` 321 | : "" 322 | }${note.text}${ 323 | note.location 324 | ? `\nSuggestion: ${note.location.suggestion}` 325 | : "" 326 | }${ 327 | note.location 328 | ? `\nLine text:\n${note.location.lineText}\n` 329 | : "" 330 | }`, 331 | ) 332 | .join("\n")}` 333 | : "" 334 | }`, 335 | })) 336 | : [], 337 | }; 338 | } 339 | 340 | esbuildMinify.supportsWorkerThreads = () => false; 341 | 342 | // TODO remove in the next major release 343 | /* istanbul ignore next */ 344 | /** 345 | * @param {Input} input Input 346 | * @param {RawSourceMap=} sourceMap Source map 347 | * @param {CustomOptions=} minimizerOptions Minimizer options 348 | * @returns {Promise} Promise with minimized result 349 | */ 350 | async function parcelCssMinify(input, sourceMap, minimizerOptions) { 351 | const [[filename, code]] = Object.entries(input); 352 | // eslint-disable-next-line jsdoc/no-restricted-syntax 353 | /** 354 | * @param {Partial>=} parcelCssOptions Parcel CSS options 355 | * @returns {import("@parcel/css").TransformOptions} Built Parcel CSS options 356 | */ 357 | const buildParcelCssOptions = (parcelCssOptions = {}) => 358 | // Need deep copy objects to avoid https://github.com/terser/terser/issues/366 359 | ({ 360 | minify: true, 361 | ...parcelCssOptions, 362 | sourceMap: false, 363 | filename, 364 | code: new Uint8Array(Buffer.from(code)), 365 | }); 366 | 367 | const parcelCss = require("@parcel/css"); 368 | 369 | // Copy `parcel-css` options 370 | const parcelCssOptions = buildParcelCssOptions(minimizerOptions); 371 | 372 | // Let `esbuild` generate a SourceMap 373 | if (sourceMap) { 374 | parcelCssOptions.sourceMap = true; 375 | } 376 | 377 | const result = await parcelCss.transform(parcelCssOptions); 378 | 379 | return { 380 | code: result.code.toString(), 381 | 382 | map: result.map ? JSON.parse(result.map.toString()) : undefined, 383 | }; 384 | } 385 | 386 | parcelCssMinify.supportsWorkerThreads = () => false; 387 | 388 | /* istanbul ignore next */ 389 | /** 390 | * @param {Input} input Input 391 | * @param {RawSourceMap=} sourceMap Source map 392 | * @param {CustomOptions=} minimizerOptions Minimizer options 393 | * @returns {Promise} Promise with minimized result 394 | */ 395 | async function lightningCssMinify(input, sourceMap, minimizerOptions) { 396 | const [[filename, code]] = Object.entries(input); 397 | // eslint-disable-next-line jsdoc/no-restricted-syntax 398 | /** 399 | * @param {Partial>=} lightningCssOptions Lightning CSS options 400 | * @returns {import("lightningcss").TransformOptions} Built Lightning CSS options 401 | */ 402 | const buildLightningCssOptions = (lightningCssOptions = {}) => 403 | // Need deep copy objects to avoid https://github.com/terser/terser/issues/366 404 | ({ 405 | minify: true, 406 | ...lightningCssOptions, 407 | sourceMap: false, 408 | filename, 409 | code: new Uint8Array(Buffer.from(code)), 410 | }); 411 | 412 | const lightningCss = require("lightningcss"); 413 | 414 | // Copy `lightningCss` options 415 | const lightningCssOptions = buildLightningCssOptions(minimizerOptions); 416 | 417 | // Let `esbuild` generate a SourceMap 418 | if (sourceMap) { 419 | lightningCssOptions.sourceMap = true; 420 | } 421 | 422 | const result = await lightningCss.transform(lightningCssOptions); 423 | 424 | return { 425 | code: result.code.toString(), 426 | 427 | map: result.map ? JSON.parse(result.map.toString()) : undefined, 428 | }; 429 | } 430 | 431 | lightningCssMinify.supportsWorkerThreads = () => false; 432 | 433 | /* istanbul ignore next */ 434 | /** 435 | * @param {Input} input Input 436 | * @param {RawSourceMap=} sourceMap Source map 437 | * @param {CustomOptions=} minimizerOptions Minimizer options 438 | * @returns {Promise} Promise with minimized result 439 | */ 440 | async function swcMinify(input, sourceMap, minimizerOptions) { 441 | const [[filename, code]] = Object.entries(input); 442 | /** 443 | * @param {Partial=} swcOptions SWC options 444 | * @returns {import("@swc/css").MinifyOptions} Built SWC options 445 | */ 446 | const buildSwcOptions = (swcOptions = {}) => 447 | // Need deep copy objects to avoid https://github.com/terser/terser/issues/366 448 | ({ 449 | ...swcOptions, 450 | filename, 451 | }); 452 | 453 | const swc = require("@swc/css"); 454 | 455 | // Copy `swc` options 456 | const swcOptions = buildSwcOptions(minimizerOptions); 457 | 458 | // Let `swc` generate a SourceMap 459 | if (sourceMap) { 460 | swcOptions.sourceMap = true; 461 | } 462 | 463 | const result = await swc.minify(Buffer.from(code), swcOptions); 464 | 465 | return { 466 | code: result.code.toString(), 467 | 468 | map: result.map ? JSON.parse(result.map.toString()) : undefined, 469 | errors: result.errors 470 | ? result.errors.map((diagnostic) => { 471 | const error = new Error(diagnostic.message); 472 | 473 | // eslint-disable-next-line jsdoc/no-restricted-syntax 474 | /** @type {any} */ (error).span = diagnostic.span; 475 | 476 | // eslint-disable-next-line jsdoc/no-restricted-syntax 477 | /** @type {any} */ (error).level = diagnostic.level; 478 | 479 | return error; 480 | }) 481 | : undefined, 482 | }; 483 | } 484 | 485 | swcMinify.supportsWorkerThreads = () => false; 486 | 487 | /** 488 | * @template T 489 | * @param {(() => unknown) | undefined} fn Function to memoize 490 | * @returns {() => T} Memoized function 491 | */ 492 | function memoize(fn) { 493 | let cache = false; 494 | /** @type {T} */ 495 | let result; 496 | 497 | return () => { 498 | if (cache) { 499 | return result; 500 | } 501 | result = /** @type {T} */ (/** @type {() => unknown} */ (fn)()); 502 | cache = true; 503 | // Allow to clean up memory for fn 504 | // and all dependent resources 505 | 506 | fn = undefined; 507 | 508 | return result; 509 | }; 510 | } 511 | 512 | module.exports = { 513 | cleanCssMinify, 514 | cssnanoMinify, 515 | cssoMinify, 516 | esbuildMinify, 517 | lightningCssMinify, 518 | memoize, 519 | parcelCssMinify, 520 | swcMinify, 521 | throttleAll, 522 | }; 523 | -------------------------------------------------------------------------------- /test/__snapshots__/CssMinimizerPlugin.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`CssMinimizerPlugin buildError method 1`] = ` 4 | [Error: test.css from Css Minimizer plugin 5 | Message] 6 | `; 7 | 8 | exports[`CssMinimizerPlugin buildError method 2`] = ` 9 | [Error: test.css from Css Minimizer plugin 10 | Message [test.css:1,1]] 11 | `; 12 | 13 | exports[`CssMinimizerPlugin buildError method 3`] = ` 14 | [Error: test.css from Css Minimizer plugin 15 | Message [http://example.com/www/js/one.css:1,1][test.css:1,1]] 16 | `; 17 | 18 | exports[`CssMinimizerPlugin buildError method 4`] = ` 19 | [Error: test.css from Css Minimizer plugin 20 | Stack] 21 | `; 22 | 23 | exports[`CssMinimizerPlugin buildWarning method 1`] = ` 24 | [Warning: undefined from Css Minimizer plugin 25 | Warning test.css:1:1] 26 | `; 27 | 28 | exports[`CssMinimizerPlugin buildWarning method 2`] = ` 29 | [Warning: test.css from Css Minimizer plugin 30 | Warning test.css:1:1] 31 | `; 32 | 33 | exports[`CssMinimizerPlugin buildWarning method 3`] = ` 34 | [Warning: test.css from Css Minimizer plugin 35 | Warning test.css:1:1] 36 | `; 37 | 38 | exports[`CssMinimizerPlugin buildWarning method 4`] = ` 39 | [Warning: test.css from Css Minimizer plugin 40 | Warning http://example.com/www/js/one.css:1:1] 41 | `; 42 | 43 | exports[`CssMinimizerPlugin buildWarning method 5`] = ` 44 | [Warning: test.css from Css Minimizer plugin 45 | Warning http://example.com/www/js/one.css:1:1] 46 | `; 47 | 48 | exports[`CssMinimizerPlugin buildWarning method 6`] = `undefined`; 49 | 50 | exports[`CssMinimizerPlugin buildWarning method 7`] = ` 51 | [Warning: test.css from Css Minimizer plugin 52 | warning http://example.com/www/js/one.css:1:1] 53 | `; 54 | 55 | exports[`CssMinimizerPlugin should build error: error 1`] = ` 56 | [ 57 | "Error: error.css from Css Minimizer plugin 58 | /error.css:1:1: Unknown word null [error.css:1,1]", 59 | ] 60 | `; 61 | 62 | exports[`CssMinimizerPlugin should build error: warning 1`] = `[]`; 63 | 64 | exports[`CssMinimizerPlugin should build warning: error 1`] = `[]`; 65 | 66 | exports[`CssMinimizerPlugin should build warning: warning 1`] = ` 67 | [ 68 | "Warning: foo.css from Css Minimizer plugin 69 | [warning-plugin] Warning webpack://./test/foo.css:2:2", 70 | ] 71 | `; 72 | 73 | exports[`CssMinimizerPlugin should respect the hash options #1: assets 1`] = ` 74 | { 75 | "entry.css": "a{text-align:center}", 76 | } 77 | `; 78 | 79 | exports[`CssMinimizerPlugin should respect the hash options #1: errors 1`] = `[]`; 80 | 81 | exports[`CssMinimizerPlugin should respect the hash options #1: warnings 1`] = `[]`; 82 | 83 | exports[`CssMinimizerPlugin should run plugin against assets added later by plugins: assets 1`] = ` 84 | { 85 | "newFile.css": ".a{color:coral;display:block}", 86 | } 87 | `; 88 | 89 | exports[`CssMinimizerPlugin should run plugin against assets added later by plugins: errors 1`] = `[]`; 90 | 91 | exports[`CssMinimizerPlugin should run plugin against assets added later by plugins: warnings 1`] = `[]`; 92 | 93 | exports[`CssMinimizerPlugin should throw error from postcss: error 1`] = ` 94 | [ 95 | "Error: foo.css from Css Minimizer plugin 96 | error-plugin: /foo.css:2:3: Postcss error [webpack://./test/foo.css:2,2][foo.css:2,3]", 97 | ] 98 | `; 99 | 100 | exports[`CssMinimizerPlugin should throw error from postcss: warning 1`] = `[]`; 101 | 102 | exports[`CssMinimizerPlugin should work and do not use memory cache when the "cache" option is "false": assets 1`] = ` 103 | { 104 | "foo.css": "body{color:red}a{color:blue}", 105 | "style-2.css": "a{color:coral}", 106 | "style.css": "a{color:red}", 107 | } 108 | `; 109 | 110 | exports[`CssMinimizerPlugin should work and do not use memory cache when the "cache" option is "false": assets 2`] = ` 111 | { 112 | "foo.css": "body{color:red}a{color:blue}", 113 | "style-2.css": "a{color:coral}", 114 | "style.css": "a{color:red}", 115 | } 116 | `; 117 | 118 | exports[`CssMinimizerPlugin should work and do not use memory cache when the "cache" option is "false": errors 1`] = `[]`; 119 | 120 | exports[`CssMinimizerPlugin should work and do not use memory cache when the "cache" option is "false": errors 2`] = `[]`; 121 | 122 | exports[`CssMinimizerPlugin should work and do not use memory cache when the "cache" option is "false": warnings 1`] = `[]`; 123 | 124 | exports[`CssMinimizerPlugin should work and do not use memory cache when the "cache" option is "false": warnings 2`] = `[]`; 125 | 126 | exports[`CssMinimizerPlugin should work and generate real content hash: assets 1`] = ` 127 | { 128 | "entry.ee68cb428fa1feaa2c48.072159461f23c2c047e9.0e6907f00e0eaf46a2f9.css": "body{color:red}a{color:blue}", 129 | } 130 | `; 131 | 132 | exports[`CssMinimizerPlugin should work and generate real content hash: errors 1`] = `[]`; 133 | 134 | exports[`CssMinimizerPlugin should work and generate real content hash: warnings 1`] = `[]`; 135 | 136 | exports[`CssMinimizerPlugin should work and show minimized assets in stats: assets 1`] = ` 137 | { 138 | "foo.css": "body{color:red}a{color:blue}", 139 | } 140 | `; 141 | 142 | exports[`CssMinimizerPlugin should work and show minimized assets in stats: errors 1`] = `[]`; 143 | 144 | exports[`CssMinimizerPlugin should work and show minimized assets in stats: warnings 1`] = `[]`; 145 | 146 | exports[`CssMinimizerPlugin should work and use memory cache out of box: assets 1`] = ` 147 | { 148 | "foo.css": "body{color:red}a{color:blue}", 149 | "style-2.css": "a{color:coral}", 150 | "style.css": "a{color:red}", 151 | } 152 | `; 153 | 154 | exports[`CssMinimizerPlugin should work and use memory cache out of box: assets 2`] = ` 155 | { 156 | "foo.css": "body{color:red}a{color:blue}", 157 | "style-2.css": "a{color:coral}", 158 | "style.css": "a{color:red}", 159 | } 160 | `; 161 | 162 | exports[`CssMinimizerPlugin should work and use memory cache out of box: errors 1`] = `[]`; 163 | 164 | exports[`CssMinimizerPlugin should work and use memory cache out of box: errors 2`] = `[]`; 165 | 166 | exports[`CssMinimizerPlugin should work and use memory cache out of box: warnings 1`] = `[]`; 167 | 168 | exports[`CssMinimizerPlugin should work and use memory cache out of box: warnings 2`] = `[]`; 169 | 170 | exports[`CssMinimizerPlugin should work and use memory cache when the "cache" option is "true" and the asset has been changed: assets 1`] = ` 171 | { 172 | "foo.css": "body{color:red}a{color:blue}", 173 | "style-2.css": "a{color:coral}", 174 | "style.css": "a{color:red}", 175 | } 176 | `; 177 | 178 | exports[`CssMinimizerPlugin should work and use memory cache when the "cache" option is "true" and the asset has been changed: assets 2`] = ` 179 | { 180 | "foo.css": "a,body{color:red}a{color:blue}", 181 | "style-2.css": "a{color:coral}", 182 | "style.css": "a{color:red}", 183 | } 184 | `; 185 | 186 | exports[`CssMinimizerPlugin should work and use memory cache when the "cache" option is "true" and the asset has been changed: errors 1`] = `[]`; 187 | 188 | exports[`CssMinimizerPlugin should work and use memory cache when the "cache" option is "true" and the asset has been changed: errors 2`] = `[]`; 189 | 190 | exports[`CssMinimizerPlugin should work and use memory cache when the "cache" option is "true" and the asset has been changed: warnings 1`] = `[]`; 191 | 192 | exports[`CssMinimizerPlugin should work and use memory cache when the "cache" option is "true" and the asset has been changed: warnings 2`] = `[]`; 193 | 194 | exports[`CssMinimizerPlugin should work and use memory cache when the "cache" option is "true": assets 1`] = ` 195 | { 196 | "foo.css": "body{color:red}a{color:blue}", 197 | "style-2.css": "a{color:coral}", 198 | "style.css": "a{color:red}", 199 | } 200 | `; 201 | 202 | exports[`CssMinimizerPlugin should work and use memory cache when the "cache" option is "true": assets 2`] = ` 203 | { 204 | "foo.css": "body{color:red}a{color:blue}", 205 | "style-2.css": "a{color:coral}", 206 | "style.css": "a{color:red}", 207 | } 208 | `; 209 | 210 | exports[`CssMinimizerPlugin should work and use memory cache when the "cache" option is "true": errors 1`] = `[]`; 211 | 212 | exports[`CssMinimizerPlugin should work and use memory cache when the "cache" option is "true": errors 2`] = `[]`; 213 | 214 | exports[`CssMinimizerPlugin should work and use memory cache when the "cache" option is "true": warnings 1`] = `[]`; 215 | 216 | exports[`CssMinimizerPlugin should work and use memory cache when the "cache" option is "true": warnings 2`] = `[]`; 217 | 218 | exports[`CssMinimizerPlugin should work with assets using querystring: entry.css.map?v=test 1`] = `"{"version":3,"file":"entry.css?v=test","mappings":"AAAA,KACE,SACF,CACA,EACE,UACF","sources":["webpack:///entry.css%3Fv=test"],"sourcesContent":["body {\\n color: red;\\n}\\na {\\n color: blue;\\n}\\n"],"names":[],"sourceRoot":""}"`; 219 | 220 | exports[`CssMinimizerPlugin should work with assets using querystring: entry.css?v=test 1`] = ` 221 | "body{color:red}a{color:blue} 222 | /*# sourceMappingURL=entry.css.map?v=test*/" 223 | `; 224 | 225 | exports[`CssMinimizerPlugin should work with child compilation: assets 1`] = ` 226 | { 227 | "entry.css": ".entry{text-align:center}", 228 | } 229 | `; 230 | 231 | exports[`CssMinimizerPlugin should work with child compilation: errors 1`] = `[]`; 232 | 233 | exports[`CssMinimizerPlugin should work with child compilation: warnings 1`] = `[]`; 234 | 235 | exports[`CssMinimizerPlugin should work with empty files: assets 1`] = ` 236 | { 237 | "main.css": "", 238 | } 239 | `; 240 | 241 | exports[`CssMinimizerPlugin should work with empty files: errors 1`] = `[]`; 242 | 243 | exports[`CssMinimizerPlugin should work with empty files: warnings 1`] = `[]`; 244 | 245 | exports[`CssMinimizerPlugin should work with source map and use memory cache when the "cache" option is "true" and the asset has been changed: assets 1`] = ` 246 | { 247 | "foo.css": "body{color:red}a{color:blue} 248 | /*# sourceMappingURL=foo.css.map*/", 249 | "foo.css.map": "{"version":3,"file":"foo.css","mappings":"AAAA,KACE,SACF,CACA,EACE,UACF","sources":["webpack:///./foo.css"],"sourcesContent":["body {\\n color: red;\\n}\\na {\\n color: blue;\\n}"],"names":[],"sourceRoot":""}", 250 | "style-2.css": "a{color:coral}", 251 | "style.css": "a{color:red}", 252 | } 253 | `; 254 | 255 | exports[`CssMinimizerPlugin should work with source map and use memory cache when the "cache" option is "true" and the asset has been changed: assets 2`] = ` 256 | { 257 | "foo.css": "a,body{color:red}a{color:blue} 258 | /*# sourceMappingURL=foo.css.map*/", 259 | "foo.css.map": "{"version":3,"file":"foo.css","mappings":"AAAA,OACE,SACF,CACA,EACE,UACF","sources":["webpack:///./foo.css"],"sourcesContent":["body {\\n color: red;\\n}\\na {\\n color: blue;\\n}"],"names":[],"sourceRoot":""}", 260 | "style-2.css": "a{color:coral}", 261 | "style.css": "a{color:red}", 262 | } 263 | `; 264 | 265 | exports[`CssMinimizerPlugin should work with source map and use memory cache when the "cache" option is "true" and the asset has been changed: errors 1`] = `[]`; 266 | 267 | exports[`CssMinimizerPlugin should work with source map and use memory cache when the "cache" option is "true" and the asset has been changed: errors 2`] = `[]`; 268 | 269 | exports[`CssMinimizerPlugin should work with source map and use memory cache when the "cache" option is "true" and the asset has been changed: warnings 1`] = `[]`; 270 | 271 | exports[`CssMinimizerPlugin should work with source map and use memory cache when the "cache" option is "true" and the asset has been changed: warnings 2`] = `[]`; 272 | 273 | exports[`CssMinimizerPlugin should work with source map and use memory cache when the "cache" option is "true": assets 1`] = ` 274 | { 275 | "foo.css": "body{color:red}a{color:blue} 276 | /*# sourceMappingURL=foo.css.map*/", 277 | "foo.css.map": "{"version":3,"file":"foo.css","mappings":"AAAA,KACE,SACF,CACA,EACE,UACF","sources":["webpack:///./foo.css"],"sourcesContent":["body {\\n color: red;\\n}\\na {\\n color: blue;\\n}"],"names":[],"sourceRoot":""}", 278 | "style-2.css": "a{color:coral}", 279 | "style.css": "a{color:red}", 280 | } 281 | `; 282 | 283 | exports[`CssMinimizerPlugin should work with source map and use memory cache when the "cache" option is "true": assets 2`] = ` 284 | { 285 | "foo.css": "body{color:red}a{color:blue} 286 | /*# sourceMappingURL=foo.css.map*/", 287 | "foo.css.map": "{"version":3,"file":"foo.css","mappings":"AAAA,KACE,SACF,CACA,EACE,UACF","sources":["webpack:///./foo.css"],"sourcesContent":["body {\\n color: red;\\n}\\na {\\n color: blue;\\n}"],"names":[],"sourceRoot":""}", 288 | "style-2.css": "a{color:coral}", 289 | "style.css": "a{color:red}", 290 | } 291 | `; 292 | 293 | exports[`CssMinimizerPlugin should work with source map and use memory cache when the "cache" option is "true": errors 1`] = `[]`; 294 | 295 | exports[`CssMinimizerPlugin should work with source map and use memory cache when the "cache" option is "true": errors 2`] = `[]`; 296 | 297 | exports[`CssMinimizerPlugin should work with source map and use memory cache when the "cache" option is "true": warnings 1`] = `[]`; 298 | 299 | exports[`CssMinimizerPlugin should work with source map and use memory cache when the "cache" option is "true": warnings 2`] = `[]`; 300 | 301 | exports[`CssMinimizerPlugin should work with warnings and use memory cache when the "cache" option is "true" and the asset has been changed: assets 1`] = ` 302 | { 303 | "foo.css": "body { 304 | color: red; 305 | } 306 | a { 307 | color: blue; 308 | } 309 | ", 310 | "style-2.css": "a { color: coral; }", 311 | "style.css": "a { color: red; }", 312 | } 313 | `; 314 | 315 | exports[`CssMinimizerPlugin should work with warnings and use memory cache when the "cache" option is "true" and the asset has been changed: assets 2`] = ` 316 | { 317 | "foo.css": "a { color: red; }body { 318 | color: red; 319 | } 320 | a { 321 | color: blue; 322 | } 323 | ", 324 | "style-2.css": "a { color: coral; }", 325 | "style.css": "a { color: red; }", 326 | } 327 | `; 328 | 329 | exports[`CssMinimizerPlugin should work with warnings and use memory cache when the "cache" option is "true" and the asset has been changed: errors 1`] = ` 330 | [ 331 | "Warning: foo.css from Css Minimizer plugin 332 | [warning-plugin] Warning from foo.css", 333 | "Warning: style-2.css from Css Minimizer plugin 334 | [warning-plugin] Warning from style-2.css", 335 | "Warning: style.css from Css Minimizer plugin 336 | [warning-plugin] Warning from style.css", 337 | ] 338 | `; 339 | 340 | exports[`CssMinimizerPlugin should work with warnings and use memory cache when the "cache" option is "true" and the asset has been changed: errors 2`] = `[]`; 341 | 342 | exports[`CssMinimizerPlugin should work with warnings and use memory cache when the "cache" option is "true" and the asset has been changed: warnings 1`] = `[]`; 343 | 344 | exports[`CssMinimizerPlugin should work with warnings and use memory cache when the "cache" option is "true" and the asset has been changed: warnings 2`] = ` 345 | [ 346 | "Warning: foo.css from Css Minimizer plugin 347 | [warning-plugin] Warning from foo.css", 348 | "Warning: style-2.css from Css Minimizer plugin 349 | [warning-plugin] Warning from style-2.css", 350 | "Warning: style.css from Css Minimizer plugin 351 | [warning-plugin] Warning from style.css", 352 | ] 353 | `; 354 | 355 | exports[`CssMinimizerPlugin should work with warnings and use memory cache when the "cache" option is "true": assets 1`] = ` 356 | { 357 | "foo.css": "body { 358 | color: red; 359 | } 360 | a { 361 | color: blue; 362 | } 363 | ", 364 | "style-2.css": "a { color: coral; }", 365 | "style.css": "a { color: red; }", 366 | } 367 | `; 368 | 369 | exports[`CssMinimizerPlugin should work with warnings and use memory cache when the "cache" option is "true": assets 2`] = ` 370 | { 371 | "foo.css": "body { 372 | color: red; 373 | } 374 | a { 375 | color: blue; 376 | } 377 | ", 378 | "style-2.css": "a { color: coral; }", 379 | "style.css": "a { color: red; }", 380 | } 381 | `; 382 | 383 | exports[`CssMinimizerPlugin should work with warnings and use memory cache when the "cache" option is "true": errors 1`] = ` 384 | [ 385 | "Warning: foo.css from Css Minimizer plugin 386 | [warning-plugin] Warning from foo.css", 387 | "Warning: style-2.css from Css Minimizer plugin 388 | [warning-plugin] Warning from style-2.css", 389 | "Warning: style.css from Css Minimizer plugin 390 | [warning-plugin] Warning from style.css", 391 | ] 392 | `; 393 | 394 | exports[`CssMinimizerPlugin should work with warnings and use memory cache when the "cache" option is "true": errors 2`] = `[]`; 395 | 396 | exports[`CssMinimizerPlugin should work with warnings and use memory cache when the "cache" option is "true": warnings 1`] = `[]`; 397 | 398 | exports[`CssMinimizerPlugin should work with warnings and use memory cache when the "cache" option is "true": warnings 2`] = ` 399 | [ 400 | "Warning: foo.css from Css Minimizer plugin 401 | [warning-plugin] Warning from foo.css", 402 | "Warning: style-2.css from Css Minimizer plugin 403 | [warning-plugin] Warning from style-2.css", 404 | "Warning: style.css from Css Minimizer plugin 405 | [warning-plugin] Warning from style.css", 406 | ] 407 | `; 408 | 409 | exports[`CssMinimizerPlugin should work: assets 1`] = ` 410 | { 411 | "main.css": "body{color:red}a{color:blue}", 412 | } 413 | `; 414 | 415 | exports[`CssMinimizerPlugin should work: errors 1`] = `[]`; 416 | 417 | exports[`CssMinimizerPlugin should work: warnings 1`] = `[]`; 418 | 419 | exports[`CssMinimizerPlugin should write stdout and stderr of workers to stdout and stderr of main process in parallel mode: assets 1`] = ` 420 | { 421 | "one.css": ".minify {};", 422 | "two.css": ".minify {};", 423 | } 424 | `; 425 | 426 | exports[`CssMinimizerPlugin should write stdout and stderr of workers to stdout and stderr of main process in parallel mode: errors 1`] = `[]`; 427 | 428 | exports[`CssMinimizerPlugin should write stdout and stderr of workers to stdout and stderr of main process in parallel mode: process stderr output 1`] = ` 429 | "stderr 430 | stderr 431 | " 432 | `; 433 | 434 | exports[`CssMinimizerPlugin should write stdout and stderr of workers to stdout and stderr of main process in parallel mode: process stdout output 1`] = ` 435 | "stdout 436 | stdout 437 | " 438 | `; 439 | 440 | exports[`CssMinimizerPlugin should write stdout and stderr of workers to stdout and stderr of main process in parallel mode: warnings 1`] = `[]`; 441 | --------------------------------------------------------------------------------