Skip to content

Commit 021c752

Browse files
feat: added swcMinifyFragment to minify HTML fragments
1 parent afaf453 commit 021c752

File tree

8 files changed

+254
-8
lines changed

8 files changed

+254
-8
lines changed

README.md

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,24 @@ module.exports = {
9595

9696
// For `@swc/html`:
9797
//
98+
// HTML documents - HTML documents with `Doctype` and `<html>/`<head>`/`<body>` tags
99+
//
98100
// Options - https://github.com/swc-project/bindings/blob/main/packages/html/index.ts#L5
99101
//
100102
// new HtmlMinimizerPlugin({
101103
// minify: HtmlMinimizerPlugin.swcMinify,
102104
// minimizerOptions: {}
103105
// })
106+
//
107+
//
108+
// HTML fragments - HTML fragments, i.e. HTML files without doctype or used in `<template>` tags or HTML parts which injects into another HTML parts
109+
//
110+
// Options - https://github.com/swc-project/bindings/blob/main/packages/html/index.ts#L38
111+
//
112+
// new HtmlMinimizerPlugin({
113+
// minify: HtmlMinimizerPlugin.swcMinifyFragment,
114+
// minimizerOptions: {}
115+
// })
104116
],
105117
},
106118
};
@@ -297,7 +309,13 @@ By default, plugin uses [html-minifier-terser](https://github.com/terser/html-mi
297309
We currently support:
298310

299311
- `HtmlMinimizerPlugin.htmlMinifierTerser`
300-
- `HtmlMinimizerPlugin.swcMinify`
312+
- `HtmlMinimizerPlugin.swcMinify` (used to compress HTML documents, i.e. with HTML doctype and `<html>`/`<body>`/`<head>` tags)
313+
- `HtmlMinimizerPlugin.swcMinifyFragment` (used to compress HTML fragments, i.e. when you have part of HTML which will be inserted into another HTML parts)
314+
315+
> **Note**
316+
>
317+
> The difference between `swcMinify` and `swcMinifyFragment` is the error reporting.
318+
> You will get errors (invalid or broken syntax) if you have them in your HTML document or fragment. Which allows you to see all the errors and problems at the build stage.
301319
302320
Useful for using and testing unpublished versions or forks.
303321

@@ -454,6 +472,8 @@ module.exports = {
454472

455473
Available [`options`](https://github.com/swc-project/bindings/blob/main/packages/html/index.ts#L5).
456474

475+
HTML Documents:
476+
457477
```js
458478
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");
459479
const CopyPlugin = require("copy-webpack-plugin");
@@ -491,6 +511,48 @@ module.exports = {
491511
};
492512
```
493513

514+
HTML Framgents:
515+
516+
```js
517+
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");
518+
const CopyPlugin = require("copy-webpack-plugin");
519+
520+
module.exports = {
521+
module: {
522+
rules: [
523+
{
524+
test: /\.html$/i,
525+
type: "asset/resource",
526+
},
527+
],
528+
},
529+
plugins: [
530+
new CopyPlugin({
531+
patterns: [
532+
{
533+
context: path.resolve(__dirname, "dist"),
534+
from: "./src/*.html",
535+
},
536+
],
537+
}),
538+
],
539+
optimization: {
540+
minimize: true,
541+
minimizer: [
542+
new HtmlMinimizerPlugin({
543+
test: /\.template\.html$/i,
544+
minify: HtmlMinimizerPlugin.swcMinifyFragment,
545+
minimizerOptions: {
546+
// Options - https://github.com/swc-project/bindings/blob/main/packages/html/index.ts#L38
547+
},
548+
}),
549+
],
550+
},
551+
};
552+
```
553+
554+
You can use multiple `HtmlMinimizerPlugin` plugins to compress different files with the different `minify` function.
555+
494556
## Contributing
495557

496558
Please take a moment to read our contributing guidelines if you haven't yet done so.

src/index.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ const { Worker } = require("jest-worker");
66

77
const schema = require("./options.json");
88

9-
const { throttleAll, htmlMinifierTerser, swcMinify } = require("./utils");
10-
const { minify } = require("./minify");
9+
const {
10+
throttleAll,
11+
htmlMinifierTerser,
12+
swcMinify,
13+
swcMinifyFragment,
14+
} = require("./utils");
15+
const { minify: minifyInternal } = require("./minify");
1116

1217
/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
1318
/** @typedef {import("webpack").Compiler} Compiler */
@@ -378,7 +383,7 @@ class HtmlMinimizerPlugin {
378383
try {
379384
output = await (getWorker
380385
? getWorker().transform(serialize(options))
381-
: minify(options));
386+
: minifyInternal(options));
382387
} catch (error) {
383388
compilation.errors.push(
384389
/** @type {WebpackError} */
@@ -475,5 +480,6 @@ class HtmlMinimizerPlugin {
475480

476481
HtmlMinimizerPlugin.htmlMinifierTerser = htmlMinifierTerser;
477482
HtmlMinimizerPlugin.swcMinify = swcMinify;
483+
HtmlMinimizerPlugin.swcMinifyFragment = swcMinifyFragment;
478484

479485
module.exports = HtmlMinimizerPlugin;

src/utils.js

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,46 @@ async function htmlMinifierTerser(input, minimizerOptions = {}) {
112112
async function swcMinify(input, minimizerOptions = {}) {
113113
// eslint-disable-next-line global-require, import/no-extraneous-dependencies, import/no-unresolved
114114
const swcMinifier = require("@swc/html");
115-
115+
const [[, code]] = Object.entries(input);
116116
// TODO `import("@swc/html").Options`
117117
const options = /** @type {*} */ ({
118118
...minimizerOptions,
119119
});
120+
const result = await swcMinifier.minify(Buffer.from(code), options);
121+
122+
let errors;
123+
124+
if (typeof result.errors !== "undefined") {
125+
errors = result.errors.map((diagnostic) => {
126+
const error = new Error(diagnostic.message);
127+
128+
// @ts-ignore
129+
error.span = diagnostic.span;
130+
// @ts-ignore
131+
error.level = diagnostic.level;
132+
133+
return error;
134+
});
135+
}
120136

137+
return { code: result.code, errors };
138+
}
139+
140+
/**
141+
* @param {Input} input
142+
* @param {CustomOptions | undefined} [minimizerOptions]
143+
* @returns {Promise<MinimizedResult>}
144+
*/
145+
/* istanbul ignore next */
146+
async function swcMinifyFragment(input, minimizerOptions = {}) {
147+
// eslint-disable-next-line global-require, import/no-extraneous-dependencies, import/no-unresolved
148+
const swcMinifier = require("@swc/html");
121149
const [[, code]] = Object.entries(input);
122-
const result = await swcMinifier.minify(Buffer.from(code), options);
150+
// TODO `import("@swc/html").Options`
151+
const options = /** @type {*} */ ({
152+
...minimizerOptions,
153+
});
154+
const result = await swcMinifier.minifyFragment(Buffer.from(code), options);
123155

124156
let errors;
125157

@@ -139,4 +171,9 @@ async function swcMinify(input, minimizerOptions = {}) {
139171
return { code: result.code, errors };
140172
}
141173

142-
module.exports = { throttleAll, htmlMinifierTerser, swcMinify };
174+
module.exports = {
175+
throttleAll,
176+
htmlMinifierTerser,
177+
swcMinify,
178+
swcMinifyFragment,
179+
};

test/__snapshots__/minify-option.test.js.snap

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,78 @@ exports[`"minify" option should work with 'swcMinify': assets 1`] = `
155155
exports[`"minify" option should work with 'swcMinify': errors 1`] = `[]`;
156156

157157
exports[`"minify" option should work with 'swcMinify': warnings 1`] = `[]`;
158+
159+
exports[`"minify" option should work with 'swcMinifyFragment' and options: assets 1`] = `
160+
{
161+
"template.html": "<div> <ul class="bar baz foo"> <li>test</li> <li>test</li> <li>test</li> </ul> </div> <span>test</span> ",
162+
}
163+
`;
164+
165+
exports[`"minify" option should work with 'swcMinifyFragment' and options: errors 1`] = `[]`;
166+
167+
exports[`"minify" option should work with 'swcMinifyFragment' and options: warnings 1`] = `[]`;
168+
169+
exports[`"minify" option should work with 'swcMinifyFragment' and throw errors: assets 1`] = `
170+
{
171+
"broken-html-syntax.html": "Text &lt; img src="image.png" >
172+
Text &lt;<img src=image.png>
173+
Text ><img src=image.png>
174+
175+
<a foo><bar></bar></a><boo>boohay
176+
&lt;&lt;&lt;&lt;>foo
177+
>>&lt;</boo>",
178+
}
179+
`;
180+
181+
exports[`"minify" option should work with 'swcMinifyFragment' and throw errors: errors 1`] = `
182+
[
183+
"Error: broken-html-syntax.html from Html Minimizer plugin
184+
Abrupt closing of empty comment",
185+
"Error: broken-html-syntax.html from Html Minimizer plugin
186+
Cdata in html content",
187+
"Error: broken-html-syntax.html from Html Minimizer plugin
188+
End tag "a" violates nesting rules",
189+
"Error: broken-html-syntax.html from Html Minimizer plugin
190+
Eof in tag",
191+
"Error: broken-html-syntax.html from Html Minimizer plugin
192+
Incorrectly opened comment",
193+
"Error: broken-html-syntax.html from Html Minimizer plugin
194+
Invalid first character of tag name",
195+
"Error: broken-html-syntax.html from Html Minimizer plugin
196+
Invalid first character of tag name",
197+
"Error: broken-html-syntax.html from Html Minimizer plugin
198+
Invalid first character of tag name",
199+
"Error: broken-html-syntax.html from Html Minimizer plugin
200+
Invalid first character of tag name",
201+
"Error: broken-html-syntax.html from Html Minimizer plugin
202+
Invalid first character of tag name",
203+
"Error: broken-html-syntax.html from Html Minimizer plugin
204+
Invalid first character of tag name",
205+
"Error: broken-html-syntax.html from Html Minimizer plugin
206+
Invalid first character of tag name",
207+
"Error: broken-html-syntax.html from Html Minimizer plugin
208+
Non void html element start tag with trailing solidus",
209+
"Error: broken-html-syntax.html from Html Minimizer plugin
210+
Unexpected question mark instead of tag name",
211+
]
212+
`;
213+
214+
exports[`"minify" option should work with 'swcMinifyFragment' and throw errors: warnings 1`] = `[]`;
215+
216+
exports[`"minify" option should work with 'swcMinifyFragment': assets 1`] = `
217+
{
218+
"template.html": "<div>
219+
<ul class="bar baz foo">
220+
<li>test</li>
221+
<li>test</li>
222+
<li>test</li>
223+
</ul>
224+
</div>
225+
<span>test</span>
226+
",
227+
}
228+
`;
229+
230+
exports[`"minify" option should work with 'swcMinifyFragment': errors 1`] = `[]`;
231+
232+
exports[`"minify" option should work with 'swcMinifyFragment': warnings 1`] = `[]`;

test/fixtures/template.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<div>
2+
<ul class=" foo bar baz ">
3+
<li>test</li>
4+
<li>test</li>
5+
<li>test</li>
6+
</ul>
7+
</div>
8+
<span>test</span>

test/minify-option.test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,52 @@ describe('"minify" option', () => {
238238
expect(getErrors(stats)).toMatchSnapshot("errors");
239239
expect(getWarnings(stats)).toMatchSnapshot("warnings");
240240
});
241+
242+
it("should work with 'swcMinifyFragment'", async () => {
243+
const testHtmlId = "./template.html";
244+
const compiler = getCompiler(testHtmlId);
245+
246+
new HtmlMinimizerPlugin({
247+
minify: HtmlMinimizerPlugin.swcMinifyFragment,
248+
}).apply(compiler);
249+
250+
const stats = await compile(compiler);
251+
252+
expect(readAssets(compiler, stats, /\.html$/i)).toMatchSnapshot("assets");
253+
expect(getErrors(stats)).toMatchSnapshot("errors");
254+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
255+
});
256+
257+
it("should work with 'swcMinifyFragment' and throw errors", async () => {
258+
const testHtmlId = "./broken-html-syntax.html";
259+
const compiler = getCompiler(testHtmlId);
260+
261+
new HtmlMinimizerPlugin({
262+
minify: HtmlMinimizerPlugin.swcMinifyFragment,
263+
}).apply(compiler);
264+
265+
const stats = await compile(compiler);
266+
267+
expect(readAssets(compiler, stats, /\.html$/i)).toMatchSnapshot("assets");
268+
expect(getErrors(stats)).toMatchSnapshot("errors");
269+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
270+
});
271+
272+
it("should work with 'swcMinifyFragment' and options", async () => {
273+
const testHtmlId = "./template.html";
274+
const compiler = getCompiler(testHtmlId);
275+
276+
new HtmlMinimizerPlugin({
277+
minimizerOptions: {
278+
collapseWhitespaces: "advanced-conservative",
279+
},
280+
minify: HtmlMinimizerPlugin.swcMinifyFragment,
281+
}).apply(compiler);
282+
283+
const stats = await compile(compiler);
284+
285+
expect(readAssets(compiler, stats, /\.html$/i)).toMatchSnapshot("assets");
286+
expect(getErrors(stats)).toMatchSnapshot("errors");
287+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
288+
});
241289
});

types/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ declare namespace HtmlMinimizerPlugin {
136136
export {
137137
htmlMinifierTerser,
138138
swcMinify,
139+
swcMinifyFragment,
139140
Schema,
140141
Compiler,
141142
Compilation,
@@ -191,6 +192,7 @@ type DefinedDefaultMinimizerAndOptions<T> =
191192
};
192193
import { htmlMinifierTerser } from "./utils";
193194
import { swcMinify } from "./utils";
195+
import { swcMinifyFragment } from "./utils";
194196
type Schema = import("schema-utils/declarations/validate").Schema;
195197
type Compilation = import("webpack").Compilation;
196198
type WebpackError = import("webpack").WebpackError;
@@ -246,5 +248,4 @@ type InternalPluginOptions<T> = BasePluginOptions & {
246248
: never
247249
: Minimizer<T>;
248250
};
249-
import { minify } from "./minify";
250251
import { Worker } from "jest-worker";

types/utils.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,12 @@ export function swcMinify(
3333
input: Input,
3434
minimizerOptions?: CustomOptions | undefined
3535
): Promise<MinimizedResult>;
36+
/**
37+
* @param {Input} input
38+
* @param {CustomOptions | undefined} [minimizerOptions]
39+
* @returns {Promise<MinimizedResult>}
40+
*/
41+
export function swcMinifyFragment(
42+
input: Input,
43+
minimizerOptions?: CustomOptions | undefined
44+
): Promise<MinimizedResult>;

0 commit comments

Comments
 (0)