diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2455c276..8fa75c05 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Change log
+## 3.13.0 (2024-05-26)
+
+- feat: add resolving the url() value in the style attribute:
+ ```html
+
+ ```
+
## 3.12.0 (2024-05-19)
- feat: add support for the `css-loader` option `exportType` as [css-style-sheet](https://github.com/webpack-contrib/css-loader?#exporttype)
diff --git a/README.md b/README.md
index dc0486f8..0c8d03d5 100644
--- a/README.md
+++ b/README.md
@@ -527,6 +527,7 @@ See [boilerplate](https://github.com/webdiscus/webpack-html-scss-boilerplate)
- [How to inline SVG, PNG images in HTML](#recipe-inline-image)
- [How to inline all resources into single HTML file](#recipe-inline-all-assets-to-html)
- [How to resolve source assets in an attribute containing JSON value](#recipe-resolve-attr-json)
+ - [How to resolve source image in the `style` attribute](#recipe-resolve-attr-style-url)
- [How to load CSS file dynamically](#recipe-dynamic-load-css) (lazy loading CSS)
- [How to import CSS class names in JS](#recipe-css-modules) (CSS modules)
- [How to import CSS stylesheet in JS](#recipe-css-style-sheet) ([CSSStyleSheet](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet))
@@ -5351,6 +5352,51 @@ The custom attribute will contains in the generated HTML the resolved output ass
```
+---
+
+#### [↑ back to contents](#contents)
+
+
+
+## How to resolve source image in the `style` attribute
+
+For example, there is the source image file defined in the `style` attribute as the background of the `div` tag:
+
+```html
+
+```
+
+The source image file can be a file relative to the template or you can use a webpack alias to the image directory.
+
+> **Note**
+>
+> This is BAD practice. Use it only in special cases.
+> The background image should be defined in CSS.
+
+By default, the `style` attribute is not parsed and therefore needs to be configured:
+
+```js
+new HtmlBundlerPlugin({
+ entry: {
+ index: './src/index.html',
+ },
+ loaderOptions: {
+ sources: [
+ {
+ tag: 'div', // <= specify the tag where should be parsed style
+ attributes: ['style'], // <= specify the style attribute
+ },
+ ],
+ },
+}),
+```
+
+The plugin resolves the `url()` value and replaces it in the generated HTML with the output filename:
+```html
+
+```
+
+
---
#### [↑ back to contents](#contents)
diff --git a/package.json b/package.json
index 54bf1cd8..31c21aee 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "html-bundler-webpack-plugin",
- "version": "3.12.0",
+ "version": "3.13.0",
"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.",
"keywords": [
"html",
diff --git a/src/Common/HtmlParser.js b/src/Common/HtmlParser.js
index e1ab0e2e..3e1070e1 100644
--- a/src/Common/HtmlParser.js
+++ b/src/Common/HtmlParser.js
@@ -308,7 +308,7 @@ class HtmlParser {
* @param {string} attr The attribute to parse value.
* @param {string} type The type of attribute value.
* @param {Number} offset The absolute tag offset in the content.
- * @return {{attr: string, attrs?: Array, value: string, parsedValue: Array, startPos: number, endPos: number, offset: number, inEscapedDoubleQuotes: boolean}|boolean}
+ * @return {{attr: string, attrs?: Array, value: string, parsedValue: Array, startPos: number, endPos: number, offset: number, inEscapedDoubleQuotes: boolean} | {} | boolean}
*/
static parseAttr(content, attr, type = 'asset', offset = 0) {
// TODO: allow zero or more spaces around `=`
@@ -356,21 +356,37 @@ class HtmlParser {
return {
attr,
- attrs,
value,
- parsedValue: values,
startPos,
endPos,
offset,
+ attrs,
+ parsedValue: values,
inEscapedDoubleQuotes,
};
}
+ if (attr === 'style') {
+ const { values, attrs } = this.parseStyleUrlValues(value, startPos, offset);
+
+ return values
+ ? {
+ attr,
+ value,
+ startPos,
+ endPos,
+ offset,
+ attrs,
+ parsedValue: values,
+ inEscapedDoubleQuotes,
+ }
+ : {};
+ }
+
let result = {
type,
attr,
value,
- parsedValue: '',
startPos,
endPos,
offset,
@@ -389,6 +405,45 @@ class HtmlParser {
return result;
}
+ /**
+ * Parse url() in the style attribute.
+ *
+ * For example:
+ *
+ *
+ * @param {string} content The attribute value.
+ * @param {Number} valueOffset The offset of value in the tag.
+ * @param {Number} offset The absolute tag offset in the content.
+ * @return {{values: *[], attrs: *[]} | {}}
+ */
+ static parseStyleUrlValues(content, valueOffset, offset) {
+ let pos = content.indexOf('url(');
+
+ if (pos < 0) return {};
+
+ let valueStartPos = pos + 4;
+ let valueEndPos = content.indexOf(')', valueStartPos);
+ let quote = content.charAt(valueStartPos);
+ let skipQuotes = 1;
+
+ if (quote !== '"' && quote !== "'") {
+ quote = '';
+ skipQuotes = 0;
+ }
+
+ let parsedValue = content.slice(valueStartPos + skipQuotes, valueEndPos - skipQuotes); // skip quotes around value
+
+ let attrs = {
+ value: parsedValue,
+ quote,
+ startPos: valueStartPos,
+ endPos: valueEndPos,
+ offset: offset + valueOffset,
+ };
+
+ return { values: [parsedValue], attrs: [attrs] };
+ }
+
/**
* Parse require() in the attribute value.
*
diff --git a/src/Loader/Template.js b/src/Loader/Template.js
index 1654ffa6..947cc05b 100644
--- a/src/Loader/Template.js
+++ b/src/Loader/Template.js
@@ -12,7 +12,6 @@ class Template {
* @param { HtmlBundlerPlugin.Hooks} hooks The plugin hooks.
* @return {string}
*/
-
static resolve(content, issuer, entryId, hooks) {
const sources = Option.getSources();
@@ -24,7 +23,6 @@ class Template {
let parsedTags = [];
for (let opts of sources) {
opts.resourcePath = issuer;
-
parsedTags.push(...HtmlParser.parseTag(content, opts));
}
parsedTags = parsedTags.sort(comparePos);
@@ -36,6 +34,8 @@ class Template {
for (let { tag, source, parsedAttrs } of parsedTags) {
for (let { type, attr, startPos, endPos, value, quote, offset, inEscapedDoubleQuotes } of parsedAttrs) {
+ if (!value) continue;
+
const result = this.resolveFile({
isBasedir,
type,
diff --git a/test/cases/resolve-attr-style-url/expected/img/bild.7b396424.png b/test/cases/resolve-attr-style-url/expected/img/bild.7b396424.png
new file mode 100644
index 00000000..1e0c9bcd
Binary files /dev/null and b/test/cases/resolve-attr-style-url/expected/img/bild.7b396424.png differ
diff --git a/test/cases/resolve-attr-style-url/expected/img/image.d4711676.webp b/test/cases/resolve-attr-style-url/expected/img/image.d4711676.webp
new file mode 100644
index 00000000..238589d7
Binary files /dev/null and b/test/cases/resolve-attr-style-url/expected/img/image.d4711676.webp differ
diff --git a/test/cases/resolve-attr-style-url/expected/index.html b/test/cases/resolve-attr-style-url/expected/index.html
new file mode 100644
index 00000000..e7ba21ba
--- /dev/null
+++ b/test/cases/resolve-attr-style-url/expected/index.html
@@ -0,0 +1,12 @@
+
+
+
+ Test
+
+
+ Hello World!
+ text
+
+
+
+
\ No newline at end of file
diff --git a/test/cases/resolve-attr-style-url/src/index.html b/test/cases/resolve-attr-style-url/src/index.html
new file mode 100644
index 00000000..3dadf2da
--- /dev/null
+++ b/test/cases/resolve-attr-style-url/src/index.html
@@ -0,0 +1,12 @@
+
+
+
+ Test
+
+
+ Hello World!
+ text
+
+
+
+
\ No newline at end of file
diff --git a/test/cases/resolve-attr-style-url/webpack.config.js b/test/cases/resolve-attr-style-url/webpack.config.js
new file mode 100644
index 00000000..b185e2d4
--- /dev/null
+++ b/test/cases/resolve-attr-style-url/webpack.config.js
@@ -0,0 +1,48 @@
+const path = require('path');
+const HtmlBundlerPlugin = require('@test/html-bundler-webpack-plugin');
+
+module.exports = {
+ mode: 'production',
+
+ output: {
+ path: path.join(__dirname, 'dist/'),
+ },
+
+ resolve: {
+ alias: {
+ '@images': path.join(__dirname, '../../fixtures/images'),
+ },
+ },
+
+ plugins: [
+ new HtmlBundlerPlugin({
+ entry: {
+ index: './src/index.html',
+ },
+ loaderOptions: {
+ sources: [
+ {
+ tag: 'div',
+ attributes: ['style'],
+ },
+ ],
+ },
+ }),
+ ],
+
+ module: {
+ rules: [
+ {
+ test: /\.css$/,
+ use: ['css-loader'],
+ },
+ {
+ test: /\.(png|jpe?g|webp|ico|svg)$/,
+ type: 'asset/resource',
+ generator: {
+ filename: 'img/[name].[hash:8][ext]',
+ },
+ },
+ ],
+ },
+};
diff --git a/test/integration.test.js b/test/integration.test.js
index 6689dd88..6ffa5414 100644
--- a/test/integration.test.js
+++ b/test/integration.test.js
@@ -449,6 +449,8 @@ describe('special cases', () => {
test('modify js in postprocess', () => compareFiles('postprocess-modify-js'));
+ test('resolve-attr-style-url', () => compareFiles('resolve-attr-style-url'));
+
// for debugging
// test('resolve hmr file', () => watchCompareFiles('resolve-hmr-file'));
});