Skip to content

Commit

Permalink
feat: add resolving the url() value in the style attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
webdiscus committed May 26, 2024
1 parent 371f46c commit 5488595
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 7 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Change log

## 3.13.0 (2024-05-26)

- feat: add resolving the url() value in the style attribute:
```html
<div style="background-image: url(./image.png);"></div>
```

## 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)
Expand Down
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -5351,6 +5352,51 @@ The custom attribute will contains in the generated HTML the resolved output ass
</a>
```
---
#### [↑ back to contents](#contents)
<a id="recipe-resolve-attr-style-url" name="recipe-resolve-attr-style-url"></a>
## 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
<div style="background-image: url(./pic1.png);"></div>
```
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
<div style="background-image: url(assets/img/pic1.d4711676.png);"></div>
```
---
#### [↑ back to contents](#contents)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
63 changes: 59 additions & 4 deletions src/Common/HtmlParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>, startPos: number, endPos: number, offset: number, inEscapedDoubleQuotes: boolean}|boolean}
* @return {{attr: string, attrs?: Array, value: string, parsedValue: Array<string>, startPos: number, endPos: number, offset: number, inEscapedDoubleQuotes: boolean} | {} | boolean}
*/
static parseAttr(content, attr, type = 'asset', offset = 0) {
// TODO: allow zero or more spaces around `=`
Expand Down Expand Up @@ -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,
Expand All @@ -389,6 +405,45 @@ class HtmlParser {
return result;
}

/**
* Parse url() in the style attribute.
*
* For example:
* <div style="background-image: url(./image.png);"></div>
*
* @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.
*
Expand Down
4 changes: 2 additions & 2 deletions src/Loader/Template.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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);
Expand All @@ -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,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
12 changes: 12 additions & 0 deletions test/cases/resolve-attr-style-url/expected/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
<h1>Hello World!</h1>
<div style="color: red;">text</div>
<div style="width: 160px; background-image: url(img/image.d4711676.webp); height: 130px;"></div>
<div style="width: 180px; background-image: url('img/bild.7b396424.png'); height: 140px;"></div>
</body>
</html>
12 changes: 12 additions & 0 deletions test/cases/resolve-attr-style-url/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
<h1>Hello World!</h1>
<div style="color: red;">text</div>
<div style="width: 160px; background-image: url(@images/image.webp); height: 130px;"></div>
<div style="width: 180px; background-image: url('@images/2k-over/bild.png'); height: 140px;"></div>
</body>
</html>
48 changes: 48 additions & 0 deletions test/cases/resolve-attr-style-url/webpack.config.js
Original file line number Diff line number Diff line change
@@ -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]',
},
},
],
},
};
2 changes: 2 additions & 0 deletions test/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
});
Expand Down

0 comments on commit 5488595

Please sign in to comment.