Skip to content

Commit 8dc7753

Browse files
committed
feat: add entryFilter option
1 parent b13b216 commit 8dc7753

119 files changed

Lines changed: 908 additions & 40 deletions

File tree

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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Change log
22

3+
## 3.11.0 (2024-04-23)
4+
5+
- feat: add `entryFilter` option to include or exclude entry files when the `entry` option is the path
6+
37
## 3.10.0 (2024-04-18)
48

59
- feat: add support the [CSS Modules](https://github.com/css-modules/css-modules) for styles imported in JS using the [css-loader modules](https://github.com/webpack-contrib/css-loader#modules) option.\

README.md

Lines changed: 102 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ See [boilerplate](https://github.com/webdiscus/webpack-html-scss-boilerplate)
472472
- [entry as a path](#option-entry-path) (find templates in a directory recursively)
473473
- [entry data](#option-entry-data) (pass data in the single template as an object or a file)
474474
- [entry dynamic](#option-entry-path) (entry as a path to template files)
475+
- [entryFilter](#option-entry-filter) (filter for entry dynamic)
475476
- [outputPath](#option-outputpath) (output path of HTML file)
476477
- [filename](#option-filename) (output filename of HTML file)
477478
- [js](#option-js) (options for JS)
@@ -542,7 +543,6 @@ See [boilerplate](https://github.com/webdiscus/webpack-html-scss-boilerplate)
542543
3. <a id="demo-sites" name="demo-sites"></a>
543544
Demo sites
544545
- Multiple page e-shop template (`Handlebars`) [demo](https://alpine-html-bootstrap.vercel.app/) | [source](https://github.com/webdiscus/demo-shop-template-bundler-plugin)
545-
- Design system NIHR: Components, Elements, Layouts (`Handlebars`) [demo](https://design-system.nihr.ac.uk) | [source](https://github.com/webdiscus/design-system)
546546
- Asia restaurant (`Nunjucks`) [demo](https://webdiscus.github.io/demo-asia-restaurant-bundler-plugin) | [source](https://github.com/webdiscus/demo-asia-restaurant-bundler-plugin)
547547
- 10up / Animation Best Practices [demo](https://animation.10up.com/) | [source](https://github.com/10up/animation-best-practices)
548548
1. <a id="usage-examples" name="usage-examples"></a>
@@ -1561,6 +1561,95 @@ new HtmlBundlerPlugin({
15611561
15621562
#### [↑ back to contents](#contents)
15631563
1564+
<a id="option-entry-filter" name="option-entry-filter"></a>
1565+
1566+
### `entryFilter`
1567+
1568+
Filter to process only matching template files.
1569+
This option works only if the [entry](#option-entry-path) option is a path.
1570+
1571+
Type:
1572+
```ts
1573+
type entryFilter =
1574+
| RegExp
1575+
| Array<RegExp>
1576+
| { includes?: Array<RegExp>; excludes?: Array<RegExp> }
1577+
| ((file: string) => void | false);
1578+
```
1579+
1580+
Default value:
1581+
1582+
```ts
1583+
{
1584+
includes: [
1585+
/\.(html|eta)$/,
1586+
],
1587+
excludes: []
1588+
}
1589+
```
1590+
1591+
The default `includes` property depends on the used [preprocessor](#loader-option-preprocessor) option.
1592+
Each preprocessor has its own filter to include from the entry path only relevant template files.
1593+
1594+
#### `entryFilter` as `RegExp`
1595+
1596+
The filter works as `include` only files that match the regular expressions.
1597+
For example:
1598+
```js
1599+
new HtmlBundlerPlugin({
1600+
entry: 'src/views/pages/',
1601+
entryFilter: /index\.html$/, // render only `index.html` files in all sub dirs
1602+
})
1603+
```
1604+
1605+
#### `entryFilter` as `Array<RegExp>`
1606+
1607+
The filter works as `include` only files that match one of the regular expressions.
1608+
For example:
1609+
```js
1610+
new HtmlBundlerPlugin({
1611+
entry: 'src/views/pages/',
1612+
entryFilter: [
1613+
// render only page specifically files, e.g.: `index.html`, `contact.html`, `about.html`
1614+
/index\.html$/,
1615+
/contact\.html$/,
1616+
/about\.html$/,
1617+
],
1618+
})
1619+
```
1620+
1621+
#### `entryFilter` as `{ includes: Array<RegExp>, excludes: Array<RegExp> }`
1622+
1623+
The filter includes only files that match one of the regular expressions, except excluded files.
1624+
For example:
1625+
```js
1626+
new HtmlBundlerPlugin({
1627+
entry: 'src/views/pages/',
1628+
entryFilter: {
1629+
includes: [/\.(html|eta)$/,], // render all `.html` and `.eta` template files
1630+
excludes: [/partial/], // except partial files
1631+
},
1632+
})
1633+
```
1634+
1635+
#### `entryFilter` as `callback`
1636+
1637+
In addition to the default `includes` filter, this filter works as `exclude` a file if it returns `false`.
1638+
If the callback returns `true` or nothing, then the file will be processed.
1639+
1640+
For example:
1641+
```js
1642+
new HtmlBundlerPlugin({
1643+
entry: 'src/views/pages/',
1644+
entryFilter: (file) => {
1645+
if (/partial/.test(file)) return false; // ignore files containing the `partial` in the path
1646+
},
1647+
})
1648+
```
1649+
The `file` argument is the absolute path of a template file.
1650+
1651+
#### [↑ back to contents](#contents)
1652+
15641653
<a id="option-outputpath" name="option-outputpath"></a>
15651654
15661655
### `outputPath`
@@ -2705,7 +2794,7 @@ The `HtmlBundlerPlugin.loader` will be added automatically.
27052794
27062795
For example, both configurations are functionally identical:
27072796
2708-
_1) the variant using the `loaderOptions`_ (recommended for common use cases)
2797+
_1) the variant using the `loaderOptions`_, **recommended** for common use cases:
27092798
27102799
```js
27112800
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
@@ -2715,18 +2804,18 @@ module.exports = {
27152804
entry: {
27162805
index: 'src/views/index.ejs',
27172806
},
2807+
// compile a template into HTML using `ejs` module
2808+
preprocessor: 'ejs',
27182809
loaderOptions: {
27192810
// resolve files specified in non-standard attributes 'data-src', 'data-srcset'
27202811
sources: [{ tag: 'img', attributes: ['data-src', 'data-srcset'] }],
2721-
// compile a template into HTML using `ejs` module
2722-
preprocessor: 'ejs',
27232812
},
27242813
}),
27252814
],
27262815
};
27272816
```
27282817
2729-
_2) the variant using the `module.rules`_
2818+
_2) the `low level` variant using the `module.rules`_:
27302819
27312820
```js
27322821
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
@@ -2753,10 +2842,13 @@ module.exports = {
27532842
};
27542843
```
27552844
2756-
For common use cases, the first option is recommended. So your config is smaller and cleaner.
2757-
2758-
The second variant use only for special cases, e.g. when you have templates with different syntax.
2759-
An example see by [How to use some different template engines](#recipe-diff-templates).
2845+
> ⚠️ **Warning**
2846+
>
2847+
> If you don't know what it's for, don't define a module rule for template files.
2848+
> The plugin automatically configures this rule.
2849+
>
2850+
> Define this rule only for special cases, e.g. when you have templates with different templating engines.\
2851+
> An example see by [How to use some different template engines](#recipe-diff-templates).
27602852
27612853
> **Note**
27622854
>
@@ -3151,7 +3243,7 @@ module.exports = {
31513243
> **Note**
31523244
>
31533245
> Since the `v2.2.0` is available new syntax, the [preprocessor](#option-preprocessor)
3154-
> and the [preprocessorOptions](#option-preprocessor) can be defined directly in the plugin option
3246+
> and the [preprocessorOptions](#option-preprocessor) should be defined directly in the plugin option
31553247
> to simplify the config:
31563248
>
31573249
> ```js

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": "3.10.0",
3+
"version": "3.11.0",
44
"description": "HTML bundler plugin for webpack handles a template as an entry point, extracts CSS and JS from their sources referenced in HTML, supports template engines like Eta, EJS, Handlebars, Nunjucks.",
55
"keywords": [
66
"html",

src/Plugin/AssetCompiler.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -268,11 +268,8 @@ class AssetCompiler {
268268
PluginService.watchRun();
269269
});
270270

271-
// TODO:
272-
// - before AssetEntry.initEntry() init preprocessor by Loader.Options to get `test`
273-
274271
// entry option
275-
AssetEntry.initEntry();
272+
AssetEntry.init({ fs: this.fs, entryLibrary: this.entryLibrary, collection: Collection });
276273
compiler.hooks.entryOption.tap(pluginName, this.afterProcessEntry);
277274

278275
// watch changes for entry-points
@@ -289,7 +286,7 @@ class AssetCompiler {
289286
PluginService.plugin = AssetCompiler;
290287
PluginService.compilation = compilation;
291288

292-
AssetEntry.init({ compilation, entryLibrary: this.entryLibrary, collection: Collection });
289+
AssetEntry.initCompilation(compilation);
293290
AssetTrash.init(compilation);
294291
CssExtractModule.init(compilation);
295292
Collection.init({ compilation, assetEntry: AssetEntry, hooks: AssetCompiler.getHooks(compilation) });

src/Plugin/AssetEntry.js

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
const fs = require('fs');
21
const path = require('path');
32
const Option = require('./Option');
43
const PluginService = require('./PluginService');
@@ -82,21 +81,18 @@ class AssetEntry {
8281
static entryNamePrefix = '__bundler-plugin-entry__';
8382

8483
/**
85-
* Init the object, inject dependencies.
84+
* Init the asset entry.
8685
*
87-
* @param {Compilation} compilation
86+
* @param {FileSystem} fs
8887
* @param {Object} entryLibrary
8988
* @param {Collection} collection
9089
*/
91-
static init({ compilation, entryLibrary, collection }) {
92-
this.compilation = compilation;
90+
static init({ fs, entryLibrary, collection }) {
91+
this.fs = fs;
9392
this.entryLibrary = entryLibrary;
9493
this.collection = collection;
95-
this.fs = compilation.compiler.inputFileSystem.fileSystem;
9694
this.entryIdRegexp = new RegExp(`\\?${this.entryIdKey}=(\\d+)`);
97-
}
9895

99-
static initEntry() {
10096
const { entry } = Option.webpackOptions;
10197
let pluginEntry;
10298

@@ -136,26 +132,60 @@ class AssetEntry {
136132
Option.webpackOptions.entry = pluginEntry;
137133
}
138134

135+
/**
136+
* Init for the compilation.
137+
*
138+
* @param {Compilation} compilation
139+
*/
140+
static initCompilation(compilation) {
141+
this.compilation = compilation;
142+
}
143+
139144
/**
140145
* Returns dynamic entries read recursively from the entry path.
141146
*
142147
* @return {Object}
143148
* @throws
144149
*/
145150
static getDynamicEntry() {
146-
//const { fs } = this; // TODO: fix error with injected fs
151+
const { fs } = this;
147152
const dir = Option.get().entry;
153+
const entryFilter = Option.get().entryFilter;
154+
const isFunctionEntryFilter = typeof entryFilter === 'function';
155+
let includes = [Option.get().test];
156+
let excludes = [];
157+
158+
if (entryFilter && !isFunctionEntryFilter) {
159+
if (entryFilter instanceof RegExp) {
160+
includes = [entryFilter];
161+
} else {
162+
if (Array.isArray(entryFilter)) {
163+
includes = entryFilter;
164+
} else {
165+
if ('includes' in entryFilter && Array.isArray(entryFilter.includes)) {
166+
includes = entryFilter.includes;
167+
}
168+
if ('excludes' in entryFilter && Array.isArray(entryFilter.excludes)) {
169+
excludes = entryFilter.excludes;
170+
}
171+
}
172+
}
173+
}
148174

149175
try {
150176
if (!fs.lstatSync(dir).isDirectory()) optionEntryPathException(dir);
151177
} catch (error) {
152178
optionEntryPathException(dir);
153179
}
154180

155-
const files = readDirRecursiveSync(dir, { fs, includes: [Option.get().test] });
181+
const files = readDirRecursiveSync(dir, { fs, includes, excludes });
156182
const entry = {};
157183

158184
files.forEach((file) => {
185+
if (isFunctionEntryFilter && entryFilter(file) === false) {
186+
return;
187+
}
188+
159189
const outputFile = path.relative(dir, file);
160190
const name = outputFile.slice(0, outputFile.lastIndexOf('.'));
161191
const entryName = this.createEntryName(name);
File renamed without changes.
File renamed without changes.

test/cases/option-entry-as-array/expected/news/sport.html renamed to test/cases/option-entry-array/expected/news/sport.html

File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)