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')); });