Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added the postprocessor option #518

Merged
merged 4 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also disable cache in cpsell. It doesn't differ much from no-cache

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has a big changes when you have a lot of files, I don't think we need disable it, we use cache everywhere

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

/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`] = `[]`;
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>-->
53 changes: 53 additions & 0 deletions test/postprocessor-option.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
compile,
execute,
getCompiler,
getErrors,
getModuleSource,
getWarnings,
readAsset,
} from "./helpers";

describe("'postprocess' option", () => {
it('should work with the "postprocessor" option', async () => {
const compiler = getCompiler("postprocessor.html", {
postprocessor: (content, loaderContext) => {
expect(typeof content).toBe("string");
expect(loaderContext).toBeDefined();

return content.replace(/<%=/g, '" +').replace(/%>/g, '+ "');
},
});
const stats = await compile(compiler);

expect(getModuleSource("./postprocessor.html", stats)).toMatchSnapshot(
"module",
);
expect(
execute(readAsset("main.bundle.js", compiler, stats)),
).toMatchSnapshot("result");
expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});

it('should work with Async "postprocessor" Function option', async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
it('should work with Async "postprocessor" Function option', async () => {
it('should work with async "postprocessor" function option', async () => {

const compiler = getCompiler("preprocessor.hbs", {
postprocessor: async (content, loaderContext) => {
await expect(typeof content).toBe("string");
await expect(loaderContext).toBeDefined();

return content.replace(/<%=/g, '" +').replace(/%>/g, '+ "');
},
});
const stats = await compile(compiler);

expect(getModuleSource("./preprocessor.hbs", stats)).toMatchSnapshot(
"module",
);
expect(
execute(readAsset("main.bundle.js", compiler, stats)),
).toMatchSnapshot("result");
expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});
});
3 changes: 2 additions & 1 deletion test/preprocessor-option.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
readAsset,
} from "./helpers";

describe("'process' option", () => {
describe("'preprocess' option", () => {
it('should work with the "preprocessor" option', async () => {
const compiler = getCompiler("preprocessor.hbs", {
preprocessor: (content, loaderContext) => {
Expand Down Expand Up @@ -110,6 +110,7 @@ describe("'process' option", () => {
expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});

it('should work with the Async "preprocessor" Function option #2', async () => {
const plugin = posthtmlWebp();
const compiler = getCompiler("posthtml.html", {
Expand Down
Loading