Skip to content

Commit

Permalink
feat: added the postprocessor option (#518)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait authored Jul 24, 2024
1 parent 31685f7 commit 536a204
Show file tree
Hide file tree
Showing 13 changed files with 241 additions and 40 deletions.
5 changes: 3 additions & 2 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@
"vspace",
"jsbeautify",
"Gitter",
"commitlint"
"commitlint",
"postprocessor",
"eslintcache"
],

"ignorePaths": [
"CHANGELOG.md",
"package.json",
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ logs
*.log
npm-debug.log*
.eslintcache
.cspellcache
/coverage
/dist
/local
Expand Down
85 changes: 81 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ module.exports = {

- **[`sources`](#sources)**
- **[`preprocessor`](#preprocessor)**
- **[`postprocessor`](#postprocessor)**
- **[`minimize`](#minimize)**
- **[`esModule`](#esmodule)**

Expand Down Expand Up @@ -490,10 +491,7 @@ module.exports = {
Type:

```ts
type preprocessor = (
content: string | Buffer,
loaderContext: LoaderContext,
) => HTMLElement;
type preprocessor = (content: string, loaderContext: LoaderContext) => string;
```

Default: `undefined`
Expand Down Expand Up @@ -591,6 +589,85 @@ module.exports = {
};
```

### `postprocessor`

Type:

```ts
type postprocessor = (content: string, loaderContext: LoaderContext) => string;
```

Default: `undefined`

Allows post-processing of content after replacing all attributes (like `src`/`srcset`/etc).

**file.html**

```html
<img src="image.png" />
<img src="<%= 'Hello ' + (1+1) %>" />
<img src="<%= require('./image.png') %>" />
<img src="<%= new URL('./image.png', import.meta.url) %>" />
<div><%= require('./gallery.html').default %></div>
```

#### `function`

You can set the `postprocessor` option as a `function` instance.

**webpack.config.js**

```js
const Handlebars = require("handlebars");

module.exports = {
module: {
rules: [
{
test: /\.html$/i,
loader: "html-loader",
options: {
postprocessor: (content, loaderContext) => {
return content.replace(/<%=/g, '" +').replace(/%>/g, '+ "');
},
},
},
],
},
};
```

You can also set the `postprocessor` option as an asynchronous function instance.

For example:

**webpack.config.js**

```js
const Handlebars = require("handlebars");

module.exports = {
module: {
rules: [
{
test: /\.hbs$/i,
loader: "html-loader",
options: {
postprocessor: async (content, loaderContext) => {
const value = await getValue();

return content
.replace(/<%=/g, '" +')
.replace(/%>/g, '+ "')
.replace("my-value", value);
},
},
},
],
},
};
```

### `minimize`

Type:
Expand Down
13 changes: 12 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,18 @@ export default async function loader(content) {
plugins.push(minimizerPlugin({ minimize: options.minimize, errors }));
}

const { html } = await pluginRunner(plugins).process(content);
let { html } = await pluginRunner(plugins).process(content);

html = JSON.stringify(html)
// Invalid in JavaScript but valid HTML
.replace(/[\u2028\u2029]/g, (str) =>
str === "\u2029" ? "\\u2029" : "\\u2028",
);

if (options.postprocessor) {
// eslint-disable-next-line no-param-reassign
html = await options.postprocessor(html, this);
}

for (const error of errors) {
this.emitError(error instanceof Error ? error : new Error(error));
Expand Down
5 changes: 5 additions & 0 deletions src/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
"description": "Allows pre-processing of content before handling.",
"link": "https://github.com/webpack-contrib/html-loader#preprocessor"
},
"postprocessor": {
"instanceof": "Function",
"description": "Allows post-processing of content before handling.",
"link": "https://github.com/webpack-contrib/html-loader#postprocessor"
},
"sources": {
"anyOf": [
{ "type": "boolean" },
Expand Down
8 changes: 2 additions & 6 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1174,6 +1174,7 @@ function getSourcesOption(rawOptions) {
export function normalizeOptions(rawOptions, loaderContext) {
return {
preprocessor: rawOptions.preprocessor,
postprocessor: rawOptions.postprocessor,
sources: getSourcesOption(rawOptions),
minimize: getMinimizeOption(rawOptions, loaderContext),
esModule:
Expand Down Expand Up @@ -1251,12 +1252,7 @@ export function getImportCode(html, loaderContext, imports, options) {
}

export function getModuleCode(html, replacements) {
let code = JSON.stringify(html)
// Invalid in JavaScript but valid HTML
.replace(/[\u2028\u2029]/g, (str) =>
str === "\u2029" ? "\\u2029" : "\\u2028",
);

let code = html;
let replacersCode = "";

for (const item of replacements) {
Expand Down
49 changes: 49 additions & 0 deletions test/__snapshots__/postprocessor-option.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`'postprocess' option should work with async "postprocessor" function option: errors 1`] = `[]`;

exports[`'postprocess' option should work with async "postprocessor" function option: module 1`] = `
"// Imports
import ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___ from "../../src/runtime/getUrl.js";
var ___HTML_LOADER_IMPORT_0___ = new URL("./image.png", import.meta.url);
// Module
var ___HTML_LOADER_REPLACEMENT_0___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___);
var code = "<div>\\n <p>{{firstname}} {{lastname}}</p>\\n <img src=\\"" + ___HTML_LOADER_REPLACEMENT_0___ + "\\" alt=\\"alt\\" />\\n<div>\\n";
// Exports
export default code;"
`;
exports[`'postprocess' option should work with async "postprocessor" function option: result 1`] = `
"<div>
<p>{{firstname}} {{lastname}}</p>
<img src="replaced_file_protocol_/webpack/public/path/image.png" alt="alt" />
<div>
"
`;
exports[`'postprocess' option should work with async "postprocessor" function option: warnings 1`] = `[]`;
exports[`'postprocess' option should work with the "postprocessor" option: errors 1`] = `[]`;
exports[`'postprocess' option should work with the "postprocessor" option: module 1`] = `
"// Imports
import ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___ from "../../src/runtime/getUrl.js";
var ___HTML_LOADER_IMPORT_0___ = new URL("./image.png", import.meta.url);
// Module
var ___HTML_LOADER_REPLACEMENT_0___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___);
var code = "<img src=\\"" + ___HTML_LOADER_REPLACEMENT_0___ + "\\">\\n<img src=\\"" + 'Hello ' + (1+1) + "\\">\\n<img src=\\"" + require('./image.png') + "\\">\\n<img src=\\"" + new URL('./image.png', import.meta.url) + "\\">\\n<div>" + require('./gallery.html').default + "</div>\\n<!--Works fine, but need improve testing <div>< %= (await import('./gallery.html')).default % ></div>-->\\n";
// Exports
export default code;"
`;
exports[`'postprocess' option should work with the "postprocessor" option: result 1`] = `
"<img src="replaced_file_protocol_/webpack/public/path/image.png">
<img src="Hello 2">
<img src="/webpack/public/path/image.png">
<img src="replaced_file_protocol_/webpack/public/path/image.png">
<div><h2>Gallery</h2></div>
<!--Works fine, but need improve testing <div>< %= (await import('./gallery.html')).default % ></div>-->
"
`;
exports[`'postprocess' option should work with the "postprocessor" option: warnings 1`] = `[]`;
32 changes: 16 additions & 16 deletions test/__snapshots__/preprocessor-option.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`'process' option should work with Async "preprocessor" Function option: errors 1`] = `[]`;
exports[`'preprocess' option should work with async "preprocessor" function option: errors 1`] = `[]`;

exports[`'process' option should work with Async "preprocessor" Function option: module 1`] = `
exports[`'preprocess' option should work with async "preprocessor" function option: module 1`] = `
"// Imports
import ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___ from "../../src/runtime/getUrl.js";
var ___HTML_LOADER_IMPORT_0___ = new URL("./image.png", import.meta.url);
Expand All @@ -13,19 +13,19 @@ var code = "<div>\\n <p>Alexander Krasnoyarov</p>\\n <img src=\\"" + ___HTML_L
export default code;"
`;
exports[`'process' option should work with Async "preprocessor" Function option: result 1`] = `
exports[`'preprocess' option should work with async "preprocessor" function option: result 1`] = `
"<div>
<p>Alexander Krasnoyarov</p>
<img src="replaced_file_protocol_/webpack/public/path/image.png" alt="alt" />
<div>
"
`;
exports[`'process' option should work with Async "preprocessor" Function option: warnings 1`] = `[]`;
exports[`'preprocess' option should work with async "preprocessor" function option: warnings 1`] = `[]`;
exports[`'process' option should work with the "preprocessor" option #2: errors 1`] = `[]`;
exports[`'preprocess' option should work with the "preprocessor" option #2: errors 1`] = `[]`;
exports[`'process' option should work with the "preprocessor" option #2: module 1`] = `
exports[`'preprocess' option should work with the "preprocessor" option #2: module 1`] = `
"// Imports
import ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___ from "../../src/runtime/getUrl.js";
var ___HTML_LOADER_IMPORT_0___ = new URL("./image.png.webp", import.meta.url);
Expand All @@ -38,16 +38,16 @@ var code = "<picture><source type=\\"image/webp\\" srcset=\\"" + ___HTML_LOADER_
export default code;"
`;
exports[`'process' option should work with the "preprocessor" option #2: result 1`] = `
exports[`'preprocess' option should work with the "preprocessor" option #2: result 1`] = `
"<picture><source type="image/webp" srcset="replaced_file_protocol_/webpack/public/path/image.png.webp"><img src="replaced_file_protocol_/webpack/public/path/image.png"></picture>
"
`;
exports[`'process' option should work with the "preprocessor" option #2: warnings 1`] = `[]`;
exports[`'preprocess' option should work with the "preprocessor" option #2: warnings 1`] = `[]`;
exports[`'process' option should work with the "preprocessor" option: errors 1`] = `[]`;
exports[`'preprocess' option should work with the "preprocessor" option: errors 1`] = `[]`;
exports[`'process' option should work with the "preprocessor" option: module 1`] = `
exports[`'preprocess' option should work with the "preprocessor" option: module 1`] = `
"// Imports
import ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___ from "../../src/runtime/getUrl.js";
var ___HTML_LOADER_IMPORT_0___ = new URL("./image.png", import.meta.url);
Expand All @@ -58,19 +58,19 @@ var code = "<div>\\n <p>Alexander Krasnoyarov</p>\\n <img src=\\"" + ___HTML_L
export default code;"
`;
exports[`'process' option should work with the "preprocessor" option: result 1`] = `
exports[`'preprocess' option should work with the "preprocessor" option: result 1`] = `
"<div>
<p>Alexander Krasnoyarov</p>
<img src="replaced_file_protocol_/webpack/public/path/image.png" alt="alt" />
<div>
"
`;
exports[`'process' option should work with the "preprocessor" option: warnings 1`] = `[]`;
exports[`'preprocess' option should work with the "preprocessor" option: warnings 1`] = `[]`;
exports[`'process' option should work with the Async "preprocessor" Function option #2: errors 1`] = `[]`;
exports[`'preprocess' option should work with the async "preprocessor" function option #2: errors 1`] = `[]`;
exports[`'process' option should work with the Async "preprocessor" Function option #2: module 1`] = `
exports[`'preprocess' option should work with the async "preprocessor" function option #2: module 1`] = `
"// Imports
import ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___ from "../../src/runtime/getUrl.js";
var ___HTML_LOADER_IMPORT_0___ = new URL("./image.png.webp", import.meta.url);
Expand All @@ -83,9 +83,9 @@ var code = "<picture><source type=\\"image/webp\\" srcset=\\"" + ___HTML_LOADER_
export default code;"
`;
exports[`'process' option should work with the Async "preprocessor" Function option #2: result 1`] = `
exports[`'preprocess' option should work with the async "preprocessor" function option #2: result 1`] = `
"<picture><source type="image/webp" srcset="replaced_file_protocol_/webpack/public/path/image.png.webp"><img src="replaced_file_protocol_/webpack/public/path/image.png"></picture>
"
`;
exports[`'process' option should work with the Async "preprocessor" Function option #2: warnings 1`] = `[]`;
exports[`'preprocess' option should work with the async "preprocessor" function option #2: warnings 1`] = `[]`;
16 changes: 8 additions & 8 deletions test/__snapshots__/validate-options.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -95,47 +95,47 @@ exports[`validate options should throw an error on the "sources" option with "tr
exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = `
"Invalid options object. HTML Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { preprocessor?, sources?, minimize?, esModule? }"
object { preprocessor?, postprocessor?, sources?, minimize?, esModule? }"
`;
exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = `
"Invalid options object. HTML Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { preprocessor?, sources?, minimize?, esModule? }"
object { preprocessor?, postprocessor?, sources?, minimize?, esModule? }"
`;
exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = `
"Invalid options object. HTML Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { preprocessor?, sources?, minimize?, esModule? }"
object { preprocessor?, postprocessor?, sources?, minimize?, esModule? }"
`;
exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = `
"Invalid options object. HTML Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { preprocessor?, sources?, minimize?, esModule? }"
object { preprocessor?, postprocessor?, sources?, minimize?, esModule? }"
`;
exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = `
"Invalid options object. HTML Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { preprocessor?, sources?, minimize?, esModule? }"
object { preprocessor?, postprocessor?, sources?, minimize?, esModule? }"
`;
exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = `
"Invalid options object. HTML Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { preprocessor?, sources?, minimize?, esModule? }"
object { preprocessor?, postprocessor?, sources?, minimize?, esModule? }"
`;
exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = `
"Invalid options object. HTML Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { preprocessor?, sources?, minimize?, esModule? }"
object { preprocessor?, postprocessor?, sources?, minimize?, esModule? }"
`;
exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = `
"Invalid options object. HTML Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { preprocessor?, sources?, minimize?, esModule? }"
object { preprocessor?, postprocessor?, sources?, minimize?, esModule? }"
`;
1 change: 1 addition & 0 deletions test/fixtures/gallery.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h2>Gallery</h2>
6 changes: 6 additions & 0 deletions test/fixtures/postprocessor.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<img src="image.png">
<img src="<%= 'Hello ' + (1+1) %>">
<img src="<%= require('./image.png') %>">
<img src="<%= new URL('./image.png', import.meta.url) %>">
<div><%= require('./gallery.html').default %></div>
<!--Works fine, but need improve testing <div>< %= (await import('./gallery.html')).default % ></div>-->
Loading

0 comments on commit 536a204

Please sign in to comment.