Skip to content

Commit c448365

Browse files
committed
feat: add the fixed preload filter
1 parent 09fb130 commit c448365

48 files changed

Lines changed: 364 additions & 51 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,26 @@
11
# Changelog
22

3-
## 4.14.0 (2025-01-19)
3+
## 4.15.0-beta.0 (2025-01-20)
4+
5+
- feat: add the fixed preload filter:
6+
```ts
7+
type PreloadFilter =
8+
| RegExp
9+
| Array<RegExp>
10+
| { includes?: Array<RegExp>; excludes?: Array<RegExp> }
11+
| ((asset: { sourceFiles: Array<string>; outputFile: string }) => void | boolean); // <= BRAKING CHANGES compared to v4.14.0 (DEPRECATED)
12+
```
13+
14+
## 4.14.0 (2025-01-19) DEPRECATED (filter API will be changed in next version)
415

516
- feat: add the `filter` for the `preload` option
17+
```ts
18+
type AdvancedFilter =
19+
| RegExp
20+
| Array<RegExp>
21+
| { includes?: Array<RegExp>; excludes?: Array<RegExp> }
22+
| ((value: string) => void | true | false); // <= string argument DEPRECATED
23+
```
624

725
## 4.13.0 (2025-01-18)
826

README.md

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2501,7 +2501,7 @@ Type:
25012501
```ts
25022502
type Preload = Array<{
25032503
test: RegExp;
2504-
filter?: AdvancedFilter;
2504+
filter?: PreloadFilter;
25052505
as?: string;
25062506
rel?: string;
25072507
type?: string;
@@ -2510,11 +2510,11 @@ type Preload = Array<{
25102510
```
25112511
25122512
```ts
2513-
type AdvancedFilter =
2513+
type PreloadFilter =
25142514
| RegExp
25152515
| Array<RegExp>
25162516
| { includes?: Array<RegExp>; excludes?: Array<RegExp> }
2517-
| ((value: string) => void | true | false);
2517+
| ((asset: { sourceFiles: Array<string>; outputFile: string }) => void | boolean);
25182518
```
25192519
25202520
Default: `null`
@@ -2572,9 +2572,12 @@ For example:
25722572
By default, all files matching the `test` option will be preloaded.
25732573
You can use the `filter` to preload specific files individually.
25742574
2575-
The `AdvancedFilter` type provides a versatile way to define filtering logic,
2575+
The `PreloadFilter` type provides a versatile way to define filtering logic,
25762576
from simple patterns to complex inclusion and exclusion criteria.
25772577
2578+
The filter RegExp matches both the source and output files.
2579+
If you need to match source or output files separately use the filter function, see below.
2580+
25782581
Here's are supported filter formats:
25792582
25802583
- `RegExp`\
@@ -2607,12 +2610,40 @@ Here's are supported filter formats:
26072610
excludes: [/exclude-this/,],
26082611
}
26092612
```
2610-
- `(value: string) => void | false`\
2613+
- `(asset: { sourceFiles: Array<string>; outputFile: string }) => void | boolean`\
26112614
Custom logic provides maximum flexibility for filtering that cannot be expressed with regular expressions.\
2612-
A custom filter function takes an output asset file as input and decides whether it matches based on the return value:
2615+
A custom filter function takes the source files and the output asset file as input, and decides whether they match based on the return value:
26132616
- Returns `void` (`undefined`) or `true` if the value passes the filter.
26142617
- Returns `false` if the value fails the filter.
26152618
2619+
> [!NOTE]
2620+
> Only `style` type may have many files in the `sourceFiles`.\
2621+
> If many styles are imported in one JS file, then all extracted CSS will be squashed into single CSS file with the name of a JS parent file.
2622+
> In this case the `sourceFiles` will have many files.
2623+
>
2624+
> Usage for `style` type:
2625+
> ```js
2626+
> {
2627+
> test: /\.(s?css|less)$/,
2628+
> filter: ({ sourceFiles, outputFile }) => {
2629+
> return !sourceFiles.some((sourceFile) => /noPreload/.test(sourceFile));
2630+
> },
2631+
> as: 'style',
2632+
> },
2633+
> ```
2634+
>
2635+
> All other types have only one file in the `sourceFiles` array.
2636+
> You can use the argument destructor to extract only first source file.
2637+
>
2638+
> Usage for all other types:
2639+
> ```js
2640+
> {
2641+
> test: /\.(js|ts)$/,
2642+
> filter: ({ sourceFiles: [sourceFile], outputFile }) => !/noPreload/.test(outputFile),
2643+
> as: 'script',
2644+
> },
2645+
> ```
2646+
26162647
For example, preload all JS files except dynamically imported (async chunks).
26172648
26182649
There is the _app.js_:
@@ -2634,6 +2665,7 @@ preload: [
26342665
{
26352666
test: /\.(js|ts)$/,
26362667
filter: {
2668+
// matches source and output files
26372669
excludes: [/asyncChunk/],
26382670
},
26392671
as: 'script',
@@ -2646,7 +2678,7 @@ The same effect using the `filter` function:
26462678
preload: [
26472679
{
26482680
test: /\.(js|ts)$/,
2649-
filter: (assetFile) => !/asyncChunk/.test(assetFile),
2681+
filter: ({ sourceFiles: [sourceFile], outputFile }) => !/asyncChunk/.test(sourceFile)),
26502682
as: 'script',
26512683
},
26522684
],

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "html-bundler-webpack-plugin",
3-
"version": "4.14.0",
3+
"version": "4.15.0-beta.0",
44
"description": "Generates complete single-page or multi-page website from source assets. Build-in support for Markdown, Eta, EJS, Handlebars, Nunjucks, Pug. Alternative to html-webpack-plugin.",
55
"keywords": [
66
"html",

src/Plugin/Collection.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -341,25 +341,25 @@ class Collection {
341341
injectedChunks.add(chunkFile);
342342
}
343343

344-
const assetFile = this.pluginOption.getAssetOutputFile(chunkFile, entryFile);
345344
const inline = this.pluginOption.isInlineJs(resource, chunkFile);
345+
const assetFile = this.pluginOption.getAssetOutputFile(chunkFile, entryFile);
346346

347347
splitChunkFiles.add(chunkFile);
348348
data.chunks.push({ inline, chunkFile, assetFile });
349349
}
350350

351351
// dynamic imported chunks
352-
for (let chunkFile of childrenFiles) {
352+
for (let { sourceFile, outputFile: chunkFile } of childrenFiles) {
353353
if (hasChunks) {
354354
if (injectedChunks.has(chunkFile)) continue;
355355
injectedChunks.add(chunkFile);
356356
}
357357

358-
const assetFile = this.pluginOption.getAssetOutputFile(chunkFile, entryFile);
359358
const inline = this.pluginOption.isInlineJs(resource, chunkFile);
359+
const assetFile = this.pluginOption.getAssetOutputFile(chunkFile, entryFile);
360360

361361
splitChunkFiles.add(chunkFile);
362-
data.children.push({ inline, chunkFile, assetFile });
362+
data.children.push({ inline, chunkFile, assetFile, sourceFile });
363363
}
364364

365365
const entryData = this.data.get(entryFile);
@@ -390,14 +390,24 @@ class Collection {
390390

391391
/**
392392
* @param {Entrypoint} entrypoint
393-
* @return {Array<string>}
393+
* @return {Array<{sourceFile: string, outputFile: string}>}
394394
*/
395395
#getChildrenFiles(entrypoint) {
396396
let files = [];
397397
const children = entrypoint.getChildren();
398398

399399
for (const chunkGroup of children) {
400-
let chunkFiles = chunkGroup.getFiles();
400+
// Note: there is no legal way to get the source file of the asyncChunk
401+
// except using the private property `_modulePreOrderIndices`.
402+
const [firsModule] = chunkGroup._modulePreOrderIndices.entries().next().value;
403+
404+
// The first module in the modulePreOrderIndices is the self module of the imported asyncChunk,
405+
// other modules are dependencies imported in the first module.
406+
// This is very strange structure, but other not exists.
407+
const sourceFile = firsModule.resource;
408+
409+
let chunkFiles = chunkGroup.getFiles().map((value) => ({ sourceFile, outputFile: value }));
410+
401411
if (chunkFiles) {
402412
files.push(...chunkFiles);
403413
}

src/Plugin/Option.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ class Option {
759759
* Normalize the filter option defined by user and create inner structure of one.
760760
*
761761
* @param {RegExp} test
762-
* @param {AdvancedFilter} filter
762+
* @param {PreloadFilter} filter
763763
* @return {{includes: RegExp[], excludes: RegExp[], fn: function}}
764764
*/
765765
normalizeAdvancedFiler(test, filter) {
@@ -795,7 +795,7 @@ class Option {
795795
/**
796796
* Apply the advanced filter to a value.
797797
*
798-
* @param {string} value
798+
* @param {string | {sourceFiles: Array<string>, outputFile: string}} value
799799
* @param {NormalizedAdvancedFilter} filter
800800
* @return {boolean}
801801
*/
@@ -804,9 +804,17 @@ class Option {
804804

805805
const hasIncludes = includes.length > 0;
806806
const hasExcludes = excludes.length > 0;
807+
const values = [];
807808

808-
const isIncluded = !hasIncludes || includes.some((regex) => regex.test(value));
809-
const isExcluded = hasExcludes && excludes.some((regex) => regex.test(value));
809+
if (typeof filter === 'string') {
810+
values.push(value);
811+
} else {
812+
if ('sourceFiles' in value) values.push(...value.sourceFiles);
813+
if ('outputFile' in value) values.push(value.outputFile);
814+
}
815+
816+
const isIncluded = !hasIncludes || includes.some((regex) => values.some((value) => regex.test(value)));
817+
const isExcluded = hasExcludes && excludes.some((regex) => values.some((value) => regex.test(value)));
810818

811819
return isIncluded && !isExcluded && fn(value) !== false;
812820
}

src/Plugin/Preload.js

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,21 +151,34 @@ class Preload {
151151
if (Array.isArray(item.chunks)) {
152152
// js
153153
for (let { chunkFile, assetFile } of item.chunks) {
154-
if (this.pluginOption.applyAdvancedFiler(assetFile, conf._opts.filter)) {
154+
// sourceFiles contain only one file
155+
let sourceFiles = [item.resource];
156+
let outputFile = assetFile;
157+
158+
if (this.pluginOption.applyAdvancedFiler({ sourceFiles, outputFile }, conf._opts.filter)) {
155159
preloadAssets.set(assetFile, conf._opts);
156160
}
157161
}
158162
} else {
159163
// css, images, fonts, etc
160-
if (this.pluginOption.applyAdvancedFiler(item.assetFile, conf._opts.filter)) {
164+
// sourceFiles may contain many file, when many style files are imported in a JS file,
165+
// then all generated CSS contents will be squashed into one CSS file
166+
let sourceFiles = Array.isArray(item.resource) ? item.resource : [item.resource];
167+
let outputFile = item.assetFile;
168+
169+
if (this.pluginOption.applyAdvancedFiler({ sourceFiles, outputFile }, conf._opts.filter)) {
161170
preloadAssets.set(item.assetFile, conf._opts);
162171
}
163172
}
164173

165174
// dynamic imported modules, asyncChunks
166175
if (Array.isArray(item.children)) {
167-
for (let { chunkFile, assetFile } of item.children) {
168-
if (this.pluginOption.applyAdvancedFiler(assetFile, conf._opts.filter)) {
176+
for (let { chunkFile, assetFile, sourceFile } of item.children) {
177+
// sourceFiles contain only one file
178+
let sourceFiles = [sourceFile];
179+
let outputFile = assetFile;
180+
181+
if (this.pluginOption.applyAdvancedFiler({ sourceFiles, outputFile }, conf._opts.filter)) {
169182
preloadAssets.set(assetFile, conf._opts);
170183
}
171184
}
@@ -180,12 +193,14 @@ class Preload {
180193
const conf = options.find(({ test }) => test.test(assetItem.resource));
181194
if (!conf) continue;
182195

183-
let preloadFile = assetItem.issuer
196+
// sourceFiles contain only one file
197+
let sourceFiles = [assetItem.resource];
198+
let outputFile = assetItem.issuer
184199
? this.getPreloadFile(data.entry, assetItem.issuer, assetItem.assetFile)
185200
: assetItem.assetFile;
186201

187-
if (this.pluginOption.applyAdvancedFiler(preloadFile, conf._opts.filter)) {
188-
preloadAssets.set(preloadFile, conf._opts);
202+
if (this.pluginOption.applyAdvancedFiler({ sourceFiles, outputFile }, conf._opts.filter)) {
203+
preloadAssets.set(outputFile, conf._opts);
189204
}
190205
}
191206
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@font-face {
2+
font-family: 'OpenSans';
3+
src: url(../fonts/open-sans-regular.woff2) format('woff2');
4+
font-style: normal;
5+
}
6+
7+
h1 {
8+
color: orangered;
9+
}
10+
.module-b {
11+
color: chartreuse;
12+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@font-face {
2+
font-family: 'OpenSans';
3+
src: url(../fonts/open-sans-regular.woff2) format('woff2');
4+
font-style: normal;
5+
}
6+
7+
h1 {
8+
color: orangered;
9+
}
Binary file not shown.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Test</title>
5+
<link rel="preload" href="../js/main.9e75183b.js" as="script">
6+
<link rel="preload" href="../js/module1.chunk.js" as="script">
7+
<link rel="preload" href="../css/style.0d25913e.css" as="style">
8+
<link rel="preload" href="../fonts/open-sans-regular.woff2" as="font" type="font/woff2" crossorigin>
9+
<link href="../css/style.0d25913e.css" rel="stylesheet">
10+
<link href="../css/main.5b01e96e.css" rel="stylesheet">
11+
<script src="../js/main.9e75183b.js" defer="defer"></script>
12+
</head>
13+
<body>
14+
<h1>Hello World!</h1>
15+
<script src="../js/noPreload.2382a7e6.js" defer="defer"></script>
16+
</body>
17+
</html>

0 commit comments

Comments
 (0)