html-bundler-webpack-plugin
Advanced tools
Comparing version 0.8.0 to 0.9.0
# Change log | ||
## 0.9.0 (2023-02-04) | ||
- feat(BREAKING CHANGE): the 3rd argument `data` of the `preprocessor` has been moved to the 2nd argument as a property\ | ||
`v0.9.0`: `preprocessor: (content, { resourcePath, data }) => {}` <= NEW syntax\ | ||
`v0.8.0`: `preprocessor: (content, { resourcePath }, data) => {}` <= old syntax | ||
- fix: avoids an additional query param for internal use in the module's `resource` property | ||
- fix: remove info comments before inlined SVG | ||
- docs: add description how to pass data into template using new option `entry` | ||
## 0.8.0 (2023-02-01) | ||
- feat: add `entry` plugin option, this has same API as Webpack entry, but have additional `data` property | ||
- feat: pass custom data into `preprocessor` via additional `data` property of `entry` plugin option | ||
- feat: add `entry` plugin option, this has same API as Webpack entry plus additional `data` property | ||
- feat: add 3rd `data` argument of the `preprocessor` to pass template specific data: | ||
```js | ||
module.exports = { | ||
plugins: [ | ||
new HtmlBundlerPlugin({ | ||
entry: { // <= NEW `entry` option | ||
index: { | ||
import: 'src/views/template.html', | ||
data: { // <= NEW `data` property | ||
title: 'Home', | ||
}, | ||
}, | ||
}, | ||
}), | ||
], | ||
module: { | ||
rules: [ | ||
{ | ||
test: /\.(html)$/, | ||
loader: HtmlBundlerPlugin.loader, | ||
options: { | ||
preprocessor: (content, { resourcePath }, data) => { // <= NEW 3rd `data` argument | ||
return render(content, data); | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
}; | ||
``` | ||
- feat: support split chunk | ||
@@ -7,0 +45,0 @@ |
{ | ||
"name": "html-bundler-webpack-plugin", | ||
"version": "0.8.0", | ||
"version": "0.9.0", | ||
"description": "HTML bundler plugin for webpack handels HTML templates as entry point and extracts CSS, JS, images from their sources loaded in HTML.", | ||
@@ -83,2 +83,3 @@ "keywords": [ | ||
"css-loader": "^6.7.3", | ||
"ejs": "^3.1.8", | ||
"handlebars": "^4.7.7", | ||
@@ -89,3 +90,3 @@ "jest": "^29.4.1", | ||
"responsive-loader": "^3.1.2", | ||
"sass": "^1.57.1", | ||
"sass": "^1.58.0", | ||
"sass-loader": "^13.2.0", | ||
@@ -92,0 +93,0 @@ "sharp": "^0.31.3", |
369
README.md
<div align="center"> | ||
<h1> | ||
<img height="120" src="https://user-images.githubusercontent.com/30186107/29488525-f55a69d0-84da-11e7-8a39-5476f663b5eb.png"> | ||
<img height="120" src="https://webpack.js.org/assets/icon-square-big.svg"> | ||
<a href="https://github.com/webdiscus/html-bundler-webpack-plugin"><br> | ||
<img height="200" src="https://raw.githubusercontent.com/webdiscus/html-bundler-webpack-plugin/master/images/plugin-logo.png"> | ||
<br> | ||
<a href="https://github.com/webdiscus/html-bundler-webpack-plugin"> | ||
HTML Bundler Plugin for Webpack | ||
@@ -19,9 +19,8 @@ </a> | ||
The plugin allows to use an HTML file or a template as a starting point for collecting all the dependencies used in your web application. | ||
The plugin make Webpack setup easily and intuitive. | ||
This plugin allows to use an HTML file or a template as a starting point for collecting all the dependencies used in your web application. | ||
This plugin does exactly what you want: automatically extracts JS, CSS, images, fonts from their sources loaded directly in HTML. | ||
The generated HTML contains output hashed filenames of processed source files. | ||
The purpose of this plugin is to make Webpack setup much easier and intuitiver than with other plugins like | ||
`html-webpack-plugin` `mini-css-extract-plugin`. | ||
💡 **Highlights** | ||
@@ -33,3 +32,3 @@ | ||
- You can inline JS, CSS, SVG, images **without additional plugins and loaders**. | ||
- You can use a template engine, e.g. [Nunjucks](https://mozilla.github.io/nunjucks/), [Handlebars](https://handlebarsjs.com) and others **without template loaders** | ||
- You can use a template engine, e.g. [EJS](https://ejs.co), [Nunjucks](https://mozilla.github.io/nunjucks/), [Handlebars](https://handlebarsjs.com) and others **without template loaders**. | ||
- This plugin works like the [pug-plugin](https://github.com/webdiscus/pug-plugin) but the entry point is a `HTML` template. | ||
@@ -74,3 +73,3 @@ | ||
Add the HTML templates in the Webpack entry: | ||
Add the HTML templates in the `entry` option (syntax is identical to [Webpack entry](https://webpack.js.org/configuration/entry-context/#entry)): | ||
@@ -82,14 +81,14 @@ ```js | ||
module.exports = { | ||
entry: { | ||
// define HTML templates here | ||
index: './src/views/home/index.html', // output dist/index.html | ||
}, | ||
resolve: { | ||
alias: { | ||
'@images': path.join(__dirname, './src/images'), | ||
'@images': path.join(__dirname, 'src/images'), | ||
}, | ||
}, | ||
plugins: [ | ||
// enable processing of HTML templates defined in Webpack entry | ||
new HtmlBundlerPlugin(), | ||
new HtmlBundlerPlugin({ | ||
entry: { | ||
// define HTML templates here | ||
index: 'src/views/home/index.html', // output dist/index.html | ||
}, | ||
}), | ||
], | ||
@@ -111,7 +110,17 @@ module: { | ||
--- | ||
1. [Install and Quick start](#install) | ||
2. [Features](#features) | ||
1. [Features](#features) | ||
2. [Install and Quick start](#install) | ||
3. [Plugin options](#plugin-options) | ||
- [test](#option-test) (process only templates matching RegExp) | ||
- [entry](#option-entry) (define HTML templates) | ||
- [outputPath](#option-outputPath) (output path of HTML file) | ||
- [filename](#option-filename) (output filename of HTML file) | ||
- [css](#option-css) (output filename of extracted CSS) | ||
- [js](#option-js) (output filename of extracted JS) | ||
- [postprocess](#option-postprocess) | ||
- [extractComments](#option-extractComments) | ||
- [verbose](#option-verbose) | ||
3. [Loader options](#loader-options) | ||
- [sources](#loader-option-sources) | ||
- [preprocessor](#loader-option-preprocessor) (for custom templates) | ||
4. [Recipes](#recipes) | ||
@@ -124,5 +133,4 @@ - [How to use source images in HTML](#recipe-use-images-in-html) | ||
- [How to inline SVG, PNG images in HTML](#recipe-inline-image) | ||
- [How to use a template engine, e.g. Handlebars](#recipe-template-engine) | ||
- [How to pass data into template](#recipe-pass-data) | ||
- [How to pass different data by multipage configuration](#recipe-pass-data-multipage) | ||
- [How to use a template engine](#recipe-template-engine) | ||
- [How to pass data into templates](#recipe-pass-data-to-templates) | ||
- [How to use HMR live reload](#recipe-hmr) | ||
@@ -133,4 +141,3 @@ | ||
- HTML file is the entry point for all resources (styles, scripts) | ||
- handels HTML files defined in Webpack entry | ||
- HTML template is the entry point for all resources (styles, scripts) | ||
- extracts CSS from source style loaded in HTML via a `<link>` tag | ||
@@ -189,11 +196,10 @@ - extracts JS from source script loaded in HTML via a `<script>` tag | ||
entry: { | ||
// define HTML files here | ||
index: './src/views/home/index.html', // output dist/index.html | ||
'pages/about': './src/views/about/index.html', // output dist/pages/about.html | ||
// ... | ||
}, | ||
plugins: [ | ||
new HtmlBundlerPlugin({ | ||
entry: { | ||
// define HTML files here | ||
index: 'src/views/home/index.html', // output dist/index.html | ||
'pages/about': 'src/views/about/index.html', // output dist/pages/about.html | ||
// ... | ||
}, | ||
js: { | ||
@@ -225,2 +231,8 @@ // output filename of extracted JS from source script loaded in HTML via `<script>` tag | ||
> **Note** | ||
> | ||
> Since the version `0.9.0`, you can define HTML templates in the `entry` option of the plugin. | ||
> If is used the `entry` option of the plugin, then the origin Webpack `entry` option should be undefined. | ||
--- | ||
@@ -231,6 +243,3 @@ | ||
### `verbose` | ||
Type: `boolean` Default: `false`<br> | ||
Display information about extracted files. | ||
<a id="option-test" name="option-test" href="#option-test"></a> | ||
### `test` | ||
@@ -242,3 +251,65 @@ Type: `RegExp` Default: `/\.html$/`<br> | ||
<a id="plugin-option-outputPath" name="plugin-option-outputPath" href="#plugin-option-outputPath"></a> | ||
<a id="option-entry" name="option-entry" href="#option-entry"></a> | ||
### `entry` | ||
Type: `object` is identical to [Webpack entry](https://webpack.js.org/configuration/entry-context/#entry) | ||
plus additional `data` property. | ||
Define your HTML files or templates in the entry option. | ||
HTML is a starting point for collecting all the dependencies used in your web application. | ||
Specify source scripts (JS, TS) and styles (CSS, SCSS, etc.) directly in HTML. | ||
The plugin automatically extracts JS, CSS from their sources specified in HTML. | ||
#### Simple syntax | ||
The key of an entry object is the `output file` w/o extension, relative by the [`outputPath`](#option-outputPath) option.\ | ||
The value is the `source file`, absolute or relative by the Webpack config file. | ||
```js | ||
{ | ||
entry: { | ||
index: 'src/views/home/index.html', // => dist/index.html | ||
'pages/about/index': 'src/views/about.html', // => dist/pages/about/index.html | ||
}, | ||
} | ||
``` | ||
#### Advanced syntax | ||
The entry value might be an object: | ||
```ts | ||
type entryValue = { | ||
import: string, | ||
filename: string | ||
data: object, | ||
} | ||
``` | ||
- `import` - a source file, absolute or relative by the Webpack config file | ||
- `filename` - an output file, relative by the 'outputPath' option | ||
- `data` - an object with variables passed into [`preprocessor`](#loader-option-preprocessor) to render a template | ||
Usage example: | ||
```js | ||
{ | ||
entry: { | ||
'pages/about/index': { // output file as the key | ||
import: 'src/views/about.html', // source template file | ||
data: { | ||
title: 'About', | ||
} | ||
}, | ||
contact: { | ||
import: 'src/views/contact.html', | ||
filename: 'pages/contact/index.html', // output file as the 'filename' property | ||
}, | ||
}, | ||
} | ||
``` | ||
<a id="option-outputPath" name="option-outputPath" href="#option-outputPath"></a> | ||
### `outputPath` | ||
@@ -248,6 +319,8 @@ Type: `string` Default: `webpack.options.output.path`<br> | ||
<a id="plugin-option-filename" name="plugin-option-filename" href="#plugin-option-filename"></a> | ||
<a id="option-filename" name="option-filename" href="#option-filename"></a> | ||
### `filename` | ||
Type: `string | Function` Default: `[name].html`<br> | ||
The name of output file. | ||
The output filename relative by the [`outputPath`](#option-outputPath) option. | ||
- If type is `string` then following substitutions (see [output.filename](https://webpack.js.org/configuration/output/#template-strings) for chunk-level) are available in template string: | ||
@@ -265,2 +338,4 @@ - `[id]` The ID of the chunk. | ||
<a id="option-css" name="option-css" href="#option-css"></a> | ||
### `css` | ||
@@ -272,11 +347,14 @@ Type: `Object`\ | ||
test: /\.(css|scss|sass|less|styl)$/, | ||
verbose: false, | ||
filename: '[name].css', | ||
outputPath: null, | ||
verbose: false, | ||
} | ||
``` | ||
The `filename` property see by [filename option](#plugin-option-filename). | ||
The `outputPath` property see by [outputPath option](#plugin-option-outputPath). | ||
The option to extract CSS from a style source file loaded in the HTML tag: | ||
- `test` - an RegEpx to process all source styles that pass test assertion | ||
- `filename` - an output filename of extracted CSS. Details see by [filename option](#option-filename).\ | ||
- `outputPath` - an output path of extracted CSS. Details see by [outputPath option](#option-outputPath). | ||
- `verbose` - enable/disable display process information for styles | ||
This is the option to extract CSS from a style source file specified in the HTML tag: | ||
```html | ||
@@ -288,3 +366,3 @@ <link href="./style.scss" rel="stylesheet"> | ||
> | ||
> Don't import source styles in JavaScript! Styles must be loaded directly in HTML. | ||
> Don't import source styles in JavaScript! Styles must be specified directly in HTML. | ||
@@ -316,2 +394,4 @@ The default CSS output filename is `[name].css`. | ||
<a id="option-js" name="option-js" href="#option-js"></a> | ||
### `js` | ||
@@ -322,12 +402,15 @@ Type: `Object`\ | ||
{ | ||
verbose: false, | ||
filename: '[name].js', | ||
outputPath: null, | ||
verbose: false, | ||
} | ||
``` | ||
The `filename` property see by [filename option](#plugin-option-filename). | ||
The `outputPath` property see by [outputPath option](#plugin-option-outputPath). | ||
The `test` property not exist because all JS files loaded in `<script>` tag are automatically detected. | ||
The option to extract JS from a script source file loaded in the HTML tag: | ||
- `filename` - an output filename of extracted JS. Details see by [filename option](#option-filename).\ | ||
- `outputPath` - an output path of extracted CSS. Details see by [outputPath option](#option-outputPath). | ||
- `verbose` - enable/disable display process information for styles | ||
The `test` property absent because all JS files specified in `<script>` tag are automatically detected. | ||
This is the option to extract JS from a script source file specified in the HTML tag: | ||
```html | ||
@@ -357,22 +440,4 @@ <script src="./main.js"></script> | ||
### `extractComments` | ||
Type: `boolean` Default: `false`<br> | ||
Enable / disable extraction of comments to `*.LICENSE.txt` file. | ||
When using `splitChunks` optimization for node modules containing comments, | ||
Webpack extracts those comments into a separate text file. | ||
By default, the plugin don't create such unwanted text files. | ||
But if you want to extract files like `*.LICENSE.txt`, set this option to `true`: | ||
```js | ||
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); | ||
module.exports = { | ||
plugins: [ | ||
new HtmlBundlerPlugin({ | ||
extractComments: true, | ||
}), | ||
], | ||
}; | ||
``` | ||
<a id="option-postprocess" name="option-postprocess" href="#option-postprocess"></a> | ||
### `postprocess` | ||
@@ -420,2 +485,30 @@ Type: | ||
<a id="option-extractComments" name="option-extractComments" href="#option-extractComments"></a> | ||
### `extractComments` | ||
Type: `boolean` Default: `false`<br> | ||
Enable / disable extraction of comments to `*.LICENSE.txt` file. | ||
When using `splitChunks` optimization for node modules containing comments, | ||
Webpack extracts those comments into a separate text file. | ||
By default, the plugin don't create such unwanted text files. | ||
But if you want to extract files like `*.LICENSE.txt`, set this option to `true`: | ||
```js | ||
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); | ||
module.exports = { | ||
plugins: [ | ||
new HtmlBundlerPlugin({ | ||
extractComments: true, | ||
}), | ||
], | ||
}; | ||
``` | ||
<a id="option-verbose" name="option-verbose" href="#option-verbose"></a> | ||
### `verbose` | ||
Type: `boolean` Default: `false`<br> | ||
Display information about all processed files. | ||
--- | ||
@@ -426,2 +519,3 @@ | ||
<a id="loader-option-sources" name="loader-option-sources" href="#loader-option-sources"></a> | ||
### `sources` | ||
@@ -605,2 +699,4 @@ Type: | ||
<a id="loader-option-preprocessor" name="loader-option-preprocessor" href="#loader-option-preprocessor"></a> | ||
### `preprocessor` | ||
@@ -623,10 +719,11 @@ Type: | ||
- `resourcePath: string` - a template file | ||
- `data: object|null` - variables passed form [`entry`](#option-entry) | ||
Complete API see by the [Loader Context](https://webpack.js.org/api/loaders/#the-loader-context). | ||
The preprocessor is called before handling of the content. | ||
This function can be used to replace a placeholder with a variable or compile the content with a template engine, | ||
such as [Handlebars](https://handlebarsjs.com), [Nunjucks](https://mozilla.github.io/nunjucks). | ||
The preprocessor is called for each entry file, before handling of the content. | ||
This function can be used to compile the template with a template engine, | ||
such as [EJS](https://ejs.co), [Handlebars](https://handlebarsjs.com), [Nunjucks](https://mozilla.github.io/nunjucks), etc. | ||
For example, set variable in the template | ||
For example, set a variable in the template | ||
_index.html_ | ||
@@ -653,8 +750,15 @@ ```html | ||
entry: { | ||
index: './src/index.html', | ||
}, | ||
plugins: [ | ||
new HtmlBundlerPlugin({ | ||
entry: { | ||
index: { // => dist/index.html | ||
import: './src/views/index.html', | ||
data: { | ||
title: 'Homepage', | ||
} | ||
}, | ||
}, | ||
}), | ||
], | ||
plugins: [new HtmlBundlerPlugin()], | ||
module: { | ||
@@ -666,3 +770,6 @@ rules: [ | ||
options: { | ||
preprocessor: (content, loaderContext) => content.replace('{{ title }}', 'Homepage'), | ||
preprocessor: (content, { data }) => { | ||
// you can use here a template engine like EJS, Handlebars, Nunjucks, etc | ||
return content.replace('{{ title }}', data.title); | ||
}, | ||
}, | ||
@@ -676,9 +783,3 @@ }, | ||
> **Note** | ||
> | ||
> Using the `preprocessor` you can use anyone template engine without its loader. | ||
> | ||
> The `preprocessor` will be called for each entry file. | ||
> For multipage configuration, you can use the `loaderContext.resourcePath` property to differentiate data for diverse pages. | ||
> See the [usage example](#recipe-pass-data-multipage). | ||
See the example [How to use a template engine](#recipe-template-engine). | ||
@@ -956,3 +1057,3 @@ --- | ||
You can inline images in two ways: | ||
You can inline the images in two ways: | ||
- force inline image using `?inline` query | ||
@@ -994,2 +1095,12 @@ - auto inline by image size | ||
Using the [preprocessor](#loader-option-preprocessor) you can compile the template with a template engine such as: | ||
- [EJS](https://ejs.co), | ||
- [Handlebars](https://handlebarsjs.com) | ||
- [Nunjucks](https://mozilla.github.io/nunjucks/), | ||
> **Note** | ||
> | ||
> For Pug templates use the [pug-plugin](https://github.com/webdiscus/pug-plugin). | ||
> This plugin works on the same codebase but has additional Pug-specific features. | ||
For example, using the Handlebars templating engine, there is an | ||
@@ -1011,3 +1122,3 @@ _index.hbs_ | ||
Add the `preprocessor` option to compile the content with Handlebars. | ||
Add the `preprocessor` option to compile the template. | ||
@@ -1024,9 +1135,18 @@ ```js | ||
entry: { | ||
index: './src/views/home/index.hbs', | ||
}, | ||
plugins: [ | ||
new HtmlBundlerPlugin({ | ||
test: /\.(html|hbs)$/, // add the option to match *.hbs files in entry, default is /\.html$/ | ||
entry: { | ||
index: { | ||
import: './src/views/home/index.hbs', | ||
// pass data into the preprocessor | ||
data: { | ||
title: 'My Title', | ||
headline: 'Breaking Bad', | ||
firstname: 'Walter', | ||
lastname: 'Heisenberg', | ||
}, | ||
}, | ||
}, | ||
}), | ||
@@ -1042,10 +1162,3 @@ ], | ||
// add the preprocessor function to compile *.hbs files to HTML | ||
// you can pass data here to all templates | ||
preprocessor: (content, loaderContext) => | ||
Handlebars.compile(content)({ | ||
title: 'My Title', | ||
headline: 'Breaking Bad', | ||
firstname: 'Walter', | ||
lastname: 'Heisenberg', | ||
}), | ||
preprocessor: (content, { data }) => Handlebars.compile(content)(data), | ||
}, | ||
@@ -1059,13 +1172,9 @@ }, | ||
<a id="recipe-pass-data" name="recipe-pass-data" href="#recipe-pass-data"></a> | ||
## How to pass data into template | ||
You can pass variables into template using a lightweight template engine, e.g. [Handlebars](https://handlebarsjs.com). | ||
See the usage example by [How to use a template engine](#recipe-template-engine) or [How to pass different data by multipage configuration](#recipe-pass-data-multipage). | ||
<a id="recipe-pass-data-to-templates" name="recipe-pass-data-to-templates" href="#recipe-pass-data-to-templates"></a> | ||
## How to pass data into templates | ||
<a id="recipe-pass-data-multipage" name="recipe-pass-data-multipage" href="#recipe-pass-data-multipage"></a> | ||
## How to pass different data by multipage configuration | ||
You can pass variables into template using a template engine, e.g. [Handlebars](https://handlebarsjs.com). | ||
For multiple page configuration, better to use the [Nunjucks](https://mozilla.github.io/nunjucks) templating engine maintained by Mozilla. | ||
For multipage configuration, better to use the [Nunjucks](https://mozilla.github.io/nunjucks) templating engine maintained by Mozilla. | ||
For example, you have several pages with variables.\ | ||
@@ -1097,2 +1206,3 @@ Both pages have the same layout _src/views/layouts/default.html_ | ||
{% block styles %} | ||
<!-- load source style --> | ||
<link href="./home.scss" rel="stylesheet"> | ||
@@ -1102,2 +1212,3 @@ {% endblock %} | ||
{% block scripts %} | ||
<!-- load source script --> | ||
<script src="./home.js" defer="defer"></script> | ||
@@ -1142,19 +1253,8 @@ {% endblock %} | ||
/** | ||
* Find template data by template file. | ||
* | ||
* @param {string} sourceFile | ||
* @param {Object} data | ||
* @return {Object} | ||
*/ | ||
const findData = (sourceFile, data) => { | ||
for (const [key, value] of Object.entries(data)) { | ||
if (sourceFile.endsWith(key)) return value; | ||
} | ||
return {}; | ||
}; | ||
// note: data keys are different endings of source template files defined in entry | ||
// Note: | ||
// If your pages have a lot of variables, it's a good idea to define them separately | ||
// to keep the configuration clean and clear. | ||
const entryData = { | ||
'home/index.html': { | ||
// variables for home page | ||
home: { | ||
title: 'Home', | ||
@@ -1165,3 +1265,4 @@ filmTitle: 'Breaking Bad', | ||
}, | ||
'about/index.html': { | ||
// variables for about page | ||
about: { | ||
title: 'About', | ||
@@ -1185,7 +1286,2 @@ actors: [ | ||
}, | ||
entry: { | ||
// define your templates here | ||
index: 'src/views/pages/home/index.html', // => dist/index.html | ||
about: 'src/views/pages/about/index.html', // => dist/about.html | ||
}, | ||
resolve: { | ||
@@ -1198,2 +1294,13 @@ alias: { | ||
new HtmlBundlerPlugin({ | ||
entry: { | ||
// define your templates here | ||
index: { // => dist/index.html | ||
import: 'src/views/pages/home/index.html', | ||
data: entryData.home, | ||
}, | ||
about: { // => dist/about.html | ||
import: 'src/views/pages/about/index.html', | ||
data: entryData.about, | ||
}, | ||
}, | ||
js: { | ||
@@ -1214,6 +1321,4 @@ filename: 'assets/js/[name].[contenthash:8].js', | ||
options: { | ||
preprocessor: (content, { resourcePath }) => { | ||
// get template variables by template filename | ||
const data = findData(resourcePath, entryData); | ||
// render template with specific variables | ||
preprocessor: (content, { data }) => { | ||
// render template with page-specific variables defined in entry | ||
return Nunjucks.renderString(content, data); | ||
@@ -1220,0 +1325,0 @@ }, |
const vm = require('vm'); | ||
const path = require('path'); | ||
const NormalModule = require('webpack/lib/NormalModule'); | ||
const JavascriptParser = require('webpack/lib/javascript/JavascriptParser'); | ||
@@ -141,3 +142,4 @@ const JavascriptGenerator = require('webpack/lib/javascript/JavascriptGenerator'); | ||
// the id to bind loader with data passed into template via entry.data | ||
entryDataId; | ||
entryDataIndex; | ||
entryDataCache = new Map(); | ||
@@ -191,6 +193,44 @@ // webpack's options and modules | ||
/** | ||
* Reset all initial values. | ||
* Entries defined in plugin options are merged with Webpack entry option. | ||
* | ||
* @param {{}} options The Webpack options where entry contains additional 'data' property. | ||
*/ | ||
initializeWebpackEntry(options) { | ||
let { entry } = options; | ||
const pluginEntry = this.options.entry; | ||
// check whether the entry in config is empty, defaults Webpack set structure: `{ main: {} }`, | ||
if (Object.keys(entry).length === 1 && Object.keys(Object.entries(entry)[0][1]).length === 0) { | ||
// set empty entry to avoid Webpack error | ||
entry = {}; | ||
} | ||
if (pluginEntry) { | ||
for (const key in pluginEntry) { | ||
const entry = pluginEntry[key]; | ||
if (entry.import == null) { | ||
pluginEntry[key] = { import: [entry] }; | ||
continue; | ||
} | ||
if (!Array.isArray(entry.import)) { | ||
entry.import = [entry.import]; | ||
} | ||
} | ||
options.entry = { ...entry, ...pluginEntry }; | ||
// the 'layer' entry property is only allowed when 'experiments.layers' is enabled | ||
options.experiments.layers = true; | ||
} | ||
} | ||
/** | ||
* Reset initial values. | ||
*/ | ||
reset() { | ||
this.entryDataId = 1; | ||
// TODO: test hmr | ||
this.entryDataIndex = 1; | ||
this.entryDataCache.clear(); | ||
} | ||
@@ -220,33 +260,2 @@ | ||
/** | ||
* @param {{}} options The Webpack options where entry contains additional 'data' property. | ||
*/ | ||
initializeWebpackEntry(options) { | ||
let { entry } = options; | ||
const pluginEntries = this.options.entry; | ||
// check whether the entry in config is empty, defaults Webpack set structure: `{ main: {} }`, | ||
if (Object.keys(entry).length === 1 && Object.keys(Object.entries(entry)[0][1]).length === 0) { | ||
// reset entry | ||
entry = {}; | ||
} | ||
if (pluginEntries) { | ||
for (const key in pluginEntries) { | ||
const entry = pluginEntries[key]; | ||
if (entry['import'] == null) { | ||
pluginEntries[key] = { import: [entry] }; | ||
continue; | ||
} | ||
if (!Array.isArray(entry.import)) { | ||
entry.import = [entry.import]; | ||
} | ||
} | ||
options.entry = { ...entry, ...pluginEntries }; | ||
} | ||
} | ||
/** | ||
* Apply plugin. | ||
@@ -356,2 +365,16 @@ * @param {{}} compiler | ||
// called after the module hook but right before the execution of a loader | ||
NormalModule.getCompilationHooks(compilation).loader.tap(pluginName, (loaderContext, module) => { | ||
if (typeof module.layer === 'string' && module.layer.startsWith('__entryDataId')) { | ||
const [, entryDataId] = module.layer.split('=', 2); | ||
loaderContext.entryData = this.entryDataCache.get(entryDataId); | ||
loaderContext.data = this.entryDataCache.get(entryDataId); | ||
} | ||
// [RESERVED FALLBACK] if the experimental 'layer' option will be changed or removed in next Webpack version. | ||
// if (module.__entryDataId) { | ||
// loaderContext.entryData = this.entryDataCache.get(module.__entryDataId); | ||
// } | ||
}); | ||
// render source code of modules | ||
@@ -428,6 +451,20 @@ compilation.hooks.renderManifest.tap(pluginName, this.renderManifest); | ||
if (entry.data) { | ||
entry.entryDataId = `${this.entryDataId}`; | ||
entry.import[0] += importFile.indexOf('?') < 0 ? '?' : '&'; | ||
entry.import[0] += `__entryDataId=${this.entryDataId}`; | ||
this.entryDataId++; | ||
// IMPORTANT: when the entry contains same source file for many chunks, for example: | ||
// { | ||
// page1: { import: 'src/template.html', data: { title: 'A'} }, | ||
// page2: { import: 'src/template.html', data: { title: 'B'} }, | ||
// } | ||
// add an unique identifier of the entry to execute a loader for all templates, | ||
// otherwise Webpack call a loader only for the first template. | ||
// See 'webpack/lib/NormalModule.js:identifier()'. | ||
entry.layer = `__entryDataId=${this.entryDataIndex}`; | ||
// [RESERVED FALLBACK] if the experimental 'layer' option will be changed or removed in next Webpack version. | ||
// specify the entry data id as a query param | ||
//entry.import[0] += importFile.indexOf('?') < 0 ? '?' : '&'; | ||
//entry.import[0] += `__entryDataId=${this.entryDataIndex}`; | ||
this.entryDataCache.set(`${this.entryDataIndex}`, entry.data); | ||
this.entryDataIndex++; | ||
} | ||
@@ -448,3 +485,30 @@ | ||
const { issuer } = contextInfo; | ||
const [resourceFile] = request.split('?', 2); | ||
// [RESERVED FALLBACK] if the experimental 'layer' option will be changed or removed in next Webpack version. | ||
// Resolve the entry data id from the resource query added in the 'entryOption' hook | ||
// to generate unique identifier of the module. | ||
// It is ultra important when the entry contains same source file for many chunks. | ||
// For example: | ||
// { page1: 'src/template.html', page2: 'src/template.html' } | ||
// const [, resourceQuery] = request.split('?', 2); | ||
// if (resourceQuery) { | ||
// const params = new URLSearchParams(resourceQuery); | ||
// let queryString = ''; | ||
// | ||
// if (params.has('__entryDataId')) { | ||
// resolveData.__entryDataId = params.get('__entryDataId'); | ||
// // note: don't remove this param, because it serves for unique identifier of the module | ||
// // when used entry data and the entry contains same source file for many chunks | ||
// // remove the temporary private param from request | ||
// //params.delete('__entryDataId'); | ||
// //params.forEach((value, key) => { | ||
// // queryString += queryString ? '&' : '?'; | ||
// // queryString += value ? `${key}=${value}` : `${key}`; | ||
// //}); | ||
// // recovery the original request w/o temporary private param | ||
// //resolveData.request = resourceFile + queryString; | ||
// } | ||
// } | ||
// save requests to issuer cache | ||
@@ -457,5 +521,4 @@ if (!issuer) issuerCache.add(request); | ||
if (issuer && issuer.length > 0) { | ||
const [requestFile] = request.split('?', 1); | ||
const extractCss = this.options.extractCss; | ||
if (extractCss.enabled && extractCss.test.test(issuer) && requestFile.endsWith('.js')) { | ||
if (extractCss.enabled && extractCss.test.test(issuer) && resourceFile.endsWith('.js')) { | ||
// ignore runtime scripts of a loader, because a style can't have a javascript dependency | ||
@@ -514,4 +577,10 @@ return false; | ||
const { rawRequest, resource } = createData; | ||
const issuer = resolveData.contextInfo.issuer; | ||
const { issuer } = resolveData.contextInfo; | ||
// [RESERVED FALLBACK] if the experimental 'layer' option will be changed or removed in next Webpack version. | ||
// pass a resolved entry data id into the module | ||
// if (resolveData.__entryDataId) { | ||
// module.__entryDataId = resolveData.__entryDataId; | ||
// } | ||
if (!issuer || AssetInline.isDataUrl(rawRequest)) return; | ||
@@ -518,0 +587,0 @@ |
@@ -243,3 +243,2 @@ const path = require('path'); | ||
const RawSource = compilation.compiler.webpack.sources.RawSource; | ||
const NL = '\n'; | ||
@@ -251,5 +250,5 @@ for (const assetFile of this.inlineSvgIssueAssets) { | ||
const html = asset.source(); | ||
const headPos = html.indexOf('<head'); | ||
const headEndPos = html.indexOf('</head>'); | ||
const hasHead = headPos >= 0 && headEndPos > headPos; | ||
const headStartPos = html.indexOf('<head'); | ||
const headEndPos = html.indexOf('</head>', headStartPos); | ||
const hasHead = headStartPos >= 0 && headEndPos > headStartPos; | ||
let results = []; | ||
@@ -280,6 +279,5 @@ | ||
const attrsString = attrsToString(attrs, [...excludeAttrs, 'title']); | ||
const [filename] = path.basename(value).split('?', 1); | ||
const titleStr = 'title' in attrs ? attrs['title'] : 'alt' in attrs ? attrs['alt'] : null; | ||
const title = titleStr ? `<title>${titleStr}</title>` : ''; | ||
const inlineSvg = `${NL}<!-- inline: ${filename} -->${NL}<svg${attrsString}>${title}${cache.innerSVG}</svg>${NL}`; | ||
const inlineSvg = `<svg${attrsString}>${title}${cache.innerSVG}</svg>`; | ||
@@ -286,0 +284,0 @@ output += html.slice(pos, tagStartPos) + inlineSvg; |
@@ -11,23 +11,2 @@ const { merge } = require('webpack-merge'); | ||
/** | ||
* @param {{}} entries | ||
* @param {string} resourceQuery | ||
* @return {{}|null} | ||
*/ | ||
const findEntryData = (entries, resourceQuery) => { | ||
const params = new URLSearchParams(resourceQuery); | ||
const entryDataId = params.get('__entryDataId'); | ||
if (entryDataId) { | ||
for (let key in entries) { | ||
const entry = entries[key]; | ||
if (entry.entryDataId === entryDataId) { | ||
return entry.data; | ||
} | ||
} | ||
} | ||
return null; | ||
}; | ||
/** | ||
* @param {string} content The HTML template. | ||
@@ -106,5 +85,3 @@ * @param {function(error: Error|null, result: string?)?} callback The asynchronous callback function. | ||
if (preprocessor !== null) { | ||
const data = findEntryData(webpackOptions.entry, loaderContext.resourceQuery); | ||
content = preprocessor(content, loaderContext, data); | ||
content = preprocessor(content, loaderContext); | ||
} | ||
@@ -157,2 +134,11 @@ } catch (error) { | ||
// note: the 'entryData' is the custom property defined in plugin | ||
// set the origin 'data' property of loader context, | ||
// see https://webpack.js.org/api/loaders/#thisdata | ||
if (loaderContext.entryData != null) { | ||
const loader = loaderContext.loaders[loaderContext.loaderIndex]; | ||
loader.data = loaderContext.entryData; | ||
delete loaderContext.entryData; | ||
} | ||
compile.call(loaderContext, content, (error, result) => { | ||
@@ -159,0 +145,0 @@ if (error) { |
208002
4412
1394
17