Skip to content

Commit 5488595

Browse files
committed
feat: add resolving the url() value in the style attribute
1 parent 371f46c commit 5488595

11 files changed

Lines changed: 189 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Change log
22

3+
## 3.13.0 (2024-05-26)
4+
5+
- feat: add resolving the url() value in the style attribute:
6+
```html
7+
<div style="background-image: url(./image.png);"></div>
8+
```
9+
310
## 3.12.0 (2024-05-19)
411

512
- feat: add support for the `css-loader` option `exportType` as [css-style-sheet](https://github.com/webpack-contrib/css-loader?#exporttype)

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ See [boilerplate](https://github.com/webdiscus/webpack-html-scss-boilerplate)
527527
- [How to inline SVG, PNG images in HTML](#recipe-inline-image)
528528
- [How to inline all resources into single HTML file](#recipe-inline-all-assets-to-html)
529529
- [How to resolve source assets in an attribute containing JSON value](#recipe-resolve-attr-json)
530+
- [How to resolve source image in the `style` attribute](#recipe-resolve-attr-style-url)
530531
- [How to load CSS file dynamically](#recipe-dynamic-load-css) (lazy loading CSS)
531532
- [How to import CSS class names in JS](#recipe-css-modules) (CSS modules)
532533
- [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
53515352
</a>
53525353
```
53535354
5355+
---
5356+
5357+
#### [↑ back to contents](#contents)
5358+
5359+
<a id="recipe-resolve-attr-style-url" name="recipe-resolve-attr-style-url"></a>
5360+
5361+
## How to resolve source image in the `style` attribute
5362+
5363+
For example, there is the source image file defined in the `style` attribute as the background of the `div` tag:
5364+
5365+
```html
5366+
<div style="background-image: url(./pic1.png);"></div>
5367+
```
5368+
5369+
The source image file can be a file relative to the template or you can use a webpack alias to the image directory.
5370+
5371+
> **Note**
5372+
>
5373+
> This is BAD practice. Use it only in special cases.
5374+
> The background image should be defined in CSS.
5375+
5376+
By default, the `style` attribute is not parsed and therefore needs to be configured:
5377+
5378+
```js
5379+
new HtmlBundlerPlugin({
5380+
entry: {
5381+
index: './src/index.html',
5382+
},
5383+
loaderOptions: {
5384+
sources: [
5385+
{
5386+
tag: 'div', // <= specify the tag where should be parsed style
5387+
attributes: ['style'], // <= specify the style attribute
5388+
},
5389+
],
5390+
},
5391+
}),
5392+
```
5393+
5394+
The plugin resolves the `url()` value and replaces it in the generated HTML with the output filename:
5395+
```html
5396+
<div style="background-image: url(assets/img/pic1.d4711676.png);"></div>
5397+
```
5398+
5399+
53545400
---
53555401
53565402
#### [↑ back to contents](#contents)

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.12.0",
3+
"version": "3.13.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/Common/HtmlParser.js

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ class HtmlParser {
308308
* @param {string} attr The attribute to parse value.
309309
* @param {string} type The type of attribute value.
310310
* @param {Number} offset The absolute tag offset in the content.
311-
* @return {{attr: string, attrs?: Array, value: string, parsedValue: Array<string>, startPos: number, endPos: number, offset: number, inEscapedDoubleQuotes: boolean}|boolean}
311+
* @return {{attr: string, attrs?: Array, value: string, parsedValue: Array<string>, startPos: number, endPos: number, offset: number, inEscapedDoubleQuotes: boolean} | {} | boolean}
312312
*/
313313
static parseAttr(content, attr, type = 'asset', offset = 0) {
314314
// TODO: allow zero or more spaces around `=`
@@ -356,21 +356,37 @@ class HtmlParser {
356356

357357
return {
358358
attr,
359-
attrs,
360359
value,
361-
parsedValue: values,
362360
startPos,
363361
endPos,
364362
offset,
363+
attrs,
364+
parsedValue: values,
365365
inEscapedDoubleQuotes,
366366
};
367367
}
368368

369+
if (attr === 'style') {
370+
const { values, attrs } = this.parseStyleUrlValues(value, startPos, offset);
371+
372+
return values
373+
? {
374+
attr,
375+
value,
376+
startPos,
377+
endPos,
378+
offset,
379+
attrs,
380+
parsedValue: values,
381+
inEscapedDoubleQuotes,
382+
}
383+
: {};
384+
}
385+
369386
let result = {
370387
type,
371388
attr,
372389
value,
373-
parsedValue: '',
374390
startPos,
375391
endPos,
376392
offset,
@@ -389,6 +405,45 @@ class HtmlParser {
389405
return result;
390406
}
391407

408+
/**
409+
* Parse url() in the style attribute.
410+
*
411+
* For example:
412+
* <div style="background-image: url(./image.png);"></div>
413+
*
414+
* @param {string} content The attribute value.
415+
* @param {Number} valueOffset The offset of value in the tag.
416+
* @param {Number} offset The absolute tag offset in the content.
417+
* @return {{values: *[], attrs: *[]} | {}}
418+
*/
419+
static parseStyleUrlValues(content, valueOffset, offset) {
420+
let pos = content.indexOf('url(');
421+
422+
if (pos < 0) return {};
423+
424+
let valueStartPos = pos + 4;
425+
let valueEndPos = content.indexOf(')', valueStartPos);
426+
let quote = content.charAt(valueStartPos);
427+
let skipQuotes = 1;
428+
429+
if (quote !== '"' && quote !== "'") {
430+
quote = '';
431+
skipQuotes = 0;
432+
}
433+
434+
let parsedValue = content.slice(valueStartPos + skipQuotes, valueEndPos - skipQuotes); // skip quotes around value
435+
436+
let attrs = {
437+
value: parsedValue,
438+
quote,
439+
startPos: valueStartPos,
440+
endPos: valueEndPos,
441+
offset: offset + valueOffset,
442+
};
443+
444+
return { values: [parsedValue], attrs: [attrs] };
445+
}
446+
392447
/**
393448
* Parse require() in the attribute value.
394449
*

src/Loader/Template.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ class Template {
1212
* @param { HtmlBundlerPlugin.Hooks} hooks The plugin hooks.
1313
* @return {string}
1414
*/
15-
1615
static resolve(content, issuer, entryId, hooks) {
1716
const sources = Option.getSources();
1817

@@ -24,7 +23,6 @@ class Template {
2423
let parsedTags = [];
2524
for (let opts of sources) {
2625
opts.resourcePath = issuer;
27-
2826
parsedTags.push(...HtmlParser.parseTag(content, opts));
2927
}
3028
parsedTags = parsedTags.sort(comparePos);
@@ -36,6 +34,8 @@ class Template {
3634

3735
for (let { tag, source, parsedAttrs } of parsedTags) {
3836
for (let { type, attr, startPos, endPos, value, quote, offset, inEscapedDoubleQuotes } of parsedAttrs) {
37+
if (!value) continue;
38+
3939
const result = this.resolveFile({
4040
isBasedir,
4141
type,
2.68 KB
Loading
398 Bytes
Loading
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Test</title>
5+
</head>
6+
<body>
7+
<h1>Hello World!</h1>
8+
<div style="color: red;">text</div>
9+
<div style="width: 160px; background-image: url(img/image.d4711676.webp); height: 130px;"></div>
10+
<div style="width: 180px; background-image: url('img/bild.7b396424.png'); height: 140px;"></div>
11+
</body>
12+
</html>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Test</title>
5+
</head>
6+
<body>
7+
<h1>Hello World!</h1>
8+
<div style="color: red;">text</div>
9+
<div style="width: 160px; background-image: url(@images/image.webp); height: 130px;"></div>
10+
<div style="width: 180px; background-image: url('@images/2k-over/bild.png'); height: 140px;"></div>
11+
</body>
12+
</html>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const path = require('path');
2+
const HtmlBundlerPlugin = require('@test/html-bundler-webpack-plugin');
3+
4+
module.exports = {
5+
mode: 'production',
6+
7+
output: {
8+
path: path.join(__dirname, 'dist/'),
9+
},
10+
11+
resolve: {
12+
alias: {
13+
'@images': path.join(__dirname, '../../fixtures/images'),
14+
},
15+
},
16+
17+
plugins: [
18+
new HtmlBundlerPlugin({
19+
entry: {
20+
index: './src/index.html',
21+
},
22+
loaderOptions: {
23+
sources: [
24+
{
25+
tag: 'div',
26+
attributes: ['style'],
27+
},
28+
],
29+
},
30+
}),
31+
],
32+
33+
module: {
34+
rules: [
35+
{
36+
test: /\.css$/,
37+
use: ['css-loader'],
38+
},
39+
{
40+
test: /\.(png|jpe?g|webp|ico|svg)$/,
41+
type: 'asset/resource',
42+
generator: {
43+
filename: 'img/[name].[hash:8][ext]',
44+
},
45+
},
46+
],
47+
},
48+
};

0 commit comments

Comments
 (0)