From 67338f0d92bf4adc5c49aeabb969b747bf877dd9 Mon Sep 17 00:00:00 2001 From: Alexander Akait <4567934+alexander-akait@users.noreply.github.com> Date: Mon, 6 Dec 2021 21:05:44 +0300 Subject: [PATCH] feat: added types and refactor (#43) --- package-lock.json | 37 +++- package.json | 12 +- src/index.js | 232 ++++++++++++++++++++++--- src/minify.js | 35 ++-- src/utils.js | 20 ++- test/__snapshots__/worker.test.js.snap | 6 +- test/worker.test.js | 44 +++-- tsconfig.json | 15 ++ types/cjs.d.ts | 2 + types/index.d.ts | 215 +++++++++++++++++++++++ types/minify.d.ts | 17 ++ types/utils.d.ts | 25 +++ 12 files changed, 601 insertions(+), 59 deletions(-) create mode 100644 tsconfig.json create mode 100644 types/cjs.d.ts create mode 100644 types/index.d.ts create mode 100644 types/minify.d.ts create mode 100644 types/utils.d.ts diff --git a/package-lock.json b/package-lock.json index 5328dc4..5c1de6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "3.3.2", "license": "MIT", "dependencies": { + "@types/html-minifier-terser": "^6.1.0", "html-minifier-terser": "^6.0.0", "jest-worker": "^27.0.6", "schema-utils": "^4.0.0", @@ -20,6 +21,7 @@ "@babel/preset-env": "^7.15.0", "@commitlint/cli": "^15.0.0", "@commitlint/config-conventional": "^15.0.0", + "@types/serialize-javascript": "^5.0.1", "@webpack-contrib/eslint-config-webpack": "^3.0.0", "babel-jest": "^27.0.6", "copy-webpack-plugin": "^9.0.1", @@ -36,6 +38,7 @@ "npm-run-all": "^4.1.5", "prettier": "^2.3.2", "standard-version": "^9.3.1", + "typescript": "^4.5.2", "webpack": "^5.64.1" }, "engines": { @@ -3109,6 +3112,11 @@ "@types/node": "*" } }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -3173,6 +3181,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", @@ -12331,9 +12345,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", @@ -15165,6 +15179,11 @@ "@types/node": "*" } }, + "@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" + }, "@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -15229,6 +15248,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", @@ -22113,9 +22138,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 7543fa5..8db3a75 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", + "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,12 +38,14 @@ "release": "standard-version" }, "files": [ - "dist" + "dist", + "types" ], "peerDependencies": { "webpack": "^5.1.0" }, "dependencies": { + "@types/html-minifier-terser": "^6.1.0", "html-minifier-terser": "^6.0.0", "jest-worker": "^27.0.6", "schema-utils": "^4.0.0", @@ -51,6 +57,7 @@ "@babel/preset-env": "^7.15.0", "@commitlint/cli": "^15.0.0", "@commitlint/config-conventional": "^15.0.0", + "@types/serialize-javascript": "^5.0.1", "@webpack-contrib/eslint-config-webpack": "^3.0.0", "babel-jest": "^27.0.6", "copy-webpack-plugin": "^9.0.1", @@ -67,6 +74,7 @@ "npm-run-all": "^4.1.5", "prettier": "^2.3.2", "standard-version": "^9.3.1", + "typescript": "^4.5.2", "webpack": "^5.64.1" }, "keywords": [ diff --git a/src/index.js b/src/index.js index d6d0ec6..29bdb5a 100644 --- a/src/index.js +++ b/src/index.js @@ -9,9 +9,113 @@ import schema from "./options.json"; import { htmlMinifierTerser, throttleAll } from "./utils"; 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("webpack").Asset} Asset */ +/** @typedef {import("jest-worker").Worker} JestWorker */ +/** @typedef {import("./utils.js").HtmlMinifierTerserOptions} HtmlMinifierTerserOptions */ + +/** @typedef {RegExp | string} Rule */ + +/** @typedef {Rule[] | Rule} Rules */ + +/** + * @typedef {Object} MinimizedResult + * @property {string} code + * @property {Array} [errors] + * @property {Array} [warnings] + */ + +/** + * @typedef {{ [file: string]: string }} Input + */ + +/** + * @typedef {{ [key: string]: any }} CustomOptions + */ + +/** + * @template T + * @typedef {T extends infer U ? U : CustomOptions} InferDefaultType + */ + +/** + * @template T + * @typedef {InferDefaultType | undefined} MinimizerOptions + */ + +/** + * @template T + * @callback MinimizerImplementation + * @param {Input} input + * @param {MinimizerOptions} [minimizerOptions] + * @returns {Promise} + */ + +/** + * @template T + * @typedef {Object} Minimizer + * @property {MinimizerImplementation} implementation + * @property {MinimizerOptions | undefined} [options] + */ + +/** + * @template T + * @typedef {Object} InternalOptions + * @property {string} name + * @property {string} input + * @property {T extends any[] ? { [P in keyof T]: Minimizer; } : Minimizer} minimizer + */ + +/** + * @typedef InternalResult + * @property {string} code + * @property {Array} warnings + * @property {Array} errors + */ + +/** + * @template T + * @typedef {JestWorker & { transform: (options: string) => InternalResult, minify: (options: InternalOptions) => InternalResult }} MinimizerWorker + */ + +/** + * @typedef {undefined | boolean | number} Parallel + */ + +/** + * @typedef {Object} BasePluginOptions + * @property {Rules} [test] + * @property {Rules} [include] + * @property {Rules} [exclude] + * @property {Parallel} [parallel] + */ + +/** + * @template T + * @typedef {BasePluginOptions & { minimizer: T extends any[] ? { [P in keyof T]: Minimizer } : Minimizer }} InternalPluginOptions + */ + +/** + * @template T + * @typedef {T extends HtmlMinifierTerserOptions + * ? { minify?: MinimizerImplementation | undefined, minimizerOptions?: MinimizerOptions | undefined } + * : T extends any[] + * ? { minify: { [P in keyof T]: MinimizerImplementation; }, minimizerOptions?: { [P in keyof T]?: MinimizerOptions | undefined; } | undefined } + * : { minify: MinimizerImplementation, minimizerOptions?: MinimizerOptions | undefined }} DefinedDefaultMinimizerAndOptions + */ + +/** + * @template [T=HtmlMinifierTerserOptions] + */ class HtmlMinimizerPlugin { - constructor(options = {}) { - validate(schema, options, { + /** + * @param {BasePluginOptions & DefinedDefaultMinimizerAndOptions} [options] + */ + constructor(options) { + validate(/** @type {Schema} */ (schema), options || {}, { name: "Html Minimizer Plugin", baseDataPath: "options", }); @@ -19,25 +123,69 @@ class HtmlMinimizerPlugin { const { minify = htmlMinifierTerser, minimizerOptions, - test = /\.html(\?.*)?$/i, parallel = true, + test = /\.html(\?.*)?$/i, include, exclude, - } = options; + } = options || {}; + + /** @type {T extends any[] ? { [P in keyof T]: Minimizer; } : Minimizer} */ + let minimizer; + + if (Array.isArray(minify)) { + // @ts-ignore + minimizer = + /** @type {MinimizerImplementation[]} */ + (minify).map( + /** + * @param {MinimizerImplementation} item + * @param {number} i + * @returns {Minimizer} + */ + (item, i) => { + return { + implementation: item, + options: Array.isArray(minimizerOptions) + ? minimizerOptions[i] + : minimizerOptions, + }; + } + ); + } else { + minimizer = + /** @type {T extends any[] ? { [P in keyof T]: Minimizer; } : Minimizer} */ + ({ implementation: minify, options: minimizerOptions }); + } + /** + * @private + * @type {InternalPluginOptions} + */ this.options = { test, parallel, include, exclude, - minify, - minimizerOptions, + minimizer, }; } + /** + * @private + * @param {any} warning + * @param {string} file + * @returns {Error} + */ static buildWarning(warning, file) { + /** + * @type {Error & { hideStack?: true, file?: string }} + */ const builtWarning = new Error( - warning.message ? warning.message : warning.toString() + warning instanceof Error + ? warning.message + : typeof warning.message !== "undefined" + ? warning.message + : warning.toString() ); builtWarning.name = "Warning"; @@ -47,11 +195,19 @@ class HtmlMinimizerPlugin { return builtWarning; } + /** + * @private + * @param {any} error + * @param {string} file + * @returns {Error} + */ static buildError(error, file) { + /** + * @type {Error & { file?: string }} + */ let builtError; if (typeof error === "string") { - // @ts-ignore builtError = new Error(`${file} from Html Minimizer plugin\n${error}`); builtError.file = file; @@ -78,6 +234,11 @@ class HtmlMinimizerPlugin { return builtError; } + /** + * @private + * @param {Parallel} parallel + * @returns {number} + */ static getAvailableNumberOfCores(parallel) { // In some cases cpus() returns undefined // https://github.com/nodejs/node/issues/19022 @@ -88,13 +249,21 @@ class HtmlMinimizerPlugin { : 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("HtmlMinimizerWebpackPlugin"); let numberOfAssets = 0; const assetsForMinify = await Promise.all( Object.keys(assets) .filter((name) => { - const { info } = compilation.getAsset(name); + const { info } = /** @type {Asset} */ (compilation.getAsset(name)); // Skip double minimize assets from child compilation if (info.minimized) { @@ -114,7 +283,9 @@ class HtmlMinimizerPlugin { 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); @@ -132,8 +303,11 @@ class HtmlMinimizerPlugin { return; } + /** @type {undefined | (() => MinimizerWorker)} */ let getWorker; + /** @type {undefined | MinimizerWorker} */ let initializedWorker; + /** @type {undefined | number} */ let numberOfWorkers; if (optimizeOptions.availableNumberOfCores > 0) { @@ -148,10 +322,14 @@ class HtmlMinimizerPlugin { 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(); @@ -188,11 +366,13 @@ class HtmlMinimizerPlugin { input = input.toString(); } + /** + * @type {InternalOptions} + */ const options = { name, input, - minimizerOptions: this.options.minimizerOptions, - minify: this.options.minify, + minimizer: this.options.minimizer, }; try { @@ -201,7 +381,8 @@ class HtmlMinimizerPlugin { : minifyFn(options)); } catch (error) { compilation.errors.push( - HtmlMinimizerPlugin.buildError(error, name) + /** @type {WebpackError} */ + (HtmlMinimizerPlugin.buildError(error, name)) ); return; @@ -221,7 +402,8 @@ class HtmlMinimizerPlugin { if (output.warnings && output.warnings.length > 0) { for (const warning of output.warnings) { compilation.warnings.push( - HtmlMinimizerPlugin.buildWarning(warning, name) + /** @type {WebpackError} */ + (HtmlMinimizerPlugin.buildWarning(warning, name)) ); } } @@ -229,7 +411,8 @@ class HtmlMinimizerPlugin { if (output.errors && output.errors.length > 0) { for (const error of output.errors) { compilation.errors.push( - HtmlMinimizerPlugin.buildError(error, name) + /** @type {WebpackError} */ + (HtmlMinimizerPlugin.buildError(error, name)) ); } } @@ -250,6 +433,10 @@ class HtmlMinimizerPlugin { } } + /** + * @param {Compiler} compiler + * @returns {void} + */ apply(compiler) { const pluginName = this.constructor.name; const availableNumberOfCores = @@ -275,8 +462,11 @@ class HtmlMinimizerPlugin { .tap( "html-minimizer-webpack-plugin", (minimized, { green, formatFlag }) => - // eslint-disable-next-line no-undefined - minimized ? green(formatFlag("minimized")) : undefined + minimized + ? /** @type {Function} */ (green)( + /** @type {Function} */ (formatFlag)("minimized") + ) + : "" ); }); }); diff --git a/src/minify.js b/src/minify.js index 9b68422..8d4a809 100644 --- a/src/minify.js +++ b/src/minify.js @@ -1,23 +1,29 @@ -const minify = async (options) => { - const minifyFns = - typeof options.minify === "function" ? [options.minify] : options.minify; +/** @typedef {import("./index.js").MinimizedResult} MinimizedResult */ +/** @typedef {import("./index.js").InternalResult} InternalResult */ +/** + * @template T + * @param {import("./index.js").InternalOptions} options + * @returns {Promise} + */ +const minify = async (options) => { + /** @type {InternalResult} */ const result = { code: options.input, warnings: [], errors: [], }; - for (let i = 0; i <= minifyFns.length - 1; i++) { - const minifyFn = minifyFns[i]; + const transformers = Array.isArray(options.minimizer) + ? options.minimizer + : [options.minimizer]; - const minifyOptions = Array.isArray(options.minimizerOptions) - ? options.minimizerOptions[i] - : options.minimizerOptions; + for (let i = 0; i <= transformers.length - 1; i++) { + const { implementation } = transformers[i]; // eslint-disable-next-line no-await-in-loop - const minifyResult = await minifyFn( + const minifyResult = await implementation( { [options.name]: result.code }, - minifyOptions + transformers[i].options ); if ( @@ -29,6 +35,7 @@ const minify = async (options) => { result.warnings = result.warnings.concat(minifyResult.warnings || []); result.errors = result.errors.concat(minifyResult.errors || []); } else { + // @ts-ignore result.code = minifyResult; } } @@ -36,11 +43,15 @@ 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 // eslint-disable-next-line no-new-func, no-param-reassign - options = new Function( + const evaluatedOptions = new Function( "exports", "require", "module", @@ -49,7 +60,7 @@ async function transform(options) { `'use strict'\nreturn ${options}` )(exports, require, module, __filename, __dirname); - return minify(options); + return minify(evaluatedOptions); } module.exports.minify = minify; diff --git a/src/utils.js b/src/utils.js index 4825513..0d9e1c4 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,3 +1,7 @@ +/** @typedef {import("./index.js").MinimizedResult} MinimizedResult */ +/** @typedef {import("./index.js").Input} Input */ +/** @typedef {import("html-minifier-terser").Options} HtmlMinifierTerserOptions */ + const notSettled = Symbol(`not-settled`); /** @@ -61,11 +65,17 @@ function throttleAll(limit, tasks) { }); } +/** + * @param {Input} input + * @param {HtmlMinifierTerserOptions | undefined} [minimizerOptions] + * @returns {Promise} + */ /* istanbul ignore next */ -async function htmlMinifierTerser(data, minimizerOptions) { - // eslint-disable-next-line global-require,import/no-extraneous-dependencies +async function htmlMinifierTerser(input, minimizerOptions = {}) { + // eslint-disable-next-line global-require, import/no-extraneous-dependencies const htmlMinifier = require("html-minifier-terser"); - + const [[, code]] = Object.entries(input); + /** @type {HtmlMinifierTerserOptions} */ const defaultMinimizerOptions = { caseSensitive: true, // `collapseBooleanAttributes` is not always safe, since this can break CSS attribute selectors and not safe for XHTML @@ -84,9 +94,7 @@ async function htmlMinifierTerser(data, minimizerOptions) { removeStyleLinkTypeAttributes: true, // `useShortDoctype` is not safe for XHTML }; - - const [[, input]] = Object.entries(data); - const result = await htmlMinifier.minify(input, { + const result = await htmlMinifier.minify(code, { ...defaultMinimizerOptions, ...minimizerOptions, }); diff --git a/test/__snapshots__/worker.test.js.snap b/test/__snapshots__/worker.test.js.snap index f8d7252..f4b8fb1 100644 --- a/test/__snapshots__/worker.test.js.snap +++ b/test/__snapshots__/worker.test.js.snap @@ -6,10 +6,12 @@ Array [ ] `; -exports[`worker should minify html: html 1`] = `"

foo

"`; +exports[`worker should minify html #2: html 1`] = `"

foo

"`; -exports[`worker should minify html: html 2`] = ` +exports[`worker should minify html #3: html 1`] = ` Object { "html": "

from-minify-function

", } `; + +exports[`worker should minify html: html 1`] = `"

foo

"`; diff --git a/test/worker.test.js b/test/worker.test.js index 438ecf6..1905b12 100644 --- a/test/worker.test.js +++ b/test/worker.test.js @@ -10,25 +10,47 @@ describe("worker", () => { const options = { name: "entry.html", input: '

foo

', - minimizerOptions: { - removeComments: false, + minimizer: { + implementation: HtmlMinimizerPlugin.htmlMinifierTerser, + options: { + removeComments: false, + }, }, - minify: HtmlMinimizerPlugin.htmlMinifierTerser, }; const { code } = await transform(serialize(options)); expect(code).toMatchSnapshot("html"); }); - it("should minify html", async () => { + it("should minify html #2", async () => { const options = { name: "entry.html", input: '

foo

', - minimizerOptions: { removeComments: false }, - minify: () => { - return { - html: '

from-minify-function

', - }; + minimizer: [ + { + implementation: HtmlMinimizerPlugin.htmlMinifierTerser, + options: { + removeComments: false, + }, + }, + ], + }; + const { code } = await transform(serialize(options)); + + expect(code).toMatchSnapshot("html"); + }); + + it("should minify html #3", async () => { + const options = { + name: "entry.html", + input: '

foo

', + minimizer: { + implementation: () => { + return { + html: '

from-minify-function

', + }; + }, + options: { removeComments: false }, }, }; const { code } = await transform(serialize(options)); @@ -40,7 +62,9 @@ describe("worker", () => { const options = { name: "entry.html", input: false, - minify: HtmlMinimizerPlugin.htmlMinifierTerser, + minimizer: { + implementation: HtmlMinimizerPlugin.htmlMinifierTerser, + }, }; try { diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d593ba5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "esnext", + // "module": "nodenext", + "moduleResolution": "node", + "allowJs": true, + "checkJs": true, + "strict": true, + "types": ["node"], + "resolveJsonModule": true, + "newLine": "LF", + "allowSyntheticDefaultImports": true + }, + "include": ["./src/**/*"] +} diff --git a/types/cjs.d.ts b/types/cjs.d.ts new file mode 100644 index 0000000..a7e7d3c --- /dev/null +++ b/types/cjs.d.ts @@ -0,0 +1,2 @@ +declare const _exports: typeof import("./index").default; +export = _exports; diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 0000000..fad39dc --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,215 @@ +export default HtmlMinimizerPlugin; +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 Asset = import("webpack").Asset; +export type JestWorker = import("jest-worker").Worker; +export type HtmlMinifierTerserOptions = + import("./utils.js").HtmlMinifierTerserOptions; +export type Rule = RegExp | string; +export type Rules = Rule[] | Rule; +export type MinimizedResult = { + code: string; + errors?: unknown[] | undefined; + warnings?: unknown[] | undefined; +}; +export type Input = { + [file: string]: string; +}; +export type CustomOptions = { + [key: string]: any; +}; +export type InferDefaultType = T extends infer U ? U : CustomOptions; +export type MinimizerOptions = InferDefaultType | undefined; +export type MinimizerImplementation = ( + input: Input, + minimizerOptions?: MinimizerOptions +) => Promise; +export type Minimizer = { + implementation: MinimizerImplementation; + options?: MinimizerOptions | undefined; +}; +export type InternalOptions = { + name: string; + input: string; + minimizer: T extends any[] + ? { [P in keyof T]: Minimizer } + : Minimizer; +}; +export type InternalResult = { + code: string; + warnings: Array; + errors: Array; +}; +export type MinimizerWorker = Worker & { + transform: (options: string) => InternalResult; + minify: (options: InternalOptions) => InternalResult; +}; +export type Parallel = undefined | boolean | number; +export type BasePluginOptions = { + test?: Rules | undefined; + include?: Rules | undefined; + exclude?: Rules | undefined; + parallel?: Parallel; +}; +export type InternalPluginOptions = BasePluginOptions & { + minimizer: T extends any[] + ? { [P in keyof T]: Minimizer } + : Minimizer; +}; +export type DefinedDefaultMinimizerAndOptions = + T extends import("html-minifier-terser").Options + ? { + minify?: MinimizerImplementation | undefined; + minimizerOptions?: MinimizerOptions | undefined; + } + : T extends any[] + ? { + minify: { [P in keyof T]: MinimizerImplementation }; + minimizerOptions?: + | { [P_1 in keyof T]?: MinimizerOptions } + | undefined; + } + : { + minify: MinimizerImplementation; + minimizerOptions?: MinimizerOptions | undefined; + }; +/** @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("webpack").Asset} Asset */ +/** @typedef {import("jest-worker").Worker} JestWorker */ +/** @typedef {import("./utils.js").HtmlMinifierTerserOptions} HtmlMinifierTerserOptions */ +/** @typedef {RegExp | string} Rule */ +/** @typedef {Rule[] | Rule} Rules */ +/** + * @typedef {Object} MinimizedResult + * @property {string} code + * @property {Array} [errors] + * @property {Array} [warnings] + */ +/** + * @typedef {{ [file: string]: string }} Input + */ +/** + * @typedef {{ [key: string]: any }} CustomOptions + */ +/** + * @template T + * @typedef {T extends infer U ? U : CustomOptions} InferDefaultType + */ +/** + * @template T + * @typedef {InferDefaultType | undefined} MinimizerOptions + */ +/** + * @template T + * @callback MinimizerImplementation + * @param {Input} input + * @param {MinimizerOptions} [minimizerOptions] + * @returns {Promise} + */ +/** + * @template T + * @typedef {Object} Minimizer + * @property {MinimizerImplementation} implementation + * @property {MinimizerOptions | undefined} [options] + */ +/** + * @template T + * @typedef {Object} InternalOptions + * @property {string} name + * @property {string} input + * @property {T extends any[] ? { [P in keyof T]: Minimizer; } : Minimizer} minimizer + */ +/** + * @typedef InternalResult + * @property {string} code + * @property {Array} warnings + * @property {Array} errors + */ +/** + * @template T + * @typedef {JestWorker & { transform: (options: string) => InternalResult, minify: (options: InternalOptions) => InternalResult }} MinimizerWorker + */ +/** + * @typedef {undefined | boolean | number} Parallel + */ +/** + * @typedef {Object} BasePluginOptions + * @property {Rules} [test] + * @property {Rules} [include] + * @property {Rules} [exclude] + * @property {Parallel} [parallel] + */ +/** + * @template T + * @typedef {BasePluginOptions & { minimizer: T extends any[] ? { [P in keyof T]: Minimizer } : Minimizer }} InternalPluginOptions + */ +/** + * @template T + * @typedef {T extends HtmlMinifierTerserOptions + * ? { minify?: MinimizerImplementation | undefined, minimizerOptions?: MinimizerOptions | undefined } + * : T extends any[] + * ? { minify: { [P in keyof T]: MinimizerImplementation; }, minimizerOptions?: { [P in keyof T]?: MinimizerOptions | undefined; } | undefined } + * : { minify: MinimizerImplementation, minimizerOptions?: MinimizerOptions | undefined }} DefinedDefaultMinimizerAndOptions + */ +/** + * @template [T=HtmlMinifierTerserOptions] + */ +declare class HtmlMinimizerPlugin { + /** + * @private + * @param {any} warning + * @param {string} file + * @returns {Error} + */ + private static buildWarning; + /** + * @private + * @param {any} error + * @param {string} file + * @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 HtmlMinimizerPlugin { + export { htmlMinifierTerser }; +} +import { Worker } from "jest-worker"; +import { htmlMinifierTerser } from "./utils"; diff --git a/types/minify.d.ts b/types/minify.d.ts new file mode 100644 index 0000000..2fa565f --- /dev/null +++ b/types/minify.d.ts @@ -0,0 +1,17 @@ +export type MinimizedResult = import("./index.js").MinimizedResult; +export type InternalResult = import("./index.js").InternalResult; +/** @typedef {import("./index.js").MinimizedResult} MinimizedResult */ +/** @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..b416dc4 --- /dev/null +++ b/types/utils.d.ts @@ -0,0 +1,25 @@ +export type Task = () => Promise; +export type MinimizedResult = import("./index.js").MinimizedResult; +export type Input = import("./index.js").Input; +export type HtmlMinifierTerserOptions = import("html-minifier-terser").Options; +/** + * @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 {HtmlMinifierTerserOptions | undefined} [minimizerOptions] + * @returns {Promise} + */ +export function htmlMinifierTerser( + input: Input, + minimizerOptions?: HtmlMinifierTerserOptions | undefined +): Promise;