1 | ;
|
2 |
|
3 | /** @typedef {import("./index.js").MinimizedResult} MinimizedResult */
|
4 |
|
5 | /** @typedef {import("./index.js").Input} Input */
|
6 |
|
7 | /** @typedef {import("html-minifier-terser").Options} HtmlMinifierTerserOptions */
|
8 | const notSettled = Symbol(`not-settled`);
|
9 | /**
|
10 | * @template T
|
11 | * @typedef {() => Promise<T>} Task
|
12 | */
|
13 |
|
14 | /**
|
15 | * Run tasks with limited concurency.
|
16 | * @template T
|
17 | * @param {number} limit - Limit of tasks that run at once.
|
18 | * @param {Task<T>[]} tasks - List of tasks to run.
|
19 | * @returns {Promise<T[]>} A promise that fulfills to an array of the results
|
20 | */
|
21 |
|
22 | function throttleAll(limit, tasks) {
|
23 | if (!Number.isInteger(limit) || limit < 1) {
|
24 | throw new TypeError(`Expected \`limit\` to be a finite number > 0, got \`${limit}\` (${typeof limit})`);
|
25 | }
|
26 |
|
27 | if (!Array.isArray(tasks) || !tasks.every(task => typeof task === `function`)) {
|
28 | throw new TypeError(`Expected \`tasks\` to be a list of functions returning a promise`);
|
29 | }
|
30 |
|
31 | return new Promise((resolve, reject) => {
|
32 | const result = Array(tasks.length).fill(notSettled);
|
33 | const entries = tasks.entries();
|
34 |
|
35 | const next = () => {
|
36 | const {
|
37 | done,
|
38 | value
|
39 | } = entries.next();
|
40 |
|
41 | if (done) {
|
42 | const isLast = !result.includes(notSettled);
|
43 | if (isLast) resolve(
|
44 | /** @type{T[]} **/
|
45 | result);
|
46 | return;
|
47 | }
|
48 |
|
49 | const [index, task] = value;
|
50 | /**
|
51 | * @param {T} x
|
52 | */
|
53 |
|
54 | const onFulfilled = x => {
|
55 | result[index] = x;
|
56 | next();
|
57 | };
|
58 |
|
59 | task().then(onFulfilled, reject);
|
60 | };
|
61 |
|
62 | Array(limit).fill(0).forEach(next);
|
63 | });
|
64 | }
|
65 | /**
|
66 | * @param {Input} input
|
67 | * @param {HtmlMinifierTerserOptions | undefined} [minimizerOptions]
|
68 | * @returns {Promise<MinimizedResult>}
|
69 | */
|
70 |
|
71 | /* istanbul ignore next */
|
72 |
|
73 |
|
74 | async function htmlMinifierTerser(input, minimizerOptions = {}) {
|
75 | // eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
76 | const htmlMinifier = require("html-minifier-terser");
|
77 |
|
78 | const [[, code]] = Object.entries(input);
|
79 | /** @type {HtmlMinifierTerserOptions} */
|
80 |
|
81 | const defaultMinimizerOptions = {
|
82 | caseSensitive: true,
|
83 | // `collapseBooleanAttributes` is not always safe, since this can break CSS attribute selectors and not safe for XHTML
|
84 | collapseWhitespace: true,
|
85 | conservativeCollapse: true,
|
86 | keepClosingSlash: true,
|
87 | // We need ability to use cssnano, or setup own function without extra dependencies
|
88 | minifyCSS: true,
|
89 | minifyJS: true,
|
90 | // `minifyURLs` is unsafe, because we can't guarantee what the base URL is
|
91 | // `removeAttributeQuotes` is not safe in some rare cases, also HTML spec recommends against doing this
|
92 | removeComments: true,
|
93 | // `removeEmptyAttributes` is not safe, can affect certain style or script behavior, look at https://github.com/webpack-contrib/html-loader/issues/323
|
94 | // `removeRedundantAttributes` is not safe, can affect certain style or script behavior, look at https://github.com/webpack-contrib/html-loader/issues/323
|
95 | removeScriptTypeAttributes: true,
|
96 | removeStyleLinkTypeAttributes: true // `useShortDoctype` is not safe for XHTML
|
97 |
|
98 | };
|
99 | const result = await htmlMinifier.minify(code, { ...defaultMinimizerOptions,
|
100 | ...minimizerOptions
|
101 | });
|
102 | return {
|
103 | code: result
|
104 | };
|
105 | }
|
106 |
|
107 | module.exports = {
|
108 | throttleAll,
|
109 | htmlMinifierTerser
|
110 | }; |
\ | No newline at end of file |