diff --git a/README.md b/README.md index 2901c84..72d46c8 100644 --- a/README.md +++ b/README.md @@ -87,16 +87,15 @@ If you use your own `minify` function please read the `minify` section for handl ## Options -| Name | Type | Default | Description | -| :-----------------------------------------: | :--------------------------------------------: | :----------------------------------: | :---------------------------------------------------------------------------------------------------- | -| **[`test`](#test)** | `String\|RegExp\|Array` | `/\.css(\?.*)?$/i` | Test to match files against. | -| **[`include`](#include)** | `String\|RegExp\|Array` | `undefined` | Files to include. | -| **[`exclude`](#exclude)** | `String\|RegExp\|Array` | `undefined` | Files to exclude. | -| **[`parallel`](#parallel)** | `Boolean\|Number` | `true` | Enable/disable multi-process parallel running. | -| **[`minify`](#minify)** | `Function\|Array` | `CssMinimizerPlugin.cssnanoMinify` | Allows to override default minify function. | -| **[`minimizerOptions`](#minimizeroptions)** | `Object\|Array` | `{ preset: 'default' }` | Cssnano optimisations [options](https://cssnano.co/docs/optimisations). | -| **[`processorOptions`](#processoroptions)** | `Object` | `{ to: assetName, from: assetName }` | Allows to specify option [`processoptions`](https://postcss.org/api/#processoptions) for the cssnano. | -| **[`warningsFilter`](#warningsfilter)** | `Function<(warning, file, source) -> Boolean>` | `() => true` | Allow to filter css-minimizer warnings. | +| Name | Type | Default | Description | +| :-----------------------------------------: | :--------------------------------------------: | :--------------------------------: | :---------------------------------------------------------------------- | +| **[`test`](#test)** | `String\|RegExp\|Array` | `/\.css(\?.*)?$/i` | Test to match files against. | +| **[`include`](#include)** | `String\|RegExp\|Array` | `undefined` | Files to include. | +| **[`exclude`](#exclude)** | `String\|RegExp\|Array` | `undefined` | Files to exclude. | +| **[`parallel`](#parallel)** | `Boolean\|Number` | `true` | Enable/disable multi-process parallel running. | +| **[`minify`](#minify)** | `Function\|Array` | `CssMinimizerPlugin.cssnanoMinify` | Allows to override default minify function. | +| **[`minimizerOptions`](#minimizeroptions)** | `Object\|Array` | `{ preset: 'default' }` | Cssnano optimisations [options](https://cssnano.co/docs/optimisations). | +| **[`warningsFilter`](#warningsfilter)** | `Function<(warning, file, source) -> Boolean>` | `() => true` | Allow to filter css-minimizer warnings. | ### `test` @@ -341,10 +340,10 @@ module.exports = { }; ``` -#### `processorOptions` +##### `processorOptions` (⚠ only cssnano) Type: `Object` -Default: `{ to: assetName, from: assetName }` +Default: `{ from: assetName }` Allows filtering options [`processoptions`](https://postcss.org/api/#processoptions) for the cssnano. The `parser`,` stringifier` and `syntax` can be either a function or a string indicating the module that will be imported. diff --git a/package-lock.json b/package-lock.json index 91e3ee9..e95712f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "3.1.4", "license": "MIT", "dependencies": { + "@types/cssnano": "^4.0.1", "cssnano": "^5.0.6", "jest-worker": "^27.0.2", "postcss": "^8.3.5", @@ -22,6 +23,9 @@ "@babel/preset-env": "^7.15.0", "@commitlint/cli": "^15.0.0", "@commitlint/config-conventional": "^15.0.0", + "@types/clean-css": "^4.2.5", + "@types/csso": "^4.2.0", + "@types/serialize-javascript": "^5.0.1", "@webpack-contrib/eslint-config-webpack": "^3.0.0", "babel-jest": "^27.0.6", "clean-css": "^5.1.5", @@ -47,6 +51,7 @@ "sass-loader": "^12.1.0", "standard-version": "^9.3.0", "sugarss": "^4.0.1", + "typescript": "^4.5.2", "webpack": "^5.50.0" }, "engines": { @@ -3104,6 +3109,60 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/clean-css": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.5.tgz", + "integrity": "sha512-NEzjkGGpbs9S9fgC4abuBvTpVwE3i+Acu9BBod3PUyjDVZcNsGx61b8r2PphR61QGPnn0JHVs5ey6/I4eTrkxw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "source-map": "^0.6.0" + } + }, + "node_modules/@types/css-tree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/css-tree/-/css-tree-1.0.7.tgz", + "integrity": "sha512-Pz+DfVODpQTAV6PwPBK6kzyy7+f6EyPbr1+mYkc1YolJfl2NAJ4wbg0TC/AJPBsqn9jWfyiO19A/sgpvFLfqnw==", + "dev": true + }, + "node_modules/@types/cssnano": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/cssnano/-/cssnano-4.0.1.tgz", + "integrity": "sha512-hGOroxRTBkYl5gSBRJOffhV4+io+Y2bFX1VP7LgKEVHJt/LPPJaWUIuDAz74Vlp7l7hCDZfaDi7iPxwNwuVA4Q==", + "dependencies": { + "postcss": "5 - 7" + } + }, + "node_modules/@types/cssnano/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, + "node_modules/@types/cssnano/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/@types/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-tGMZcJGgeIA67qbbm/ZKUi7ZyiTETEL0X4opRpFzULbIE1Kyt6EfOVSctw4N/WrEJZwmKw0J+1i/OApm6/CAOQ==", + "dev": true, + "dependencies": { + "@types/css-tree": "*" + } + }, "node_modules/@types/eslint": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz", @@ -3203,6 +3262,12 @@ "integrity": "sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA==", "dev": true }, + "node_modules/@types/serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-QqgTcm7IgIt/oWNFQMlpVv5Z3saYtxWK9yFrAUkk3jxvjbqIG835xNNoOYq12mXKQMuWGc+PgOXwXy92eax5BA==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -13474,9 +13539,9 @@ } }, "node_modules/typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -16280,6 +16345,55 @@ "@babel/types": "^7.3.0" } }, + "@types/clean-css": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.5.tgz", + "integrity": "sha512-NEzjkGGpbs9S9fgC4abuBvTpVwE3i+Acu9BBod3PUyjDVZcNsGx61b8r2PphR61QGPnn0JHVs5ey6/I4eTrkxw==", + "dev": true, + "requires": { + "@types/node": "*", + "source-map": "^0.6.0" + } + }, + "@types/css-tree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/css-tree/-/css-tree-1.0.7.tgz", + "integrity": "sha512-Pz+DfVODpQTAV6PwPBK6kzyy7+f6EyPbr1+mYkc1YolJfl2NAJ4wbg0TC/AJPBsqn9jWfyiO19A/sgpvFLfqnw==", + "dev": true + }, + "@types/cssnano": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/cssnano/-/cssnano-4.0.1.tgz", + "integrity": "sha512-hGOroxRTBkYl5gSBRJOffhV4+io+Y2bFX1VP7LgKEVHJt/LPPJaWUIuDAz74Vlp7l7hCDZfaDi7iPxwNwuVA4Q==", + "requires": { + "postcss": "5 - 7" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + } + } + }, + "@types/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-tGMZcJGgeIA67qbbm/ZKUi7ZyiTETEL0X4opRpFzULbIE1Kyt6EfOVSctw4N/WrEJZwmKw0J+1i/OApm6/CAOQ==", + "dev": true, + "requires": { + "@types/css-tree": "*" + } + }, "@types/eslint": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz", @@ -16379,6 +16493,12 @@ "integrity": "sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA==", "dev": true }, + "@types/serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-QqgTcm7IgIt/oWNFQMlpVv5Z3saYtxWK9yFrAUkk3jxvjbqIG835xNNoOYq12mXKQMuWGc+PgOXwXy92eax5BA==", + "dev": true + }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -23921,9 +24041,9 @@ } }, "typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index b3a2431..bed01f3 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "url": "https://opencollective.com/webpack" }, "main": "dist/cjs.js", + "types": "types/cjs.d.ts", "engines": { "node": ">= 12.13.0" }, @@ -19,11 +20,14 @@ "start": "npm run build -- -w", "clean": "del-cli dist", "prebuild": "npm run clean", - "build": "cross-env NODE_ENV=production babel src -d dist --copy-files", + "build:types": "tsc --declaration --emitDeclarationOnly --outDir types && prettier \"types/**/*.ts\" --write && prettier types --write", + "build:code": "cross-env NODE_ENV=production babel src -d dist --copy-files", + "build": "npm-run-all -p \"build:**\"", "commitlint": "commitlint --from=master", "security": "npm audit", "lint:prettier": "prettier \"{**/*,*}.{js,json,md,yml,css,ts}\" --list-different", "lint:js": "eslint --cache .", + "lint:types": "tsc --pretty --noEmit", "lint": "npm-run-all -l -p \"lint:**\"", "test:only": "cross-env NODE_ENV=test jest", "test:watch": "npm run test:only -- --watch", @@ -34,7 +38,8 @@ "release": "standard-version" }, "files": [ - "dist" + "dist", + "types" ], "peerDependencies": { "webpack": "^5.0.0" @@ -51,6 +56,7 @@ } }, "dependencies": { + "@types/cssnano": "^4.0.1", "cssnano": "^5.0.6", "jest-worker": "^27.0.2", "postcss": "^8.3.5", @@ -64,6 +70,9 @@ "@babel/preset-env": "^7.15.0", "@commitlint/cli": "^15.0.0", "@commitlint/config-conventional": "^15.0.0", + "@types/clean-css": "^4.2.5", + "@types/csso": "^4.2.0", + "@types/serialize-javascript": "^5.0.1", "@webpack-contrib/eslint-config-webpack": "^3.0.0", "babel-jest": "^27.0.6", "clean-css": "^5.1.5", @@ -89,6 +98,7 @@ "sass-loader": "^12.1.0", "standard-version": "^9.3.0", "sugarss": "^4.0.1", + "typescript": "^4.5.2", "webpack": "^5.50.0" }, "keywords": [ diff --git a/src/index.js b/src/index.js index 05c3d24..ac27ba9 100644 --- a/src/index.js +++ b/src/index.js @@ -16,36 +16,184 @@ import { import * as schema from "./options.json"; import { minify as minifyFn } from "./minify"; +/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */ +/** @typedef {import("webpack").Compiler} Compiler */ +/** @typedef {import("webpack").Compilation} Compilation */ +/** @typedef {import("webpack").WebpackError} WebpackError */ +/** @typedef {import("jest-worker").Worker} JestWorker */ +/** @typedef {import("source-map").RawSourceMap} RawSourceMap */ +/** @typedef {import("cssnano").CssNanoOptions} CssNanoOptions */ +/** @typedef {import("webpack").Asset} Asset */ +/** @typedef {import("postcss").ProcessOptions} ProcessOptions */ +/** @typedef {import("postcss").Syntax} Syntax */ +/** @typedef {import("postcss").Parser} Parser */ +/** @typedef {import("postcss").Stringifier} Stringifier */ + +/** @typedef {Error & { plugin?: string, text?: string, source?: string } | string} Warning */ + +/** + * @typedef {Object} WarningObject + * @property {string} message + * @property {string} [plugin] + * @property {string} [text] + * @property {number} [line] + * @property {number} [column] + */ + +/** + * @typedef {Object} ErrorObject + * @property {string} message + * @property {number} [line] + * @property {number} [column] + * @property {string} [stack] + */ + +/** + * @typedef {Object} MinimizedResult + * @property {string} code + * @property {RawSourceMap} [map] + * @property {Array} [errors] + * @property {Array} [warnings] + */ + +/** + * @typedef {{ [file: string]: string }} Input + */ + +/** + * @template T + * @callback BasicMinimizerImplementation + * @param {Input} input + * @param {RawSourceMap | undefined} sourceMap + * @param {T} minifyOptions + * @returns {Promise} + */ + +/** + * @template T + * @typedef {T extends any[] ? { [P in keyof T]: BasicMinimizerImplementation; } : BasicMinimizerImplementation} MinimizerImplementation + */ + +/** + * @template T + * @typedef {T extends any[] ? { [P in keyof T]?: T[P]; } : T} MinimizerOptions + */ + +/** + * @template T + * @typedef {Object} InternalOptions + * @property {string} name + * @property {string} input + * @property {RawSourceMap | undefined} inputSourceMap + * @property {{ implementation: MinimizerImplementation>, options: MinimizerOptions> }} minimizer + */ + +/** + * @typedef InternalResult + * @property {Array<{ code: string, map: RawSourceMap | undefined }>} outputs + * @property {Array} warnings + * @property {Array} errors + */ + +/** @typedef {undefined | boolean | number} Parallel */ + +/** @typedef {RegExp | string} Rule */ + +/** @typedef {Rule[] | Rule} Rules */ + +/** @typedef {(warning: Warning | WarningObject | string, file: string, source?: string) => boolean} WarningsFilter */ + +/** + * @typedef {Object} BasePluginOptions + * @property {Rules} [test] + * @property {Rules} [include] + * @property {Rules} [exclude] + * @property {WarningsFilter} [warningsFilter] + * @property {Parallel} [parallel] + */ + +/** + * @template T + * @typedef {JestWorker & { transform: (options: string) => InternalResult, minify: (options: InternalOptions) => InternalResult }} MinimizerWorker + */ + +/** + * @typedef {{ [key: string]: any }} CustomOptions + */ + +/** + * @template T + * @typedef {T extends infer U ? U : CustomOptions} InferDefaultType + */ + +/** + * @typedef{ProcessOptions | { from?: string, to?: string, parser?: string | Syntax | Parser, stringifier?: string | Syntax | Stringifier, syntax?: string | Syntax } } ProcessOptionsExtender + */ + +/** + * @typedef {CssNanoOptions & { processorOptions?: ProcessOptionsExtender }} CssNanoOptionsExtended + */ + +/** + * @template T + * @typedef {T extends CssNanoOptionsExtended ? { minify?: MinimizerImplementation> | undefined, minimizerOptions?: MinimizerOptions> | undefined } : { minify: MinimizerImplementation>, minimizerOptions?: MinimizerOptions> | undefined }} DefinedDefaultMinimizerAndOptions + */ + +/** + * @template T + * @typedef {BasePluginOptions & { minimizer: { implementation: MinimizerImplementation>, options: MinimizerOptions> } }} InternalPluginOptions + */ + const warningRegex = /\s.+:+([0-9]+):+([0-9]+)/; +/** + * @template [T=CssNanoOptionsExtended] + */ class CssMinimizerPlugin { - constructor(options = {}) { - validate(schema, options, { + /** + * @param {BasePluginOptions & DefinedDefaultMinimizerAndOptions} [options] + */ + constructor(options) { + validate(/** @type {Schema} */ (schema), options || {}, { name: "Css Minimizer Plugin", baseDataPath: "options", }); const { - minify = cssnanoMinify, - minimizerOptions, + minify = /** @type {MinimizerImplementation>} */ + (cssnanoMinify), + minimizerOptions = /** @type {MinimizerOptions>} */ + ({}), test = /\.css(\?.*)?$/i, warningsFilter = () => true, parallel = true, include, exclude, - } = options; + } = options || {}; + /** + * @private + * @type {InternalPluginOptions} + */ this.options = { test, warningsFilter, parallel, include, exclude, - minify, - minimizerOptions, + minimizer: { + implementation: + /** @type {MinimizerImplementation>} */ (minify), + options: minimizerOptions, + }, }; } + /** + * @private + * @param {any} input + * @returns {boolean} + */ static isSourceMap(input) { // All required options for `new SourceMapConsumer(...options)` // https://github.com/mozilla/source-map#new-sourcemapconsumerrawsourcemap @@ -58,12 +206,21 @@ class CssMinimizerPlugin { ); } + /** + * @private + * @param {Warning | WarningObject | string} warning + * @param {string} file + * @param {WarningsFilter} [warningsFilter] + * @param {SourceMapConsumer} [sourceMap] + * @param {Compilation["requestShortener"]} [requestShortener] + * @returns {Error & { hideStack?: boolean, file?: string } | undefined} + */ static buildWarning( warning, file, + warningsFilter, sourceMap, - requestShortener, - warningsFilter + requestShortener ) { let warningMessage = typeof warning === "string" @@ -75,11 +232,21 @@ class CssMinimizerPlugin { let source; if (sourceMap) { - const match = warningRegex.exec(warning); + let line; + let column; + + if (typeof warning === "string") { + const match = warningRegex.exec(warning); + + if (match) { + line = +match[1]; + column = +match[2]; + } + } else { + ({ line, column } = /** @type {WarningObject} */ (warning)); + } - if (match) { - const line = +match[1]; - const column = +match[2]; + if (line && column) { const original = sourceMap.originalPositionFor({ line, column, @@ -102,9 +269,12 @@ class CssMinimizerPlugin { } if (warningsFilter && !warningsFilter(warning, file, source)) { - return null; + return; } + /** + * @type {Error & { hideStack?: boolean, file?: string }} + */ const builtWarning = new Error( `${file} from Css Minimizer plugin\n${warningMessage}${ locationMessage ? ` ${locationMessage}` : "" @@ -115,10 +285,22 @@ class CssMinimizerPlugin { builtWarning.hideStack = true; builtWarning.file = file; + // eslint-disable-next-line consistent-return return builtWarning; } + /** + * @private + * @param {Error | ErrorObject | string} error + * @param {string} file + * @param {SourceMapConsumer} [sourceMap] + * @param {Compilation["requestShortener"]} [requestShortener] + * @returns {Error} + */ static buildError(error, file, sourceMap, requestShortener) { + /** + * @type {Error & { file?: string }} + */ let builtError; if (typeof error === "string") { @@ -128,13 +310,15 @@ class CssMinimizerPlugin { return builtError; } - if (error.line) { + if ( + /** @type {ErrorObject} */ (error).line && + /** @type {ErrorObject} */ (error).column + ) { + const { line, column } = + /** @type {ErrorObject & { line: number, column: number }} */ (error); + const original = - sourceMap && - sourceMap.originalPositionFor({ - line: error.line, - column: error.column, - }); + sourceMap && sourceMap.originalPositionFor({ line, column }); if (original && original.source && requestShortener) { builtError = new Error( @@ -142,7 +326,7 @@ class CssMinimizerPlugin { error.message } [${requestShortener.shorten(original.source)}:${original.line},${ original.column - }][${file}:${error.line},${error.column}]${ + }][${file}:${line},${column}]${ error.stack ? `\n${error.stack.split("\n").slice(1).join("\n")}` : "" @@ -154,9 +338,9 @@ class CssMinimizerPlugin { } builtError = new Error( - `${file} from Css Minimizer plugin\n${error.message} [${file}:${ - error.line - },${error.column}]${ + `${file} from Css Minimizer plugin\n${ + error.message + } [${file}:${line},${column}]${ error.stack ? `\n${error.stack.split("\n").slice(1).join("\n")}` : "" }` ); @@ -182,6 +366,11 @@ class CssMinimizerPlugin { return builtError; } + /** + * @private + * @param {Parallel} parallel + * @returns {number} + */ static getAvailableNumberOfCores(parallel) { // In some cases cpus() returns undefined // https://github.com/nodejs/node/issues/19022 @@ -192,13 +381,21 @@ class CssMinimizerPlugin { : Math.min(Number(parallel) || 0, cpus.length - 1); } + /** + * @private + * @param {Compiler} compiler + * @param {Compilation} compilation + * @param {Record} assets + * @param {{availableNumberOfCores: number}} optimizeOptions + * @returns {Promise} + */ async optimize(compiler, compilation, assets, optimizeOptions) { const cache = compilation.getCache("CssMinimizerWebpackPlugin"); let numberOfAssetsForMinify = 0; const assetsForMinify = await Promise.all( Object.keys(typeof assets === "undefined" ? compilation.assets : assets) .filter((name) => { - const { info } = compilation.getAsset(name); + const { info } = /** @type {Asset} */ (compilation.getAsset(name)); if ( // Skip double minimize assets from child compilation @@ -220,7 +417,9 @@ class CssMinimizerPlugin { return true; }) .map(async (name) => { - const { info, source } = compilation.getAsset(name); + const { info, source } = /** @type {Asset} */ ( + compilation.getAsset(name) + ); const eTag = cache.getLazyHashedEtag(source); const cacheItem = cache.getItemCache(name, eTag); @@ -238,8 +437,11 @@ class CssMinimizerPlugin { return; } + /** @type {undefined | (() => MinimizerWorker)} */ let getWorker; + /** @type {undefined | MinimizerWorker} */ let initializedWorker; + /** @type {undefined | number} */ let numberOfWorkers; if (optimizeOptions.availableNumberOfCores > 0) { @@ -254,10 +456,12 @@ class CssMinimizerPlugin { return initializedWorker; } - initializedWorker = new Worker(require.resolve("./minify"), { - numWorkers: numberOfWorkers, - enableWorkerThreads: true, - }); + initializedWorker = /** @type {MinimizerWorker} */ ( + new Worker(require.resolve("./minify"), { + numWorkers: numberOfWorkers, + enableWorkerThreads: true, + }) + ); // https://github.com/facebook/jest/issues/8872#issuecomment-524822081 const workerStdout = initializedWorker.getStdout(); @@ -286,6 +490,7 @@ class CssMinimizerPlugin { if (!output) { let input; + /** @type {RawSourceMap | undefined} */ let inputSourceMap; const { source: sourceFromInputSource, map } = @@ -294,12 +499,14 @@ class CssMinimizerPlugin { input = sourceFromInputSource; if (map) { - if (CssMinimizerPlugin.isSourceMap(map)) { - inputSourceMap = map; - } else { + if (!CssMinimizerPlugin.isSourceMap(map)) { compilation.warnings.push( - new Error(`${name} contains invalid source map`) + /** @type {WebpackError} */ ( + new Error(`${name} contains invalid source map`) + ) ); + } else { + inputSourceMap = /** @type {RawSourceMap} */ (map); } } @@ -307,12 +514,17 @@ class CssMinimizerPlugin { input = input.toString(); } + /** + * @type {InternalOptions} + */ const options = { name, input, inputSourceMap, - minify: this.options.minify, - minifyOptions: this.options.minimizerOptions, + minimizer: { + implementation: this.options.minimizer.implementation, + options: this.options.minimizer.options, + }, }; let result; @@ -326,15 +538,19 @@ class CssMinimizerPlugin { inputSourceMap && CssMinimizerPlugin.isSourceMap(inputSourceMap); compilation.errors.push( - CssMinimizerPlugin.buildError( - error, - name, - hasSourceMap - ? new SourceMapConsumer(inputSourceMap) - : // eslint-disable-next-line no-undefined - undefined, - // eslint-disable-next-line no-undefined - hasSourceMap ? compilation.requestShortener : undefined + /** @type {WebpackError} */ ( + CssMinimizerPlugin.buildError( + /** @type {any} */ (error), + name, + hasSourceMap + ? new SourceMapConsumer( + /** @type {RawSourceMap} */ (inputSourceMap) + ) + : // eslint-disable-next-line no-undefined + undefined, + // eslint-disable-next-line no-undefined + hasSourceMap ? compilation.requestShortener : undefined + ) ) ); @@ -380,7 +596,9 @@ class CssMinimizerPlugin { error, name, hasSourceMap - ? new SourceMapConsumer(inputSourceMap) + ? new SourceMapConsumer( + /** @type {RawSourceMap} */ (inputSourceMap) + ) : // eslint-disable-next-line no-undefined undefined, // eslint-disable-next-line no-undefined @@ -398,13 +616,15 @@ class CssMinimizerPlugin { const buildWarning = CssMinimizerPlugin.buildWarning( warning, name, + this.options.warningsFilter, hasSourceMap - ? new SourceMapConsumer(inputSourceMap) + ? new SourceMapConsumer( + /** @type {RawSourceMap} */ (inputSourceMap) + ) : // eslint-disable-next-line no-undefined undefined, // eslint-disable-next-line no-undefined - hasSourceMap ? compilation.requestShortener : undefined, - this.options.warningsFilter + hasSourceMap ? compilation.requestShortener : undefined ); if (buildWarning) { @@ -441,7 +661,7 @@ class CssMinimizerPlugin { const limit = getWorker && numberOfAssetsForMinify > 0 - ? numberOfWorkers + ? /** @type {number} */ (numberOfWorkers) : scheduledTasks.length; await throttleAll(limit, scheduledTasks); @@ -451,6 +671,10 @@ class CssMinimizerPlugin { } } + /** + * @param {Compiler} compiler + * @returns {void} + */ apply(compiler) { const pluginName = this.constructor.name; const availableNumberOfCores = CssMinimizerPlugin.getAvailableNumberOfCores( @@ -478,7 +702,11 @@ class CssMinimizerPlugin { "css-minimizer-webpack-plugin", (minimized, { green, formatFlag }) => // eslint-disable-next-line no-undefined - minimized ? green(formatFlag("minimized")) : "" + minimized + ? /** @type {Function} */ (green)( + /** @type {Function} */ (formatFlag)("minimized") + ) + : "" ); }); }); diff --git a/src/minify.js b/src/minify.js index c1e2938..8ac5915 100644 --- a/src/minify.js +++ b/src/minify.js @@ -1,16 +1,27 @@ +/** @typedef {import("./index.js").MinimizedResult} MinimizedResult */ +/** @typedef {import("source-map").RawSourceMap} RawSourceMap */ +/** @typedef {import("./index.js").InternalResult} InternalResult */ + +/** + * @template T + * @param {import("./index.js").InternalOptions} options + * @returns {Promise} + */ const minify = async (options) => { - const minifyFns = - typeof options.minify === "function" ? [options.minify] : options.minify; + const minifyFns = Array.isArray(options.minimizer.implementation) + ? options.minimizer.implementation + : [options.minimizer.implementation]; + /** @type {InternalResult} */ const result = { outputs: [], warnings: [], errors: [] }; let needSourceMap = false; for (let i = 0; i <= minifyFns.length - 1; i++) { const minifyFn = minifyFns[i]; - const minifyOptions = Array.isArray(options.minifyOptions) - ? options.minifyOptions[i] - : options.minifyOptions; + const minifyOptions = Array.isArray(options.minimizer.options) + ? options.minimizer.options[i] + : options.minimizer.options; const prevResult = result.outputs.length > 0 ? result.outputs[result.outputs.length - 1] @@ -51,6 +62,10 @@ const minify = async (options) => { return result; }; +/** + * @param {string} options + * @returns {Promise} + */ async function transform(options) { // 'use strict' => this === undefined (Clean Scope) // Safer for possible security issues, albeit not critical at all here @@ -64,13 +79,7 @@ async function transform(options) { `'use strict'\nreturn ${options}` )(exports, require, module, __filename, __dirname); - const result = await minify(evaluatedOptions); - - if (result.error) { - throw result.error; - } else { - return result; - } + return minify(evaluatedOptions); } module.exports.minify = minify; diff --git a/src/utils.js b/src/utils.js index d4b6f4c..fe73c44 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,26 @@ +/** @typedef {import("./index.js").Input} Input */ +/** @typedef {import("source-map").RawSourceMap} RawSourceMap */ +/** @typedef {import("source-map").SourceMapGenerator} SourceMapGenerator */ +/** @typedef {import("./index.js").MinimizedResult} MinimizedResult */ +/** @typedef {import("./index.js").CustomOptions} CustomOptions */ +/** @typedef {import("postcss").ProcessOptions} ProcessOptions */ +/** @typedef {import("postcss").Postcss} Postcss */ +/** @typedef {import("cssnano")} CssNano */ + const notSettled = Symbol(`not-settled`); +/** + * @template T + * @typedef {() => Promise} Task + */ + +/** + * Run tasks with limited concurency. + * @template T + * @param {number} limit - Limit of tasks that run at once. + * @param {Task[]} tasks - List of tasks to run. + * @returns {Promise} A promise that fulfills to an array of the results + */ function throttleAll(limit, tasks) { if (!Number.isInteger(limit) || limit < 1) { throw new TypeError( @@ -32,6 +53,9 @@ function throttleAll(limit, tasks) { const [index, task] = value; + /** + * @param {T} x + */ const onFulfilled = (x) => { result[index] = x; next(); @@ -45,11 +69,22 @@ function throttleAll(limit, tasks) { } /* istanbul ignore next */ +/** + * @param {Input} input + * @param {RawSourceMap | undefined} sourceMap + * @param {CustomOptions} minimizerOptions + * @return {Promise} + */ async function cssnanoMinify( input, - inputSourceMap, + sourceMap, minimizerOptions = { preset: "default" } ) { + /** + * @template T + * @param {string} module + * @returns {Promise} + */ const load = async (module) => { let exports; @@ -68,7 +103,11 @@ async function cssnanoMinify( importESM = null; } - if (requireError.code === "ERR_REQUIRE_ESM" && importESM) { + if ( + /** @type {Error & {code: string}} */ + (requireError).code === "ERR_REQUIRE_ESM" && + importESM + ) { exports = await importESM(module); return exports.default; @@ -79,6 +118,7 @@ async function cssnanoMinify( }; const [[name, code]] = Object.entries(input); + /** @type {ProcessOptions} */ const postcssOptions = { from: name, ...minimizerOptions.processorOptions, @@ -89,7 +129,9 @@ async function cssnanoMinify( postcssOptions.parser = await load(postcssOptions.parser); } catch (error) { throw new Error( - `Loading PostCSS "${postcssOptions.parser}" parser failed: ${error.message}\n\n(@${name})` + `Loading PostCSS "${postcssOptions.parser}" parser failed: ${ + /** @type {Error} */ (error).message + }\n\n(@${name})` ); } } @@ -99,7 +141,9 @@ async function cssnanoMinify( postcssOptions.stringifier = await load(postcssOptions.stringifier); } catch (error) { throw new Error( - `Loading PostCSS "${postcssOptions.stringifier}" stringifier failed: ${error.message}\n\n(@${name})` + `Loading PostCSS "${postcssOptions.stringifier}" stringifier failed: ${ + /** @type {Error} */ (error).message + }\n\n(@${name})` ); } } @@ -109,19 +153,24 @@ async function cssnanoMinify( postcssOptions.syntax = await load(postcssOptions.syntax); } catch (error) { throw new Error( - `Loading PostCSS "${postcssOptions.syntax}" syntax failed: ${error.message}\n\n(@${name})` + `Loading PostCSS "${postcssOptions.syntax}" syntax failed: ${ + /** @type {Error} */ (error).message + }\n\n(@${name})` ); } } - if (inputSourceMap) { + if (sourceMap) { postcssOptions.map = { annotation: false }; } + /** @type {Postcss} */ // eslint-disable-next-line global-require - const postcss = require("postcss"); + const postcss = require("postcss").default; // eslint-disable-next-line global-require const cssnano = require("cssnano"); + // @ts-ignore + // Types are broken const result = await postcss([cssnano(minimizerOptions)]).process( code, postcssOptions @@ -129,59 +178,96 @@ async function cssnanoMinify( return { code: result.css, - map: result.map && result.map.toString(), + map: result.map + ? result.map.toJSON() + : // eslint-disable-next-line no-undefined + undefined, warnings: result.warnings().map(String), }; } /* istanbul ignore next */ -async function cssoMinify(input, inputSourceMap, minimizerOptions) { +/** + * @param {Input} input + * @param {RawSourceMap | undefined} sourceMap + * @param {CustomOptions} minimizerOptions + * @return {Promise} + */ +async function cssoMinify(input, sourceMap, minimizerOptions) { // eslint-disable-next-line global-require,import/no-extraneous-dependencies const csso = require("csso"); const [[filename, code]] = Object.entries(input); const result = csso.minify(code, { filename, - sourceMap: Boolean(inputSourceMap), + sourceMap: Boolean(sourceMap), ...minimizerOptions, }); return { code: result.css, - map: result.map && result.map.toJSON(), + map: result.map + ? /** @type {SourceMapGenerator & { toJSON(): RawSourceMap }} */ + (result.map).toJSON() + : // eslint-disable-next-line no-undefined + undefined, }; } /* istanbul ignore next */ -async function cleanCssMinify(input, inputSourceMap, minimizerOptions) { +/** + * @param {Input} input + * @param {RawSourceMap | undefined} sourceMap + * @param {CustomOptions} minimizerOptions + * @return {Promise} + */ +async function cleanCssMinify(input, sourceMap, minimizerOptions) { // eslint-disable-next-line global-require,import/no-extraneous-dependencies const CleanCSS = require("clean-css"); const [[name, code]] = Object.entries(input); const result = await new CleanCSS({ - sourceMap: Boolean(inputSourceMap), + sourceMap: Boolean(sourceMap), ...minimizerOptions, + returnPromise: true, }).minify({ [name]: { styles: code } }); - const sourceMap = result.sourceMap && result.sourceMap.toJSON(); + const generatedSourceMap = + result.sourceMap && + /** @type {SourceMapGenerator & { toJSON(): RawSourceMap }} */ + (result.sourceMap).toJSON(); // workaround for source maps on windows - if (sourceMap) { + if (generatedSourceMap) { // eslint-disable-next-line global-require const isWindowsPathSep = require("path").sep === "\\"; - sourceMap.sources = sourceMap.sources.map((item) => - isWindowsPathSep ? item.replace(/\\/g, "/") : item + generatedSourceMap.sources = generatedSourceMap.sources.map( + /** + * @param {string} item + * @returns {string} + */ + (item) => (isWindowsPathSep ? item.replace(/\\/g, "/") : item) ); } return { code: result.styles, - map: sourceMap, + map: generatedSourceMap, warnings: result.warnings, }; } /* istanbul ignore next */ +/** + * @param {Input} input + * @param {RawSourceMap | undefined} sourceMap + * @param {CustomOptions} minimizerOptions + * @return {Promise} + */ async function esbuildMinify(input, sourceMap, minimizerOptions) { + /** + * @param {import("esbuild").TransformOptions} [esbuildOptions={}] + * @returns {import("esbuild").TransformOptions} + */ const buildEsbuildOptions = (esbuildOptions = {}) => { // Need deep copy objects to avoid https://github.com/terser/terser/issues/366 return { @@ -214,14 +300,22 @@ async function esbuildMinify(input, sourceMap, minimizerOptions) { return { code: result.code, // eslint-disable-next-line no-undefined - map: result.map ? result.map : undefined, + map: result.map ? JSON.parse(result.map) : undefined, warnings: result.warnings.length > 0 ? result.warnings.map((item) => { return { source: item.location && item.location.file, - line: item.location && item.location.line, - column: item.location && item.location.column, + // eslint-disable-next-line no-undefined + line: + item.location && item.location.line + ? item.location.line + : undefined, + // eslint-disable-next-line no-undefined + column: + item.location && item.location.column + ? item.location.column + : undefined, plugin: item.pluginName, message: `${item.text}${ item.detail ? `\nDetails:\n${item.detail}` : "" diff --git a/test/CssMinimizerPlugin.test.js b/test/CssMinimizerPlugin.test.js index 499de9d..ff4d516 100644 --- a/test/CssMinimizerPlugin.test.js +++ b/test/CssMinimizerPlugin.test.js @@ -191,6 +191,7 @@ describe("CssMinimizerPlugin", () => { CssMinimizerPlugin.buildWarning( "Warning test.css:1:1", "test.css", + undefined, new SourceMapConsumer(rawSourceMap) ) ).toMatchSnapshot(); @@ -198,6 +199,7 @@ describe("CssMinimizerPlugin", () => { CssMinimizerPlugin.buildWarning( "Warning test.css:1:1", "test.css", + undefined, new SourceMapConsumer(rawSourceMap), new RequestShortener("/example.com/www/js/") ) @@ -206,18 +208,32 @@ describe("CssMinimizerPlugin", () => { CssMinimizerPlugin.buildWarning( "Warning test.css:1:1", "test.css", + () => true, new SourceMapConsumer(rawSourceMap), - new RequestShortener("/example.com/www/js/"), - () => true + new RequestShortener("/example.com/www/js/") ) ).toMatchSnapshot(); expect( CssMinimizerPlugin.buildWarning( "Warning test.css:1:1", "test.css", + () => false, new SourceMapConsumer(rawSourceMap), - new RequestShortener("/example.com/www/js/"), - () => false + new RequestShortener("/example.com/www/js/") + ) + ).toMatchSnapshot(); + expect( + CssMinimizerPlugin.buildWarning( + { + message: "warning", + line: 1, + column: 1, + }, + "test.css", + // eslint-disable-next-line no-undefined + undefined, + new SourceMapConsumer(rawSourceMap), + new RequestShortener("/example.com/www/js/") ) ).toMatchSnapshot(); }); diff --git a/test/__snapshots__/CssMinimizerPlugin.test.js.snap b/test/__snapshots__/CssMinimizerPlugin.test.js.snap index 4411694..0b1e421 100644 --- a/test/__snapshots__/CssMinimizerPlugin.test.js.snap +++ b/test/__snapshots__/CssMinimizerPlugin.test.js.snap @@ -45,7 +45,12 @@ exports[`CssMinimizerPlugin buildWarning method 5`] = ` Warning http://example.com/www/js/one.css:1:1] `; -exports[`CssMinimizerPlugin buildWarning method 6`] = `null`; +exports[`CssMinimizerPlugin buildWarning method 6`] = `undefined`; + +exports[`CssMinimizerPlugin buildWarning method 7`] = ` +[Warning: test.css from Css Minimizer plugin +warning http://example.com/www/js/one.css:1:1] +`; exports[`CssMinimizerPlugin should build error: error 1`] = ` Array [ diff --git a/test/__snapshots__/minify-option.test.js.snap b/test/__snapshots__/minify-option.test.js.snap index c6ce85f..aab73dd 100644 --- a/test/__snapshots__/minify-option.test.js.snap +++ b/test/__snapshots__/minify-option.test.js.snap @@ -272,9 +272,9 @@ exports[`"minify" option should work with "CssMinimizerPlugin.esbuildMinify" min exports[`"minify" option should work with "CssMinimizerPlugin.esbuildMinify" minifier and emit warnings: warning 1`] = ` Array [ "Warning: foo.css from Css Minimizer plugin -Expected identifier but found whitespace", +Expected identifier but found whitespace webpack://./wrong-calc.css:1:0", "Warning: foo.css from Css Minimizer plugin -Unexpected \\"calc(\\"", +Unexpected \\"calc(\\" webpack://./wrong-calc.css:1:0", ] `; diff --git a/test/__snapshots__/worker.test.js.snap b/test/__snapshots__/worker.test.js.snap index 558be60..06f2af4 100644 --- a/test/__snapshots__/worker.test.js.snap +++ b/test/__snapshots__/worker.test.js.snap @@ -12,7 +12,19 @@ Object { "outputs": Array [ Object { "code": ".foo{color:red}.bar{color:coral}", - "map": "{\\"version\\":3,\\"sources\\":[\\"entry.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,KAAK,SAAU,CACf,KAAK,WAAY\\",\\"file\\":\\"entry.css\\",\\"sourcesContent\\":[\\".foo{color:red;}\\\\n.bar{color:coral;}\\"]}", + "map": Object { + "file": "entry.css", + "mappings": "AAAA,KAAK,SAAU,CACf,KAAK,WAAY", + "names": Array [], + "sources": Array [ + "entry.css", + ], + "sourcesContent": Array [ + ".foo{color:red;} +.bar{color:coral;}", + ], + "version": 3, + }, }, ], "warnings": Array [], @@ -25,7 +37,19 @@ Object { "outputs": Array [ Object { "code": ".foo{color:red}.bar{color:coral}", - "map": "{\\"version\\":3,\\"sources\\":[\\"entry.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,KAAK,SAAU,CACf,KAAK,WAAY\\",\\"file\\":\\"entry.css\\",\\"sourcesContent\\":[\\".foo{color:red;}\\\\n.bar{color:coral;}\\"]}", + "map": Object { + "file": "entry.css", + "mappings": "AAAA,KAAK,SAAU,CACf,KAAK,WAAY", + "names": Array [], + "sources": Array [ + "entry.css", + ], + "sourcesContent": Array [ + ".foo{color:red;} +.bar{color:coral;}", + ], + "version": 3, + }, }, ], "warnings": Array [], diff --git a/test/worker.test.js b/test/worker.test.js index 53a73a6..5522875 100644 --- a/test/worker.test.js +++ b/test/worker.test.js @@ -19,8 +19,10 @@ describe("worker", () => { file: "x", sourcesContent: [".foo{color:red;}", ".bar{color:coral;}"], }, - minimizerOptions: { discardComments: false }, - minify: CssMinimizerPlugin.cssnanoMinify, + minimizer: { + implementation: CssMinimizerPlugin.cssnanoMinify, + options: { discardComments: false }, + }, }; const result = await transform(serialize(options)); @@ -31,7 +33,6 @@ describe("worker", () => { const options = { name: "entry.css", input: ".foo{color:red;}\n.bar{color:coral;}", - minimizerOptions: { discardComments: false }, inputSourceMap: { version: 3, sources: ["foo.css", "bar.css"], @@ -40,7 +41,10 @@ describe("worker", () => { file: "x", sourcesContent: [".foo{color:red;}", ".bar{color:coral;}"], }, - minify: CssMinimizerPlugin.cssnanoMinify, + minimizer: { + implementation: CssMinimizerPlugin.cssnanoMinify, + options: { discardComments: false }, + }, }; const result = await transform(serialize(options)); @@ -51,9 +55,11 @@ describe("worker", () => { const options = { name: "entry.css", input: ".foo{color:red;}\n.bar{color:coral;}", - minimizerOptions: { discardComments: false }, - minify: () => { - return { code: ".minify {};" }; + minimizer: { + implementation: () => { + return { code: ".minify {};" }; + }, + options: { discardComments: false }, }, }; const result = await transform(serialize(options)); @@ -65,8 +71,10 @@ describe("worker", () => { const options = { name: "entry.css", input: false, - minimizerOptions: { preset: "default" }, - minify: CssMinimizerPlugin.cssnanoMinify, + minimizer: { + implementation: CssMinimizerPlugin.cssnanoMinify, + options: { preset: "default" }, + }, }; try { diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fb41806 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "esnext", + "moduleResolution": "node", + "allowJs": true, + "checkJs": true, + "strict": true, + "types": ["node"], + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + }, + "include": ["./src/**/*"] +} diff --git a/types/cjs.d.ts b/types/cjs.d.ts new file mode 100644 index 0000000..2cb80fb --- /dev/null +++ b/types/cjs.d.ts @@ -0,0 +1,3 @@ +declare const _exports: typeof plugin.default; +export = _exports; +import plugin = require("./index"); diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 0000000..37eb142 --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,195 @@ +export default CssMinimizerPlugin; +export type Schema = import("schema-utils/declarations/validate").Schema; +export type Compiler = import("webpack").Compiler; +export type Compilation = import("webpack").Compilation; +export type WebpackError = import("webpack").WebpackError; +export type JestWorker = import("jest-worker").Worker; +export type RawSourceMap = import("source-map").RawSourceMap; +export type CssNanoOptions = import("cssnano").CssNanoOptions; +export type Asset = import("webpack").Asset; +export type ProcessOptions = import("postcss").ProcessOptions; +export type Syntax = import("postcss").Syntax; +export type Parser = import("postcss").Parser; +export type Stringifier = import("postcss").Stringifier; +export type Warning = + | (Error & { + plugin?: string; + text?: string; + source?: string; + }) + | string; +export type WarningObject = { + message: string; + plugin?: string | undefined; + text?: string | undefined; + line?: number | undefined; + column?: number | undefined; +}; +export type ErrorObject = { + message: string; + line?: number | undefined; + column?: number | undefined; + stack?: string | undefined; +}; +export type MinimizedResult = { + code: string; + map?: import("source-map").RawSourceMap | undefined; + errors?: (string | Error | ErrorObject)[] | undefined; + warnings?: (Warning | WarningObject)[] | undefined; +}; +export type Input = { + [file: string]: string; +}; +export type BasicMinimizerImplementation = ( + input: Input, + sourceMap: RawSourceMap | undefined, + minifyOptions: T +) => Promise; +export type MinimizerImplementation = T extends any[] + ? { [P in keyof T]: BasicMinimizerImplementation } + : BasicMinimizerImplementation; +export type MinimizerOptions = T extends any[] + ? { [P in keyof T]?: T[P] | undefined } + : T; +export type InternalOptions = { + name: string; + input: string; + inputSourceMap: RawSourceMap | undefined; + minimizer: { + implementation: MinimizerImplementation>; + options: MinimizerOptions>; + }; +}; +export type InternalResult = { + outputs: Array<{ + code: string; + map: RawSourceMap | undefined; + }>; + warnings: Array; + errors: Array; +}; +export type Parallel = undefined | boolean | number; +export type Rule = RegExp | string; +export type Rules = Rule[] | Rule; +export type WarningsFilter = ( + warning: Warning | WarningObject | string, + file: string, + source?: string | undefined +) => boolean; +export type BasePluginOptions = { + test?: Rules | undefined; + include?: Rules | undefined; + exclude?: Rules | undefined; + warningsFilter?: WarningsFilter | undefined; + parallel?: Parallel; +}; +export type MinimizerWorker = Worker & { + transform: (options: string) => InternalResult; + minify: (options: InternalOptions) => InternalResult; +}; +export type CustomOptions = { + [key: string]: any; +}; +export type InferDefaultType = T extends infer U ? U : CustomOptions; +export type ProcessOptionsExtender = + | ProcessOptions + | { + from?: string; + to?: string; + parser?: string | Syntax | Parser; + stringifier?: string | Syntax | Stringifier; + syntax?: string | Syntax; + }; +export type CssNanoOptionsExtended = CssNanoOptions & { + processorOptions?: ProcessOptionsExtender; +}; +export type DefinedDefaultMinimizerAndOptions = + T extends CssNanoOptionsExtended + ? { + minify?: MinimizerImplementation> | undefined; + minimizerOptions?: MinimizerOptions> | undefined; + } + : { + minify: MinimizerImplementation>; + minimizerOptions?: MinimizerOptions> | undefined; + }; +export type InternalPluginOptions = BasePluginOptions & { + minimizer: { + implementation: MinimizerImplementation>; + options: MinimizerOptions>; + }; +}; +/** + * @template [T=CssNanoOptionsExtended] + */ +declare class CssMinimizerPlugin { + /** + * @private + * @param {any} input + * @returns {boolean} + */ + private static isSourceMap; + /** + * @private + * @param {Warning | WarningObject | string} warning + * @param {string} file + * @param {WarningsFilter} [warningsFilter] + * @param {SourceMapConsumer} [sourceMap] + * @param {Compilation["requestShortener"]} [requestShortener] + * @returns {Error & { hideStack?: boolean, file?: string } | undefined} + */ + private static buildWarning; + /** + * @private + * @param {Error | ErrorObject | string} error + * @param {string} file + * @param {SourceMapConsumer} [sourceMap] + * @param {Compilation["requestShortener"]} [requestShortener] + * @returns {Error} + */ + private static buildError; + /** + * @private + * @param {Parallel} parallel + * @returns {number} + */ + private static getAvailableNumberOfCores; + /** + * @param {BasePluginOptions & DefinedDefaultMinimizerAndOptions} [options] + */ + constructor( + options?: + | (BasePluginOptions & DefinedDefaultMinimizerAndOptions) + | undefined + ); + /** + * @private + * @type {InternalPluginOptions} + */ + private options; + /** + * @private + * @param {Compiler} compiler + * @param {Compilation} compilation + * @param {Record} assets + * @param {{availableNumberOfCores: number}} optimizeOptions + * @returns {Promise} + */ + private optimize; + /** + * @param {Compiler} compiler + * @returns {void} + */ + apply(compiler: Compiler): void; +} +declare namespace CssMinimizerPlugin { + export { cssnanoMinify }; + export { cssoMinify }; + export { cleanCssMinify }; + export { esbuildMinify }; +} +import { Worker } from "jest-worker"; +import { cssnanoMinify } from "./utils"; +import { cssoMinify } from "./utils"; +import { cleanCssMinify } from "./utils"; +import { esbuildMinify } from "./utils"; diff --git a/types/minify.d.ts b/types/minify.d.ts new file mode 100644 index 0000000..9d2f4ff --- /dev/null +++ b/types/minify.d.ts @@ -0,0 +1,19 @@ +export type MinimizedResult = import("./index.js").MinimizedResult; +export type RawSourceMap = import("source-map").RawSourceMap; +export type InternalResult = import("./index.js").InternalResult; +/** @typedef {import("./index.js").MinimizedResult} MinimizedResult */ +/** @typedef {import("source-map").RawSourceMap} RawSourceMap */ +/** @typedef {import("./index.js").InternalResult} InternalResult */ +/** + * @template T + * @param {import("./index.js").InternalOptions} options + * @returns {Promise} + */ +export function minify( + options: import("./index.js").InternalOptions +): Promise; +/** + * @param {string} options + * @returns {Promise} + */ +export function transform(options: string): Promise; diff --git a/types/utils.d.ts b/types/utils.d.ts new file mode 100644 index 0000000..67395ff --- /dev/null +++ b/types/utils.d.ts @@ -0,0 +1,67 @@ +export type Task = () => Promise; +export type Input = import("./index.js").Input; +export type RawSourceMap = import("source-map").RawSourceMap; +export type SourceMapGenerator = import("source-map").SourceMapGenerator; +export type MinimizedResult = import("./index.js").MinimizedResult; +export type CustomOptions = import("./index.js").CustomOptions; +export type ProcessOptions = import("postcss").ProcessOptions; +export type Postcss = import("postcss").Postcss; +export type CssNano = import("cssnano/node_modules/postcss").Plugin< + import("cssnano").CssNanoOptions +>; +/** + * @template T + * @typedef {() => Promise} Task + */ +/** + * Run tasks with limited concurency. + * @template T + * @param {number} limit - Limit of tasks that run at once. + * @param {Task[]} tasks - List of tasks to run. + * @returns {Promise} A promise that fulfills to an array of the results + */ +export function throttleAll(limit: number, tasks: Task[]): Promise; +/** + * @param {Input} input + * @param {RawSourceMap | undefined} sourceMap + * @param {CustomOptions} minimizerOptions + * @return {Promise} + */ +export function cssnanoMinify( + input: Input, + sourceMap: RawSourceMap | undefined, + minimizerOptions: CustomOptions +): Promise; +/** + * @param {Input} input + * @param {RawSourceMap | undefined} sourceMap + * @param {CustomOptions} minimizerOptions + * @return {Promise} + */ +export function cssoMinify( + input: Input, + sourceMap: RawSourceMap | undefined, + minimizerOptions: CustomOptions +): Promise; +/** + * @param {Input} input + * @param {RawSourceMap | undefined} sourceMap + * @param {CustomOptions} minimizerOptions + * @return {Promise} + */ +export function cleanCssMinify( + input: Input, + sourceMap: RawSourceMap | undefined, + minimizerOptions: CustomOptions +): Promise; +/** + * @param {Input} input + * @param {RawSourceMap | undefined} sourceMap + * @param {CustomOptions} minimizerOptions + * @return {Promise} + */ +export function esbuildMinify( + input: Input, + sourceMap: RawSourceMap | undefined, + minimizerOptions: CustomOptions +): Promise;