Skip to content

Commit 5e9ebdf

Browse files
committed
feat: pick up the srcset and sizes attributes for image preload, #149
1 parent 1b0443a commit 5e9ebdf

File tree

25 files changed

+344
-23
lines changed

25 files changed

+344
-23
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 4.17.0 (2025-01-29)
4+
5+
- feat: pick up the `srcset` and `sizes` attributes for image preload, #149
6+
37
## 4.16.0 (2025-01-28)
48

59
- feat: add integrity attribute for preload tags if integrity option on, #145

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": "4.16.0",
3+
"version": "4.17.0",
44
"description": "Generates complete single-page or multi-page website from source assets. Build-in support for Markdown, Eta, EJS, Handlebars, Nunjucks, Pug. Alternative to html-webpack-plugin.",
55
"keywords": [
66
"html",

src/Plugin/Preload.js

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const path = require('path');
22
const { detectIndent, getFileExtension } = require('../Common/Helpers');
3+
const { HtmlParser } = require('../Common/HtmlParser');
34
const { optionPreloadAsException } = require('./Messages/Exception');
45

56
/**
@@ -108,6 +109,7 @@ class Preload {
108109
const LF = this.pluginOption.getLF();
109110
const indent = LF + detectIndent(content, insertPos - 1);
110111
const groupBy = {};
112+
let hasImage = false;
111113

112114
// normalize preload attributes and create sorted groups in the order of the specified 'preload' options
113115
for (const conf of options) {
@@ -127,6 +129,7 @@ class Preload {
127129

128130
// for the `font` type, the `crossorigin` attribute is mandatory, if it is not defined, set to default value
129131
if (as === 'font' && !('crossorigin' in attrs)) attrs.crossorigin = true;
132+
if (as === 'image') hasImage = true;
130133

131134
// whether the 'type' property exist regardless of a value;
132135
// if the property exists and have the undefined value, exclude this attribute in generating preload tag
@@ -141,6 +144,9 @@ class Preload {
141144
groupBy[as] = [];
142145
}
143146

147+
// pick up image attributes to preload
148+
const imageAttributes = hasImage ? this.#getImageAttributes(content) : new Map();
149+
144150
// prepare a flat array with preload assets
145151
for (let item of data.assets) {
146152
if (item.inline) continue;
@@ -170,6 +176,12 @@ class Preload {
170176

171177
if (this.pluginOption.applyAdvancedFiler({ sourceFiles, outputFile }, conf._opts.filter)) {
172178
let props = this.createPreloadAttributes(conf, { integrity: item.integrity, crossorigin });
179+
let imgAttrs = imageAttributes.get(item.assetFile);
180+
181+
if (imgAttrs) {
182+
props.attrs = Object.assign(props.attrs, imgAttrs);
183+
}
184+
173185
preloadAssets.set(item.assetFile, props);
174186
}
175187
}
@@ -204,19 +216,20 @@ class Preload {
204216
: assetItem.assetFile;
205217

206218
if (this.pluginOption.applyAdvancedFiler({ sourceFiles, outputFile }, conf._opts.filter)) {
207-
preloadAssets.set(outputFile, conf._opts);
219+
let props = this.createPreloadAttributes(conf);
220+
preloadAssets.set(outputFile, props);
208221
}
209222
}
210223
}
211224
}
212225

213226
// save generated preload tags for verbose
214227
data.preloads = [];
215-
for (const [filename, opts] of preloadAssets.entries()) {
216-
const attrs = { ...opts.attrs };
228+
for (const [filename, props] of preloadAssets.entries()) {
229+
const { attrs } = props;
217230

218231
attrs.href = filename;
219-
if (!opts.hasType) {
232+
if (!props.hasType) {
220233
const ext = getFileExtension(filename);
221234
attrs.type = mimeType[attrs.as]?.[ext] || attrs.as + '/' + ext;
222235
}
@@ -246,19 +259,59 @@ class Preload {
246259
}
247260

248261
/**
249-
* @param {Object} conf
250-
* @param {string|undefined} integrity
251-
* @param {string} crossorigin
252-
* @return {Object} Returns preload attributes. It my contains integrity if exists.
262+
* @param {Object} conf The source options from configuration.
263+
* @param {string|undefined} integrity The integrity, used by script and style only.
264+
* @param {string} crossorigin The attribute required for integrity, used by script and style only.
265+
* @return {Object} Returns new object of preload attributes. It may contains integrity if exists.
253266
*/
254-
createPreloadAttributes(conf, { integrity, crossorigin }) {
255-
let opts = { ...conf._opts };
267+
createPreloadAttributes(conf, { integrity, crossorigin } = {}) {
268+
let props = { ...conf._opts };
269+
270+
// create new object for attributes, to keep original config unchanged
271+
props.attrs = { ...props.attrs };
256272

257273
if (integrity) {
258-
opts.attrs = { ...opts.attrs, integrity, crossorigin };
274+
props.attrs.integrity = integrity;
275+
props.attrs.crossorigin = crossorigin;
276+
}
277+
278+
return props;
279+
}
280+
281+
/**
282+
* Pick up image attributes to preload.
283+
*
284+
* @param {string} content
285+
* @return {Map<string, any>}
286+
*/
287+
#getImageAttributes(content) {
288+
const imageAttributes = new Map();
289+
const parseOptions = {
290+
tag: 'img',
291+
attributes: ['srcset', 'sizes'],
292+
};
293+
const imageTags = HtmlParser.parseTag(content, parseOptions);
294+
295+
for (const tag of imageTags) {
296+
const attrs = {};
297+
let hasAttrs = false;
298+
299+
if (tag.attrs.srcset) {
300+
attrs.imagesrcset = tag.attrs.srcset.replaceAll(/\n|\b|\s{2,}/g, '');
301+
hasAttrs = true;
302+
}
303+
304+
if (tag.attrs.sizes) {
305+
attrs.imagesizes = tag.attrs.sizes.replaceAll(/\n|\b|\s{2,}/g, '');
306+
hasAttrs = true;
307+
}
308+
309+
if (hasAttrs) {
310+
imageAttributes.set(tag.attrs.src, attrs);
311+
}
259312
}
260313

261-
return opts;
314+
return imageAttributes;
262315
}
263316

264317
/**
8.74 KB
Loading
15.2 KB
Loading
8.11 KB
Loading
4.93 KB
Loading
9.91 KB
Loading
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Home</title>
5+
<link rel="preload" href="img/hardware.webp" as="image" type="image/webp" imagesrcset="img/hardware-xs.webp 340w,img/hardware-sm.webp 540w,img/hardware-md.webp 960w" imagesizes="(max-width: 576px) 100vw,(max-width: 768px) 540px,(max-width: 992px) 720px,1140px">
6+
<link rel="preload" href="img/abstract.webp" as="image" type="image/webp">
7+
</head>
8+
<body>
9+
<h1>Home</h1>
10+
<img src="img/hardware.webp" width="700" height="480"
11+
srcset="
12+
img/hardware-xs.webp 340w,
13+
img/hardware-sm.webp 540w,
14+
img/hardware-md.webp 960w"
15+
sizes="
16+
(max-width: 576px) 100vw,
17+
(max-width: 768px) 540px,
18+
(max-width: 992px) 720px,
19+
1140px"
20+
alt="hardware">
21+
22+
<img src="img/abstract.webp" width="640" height="320" alt="abstract">
23+
</body>
24+
</html>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Home</title>
5+
</head>
6+
<body>
7+
<h1>Home</h1>
8+
<img src="@images/responsive/hardware.webp" width="700" height="480"
9+
srcset="
10+
@images/responsive/hardware.webp?as=xs 340w,
11+
@images/responsive/hardware.webp?as=sm 540w,
12+
@images/responsive/hardware.webp?as=md 960w"
13+
sizes="
14+
(max-width: 576px) 100vw,
15+
(max-width: 768px) 540px,
16+
(max-width: 992px) 720px,
17+
1140px"
18+
alt="hardware">
19+
20+
<img src="@images/responsive/abstract.webp" width="640" height="320" alt="abstract">
21+
</body>
22+
</html>

0 commit comments

Comments
 (0)