Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/empty-carrots-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@web/rollup-plugin-html': major
---

1. Enabled CSS assets extraction by default (as a result, removed configuration option bundleAssetsFromCss).
2. Made extraction of assets from all link rel types
3. Fixed "assetFileNames" behavior
4. Refactored all tests, added tests for more corner cases
5. Added legacy modes for old 2.x.x behavior.

See MIGRATION.md for migration notes.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ local.log
docs/_merged_data/
docs/_merged_assets/
docs/_merged_includes/

## temp
.tmp
107 changes: 70 additions & 37 deletions docs/docs/building/rollup-plugin-html.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,65 +105,98 @@ export default {

### Bundling assets

The HTML plugin will bundle assets referenced from `img` and `link` and social media tag elements in your HTML. The assets are emitted as rollup assets, and the paths are updated to the rollup output paths.
The HTML plugin will bundle assets referenced in `img` and `link` and social media tag elements in your HTML:

By default rollup will hash the asset filenames, enabling long term caching. You can customize the filename pattern using the [assetFileNames option](https://rollupjs.org/guide/en/#outputassetfilenames) in your rollup config.

To turn off bundling assets completely, set the `extractAssets` option to false:

```js
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';

export default {
input: 'index.html',
output: { dir: 'dist' },
plugins: [
html({
extractAssets: false,
}),
],
};
```html
<html>
<head>
<link rel="apple-touch-icon" sizes="180x180" href="./images/icon-a.png" />
<link rel="icon" type="image/png" sizes="32x32" href="./images/icon-b.png" />
<link rel="mask-icon" href="./images/icon-c.svg" color="#3f93ce" />
<link rel="manifest" href="./webmanifest.json" />
<link rel="stylesheet" href="./styles.css" />
<link rel="preload" href="./fonts/font.woff2" as="font" type="font/woff2" crossorigin />
</head>
<body>
<img src="./images/image.png" />
</body>
</html>
```

#### Including assets referenced from css

If your css files reference other assets via `url`, like for example:
And the assets referenced in CSS via `url`:

```css
body {
background-image: url('images/star.gif');
background-image: url('images/image.png');
}

/* or */
@font-face {
src: url('fonts/font-bold.woff2') format('woff2');
/* ...etc */
src: url('fonts/font.woff2') format('woff2');
}
```

You can enable the `bundleAssetsFromCss` option:
The assets are emitted as rollup assets, and the paths are updated to the rollup output paths:

```js
rollupPluginHTML({
bundleAssetsFromCss: true,
// ...etc
});
```html
<html>
<head>
<link rel="apple-touch-icon" sizes="180x180" href="assets/icon-a-XOCPHCrV.png" />
<link rel="icon" type="image/png" sizes="32x32" href="assets/icon-b-BgQHKcRn.png" />
<link rel="mask-icon" href="assets/icon-c-BCCvKrTe.svg" color="#3f93ce" />
<link rel="manifest" href="assets/webmanifest-BkrOR1WG.json" />
<link rel="stylesheet" href="assets/styles-CF2Iy5n1.css" />
<link rel="preload" href="assets/font-f0mNRiTD.woff2" as="font" type="font/woff2" crossorigin />
</head>
<body>
<img src="assets/image-C4yLPiIL.png" />
</body>
</html>
```

And those assets will get output to the `assets/` dir, and the source css file will get updated with the output locations of those assets, e.g.:

```css
body {
background-image: url('assets/star-P4TYRBwL.gif');
background-image: url('assets/image-C4yLPiIL.png');
}

/* or */
@font-face {
src: url('assets/font-bold-f0mNRiTD.woff2') format('woff2');
/* ...etc */
src: url('assets/font-f0mNRiTD.woff2') format('woff2');
}
```

The images are deduped when same ones are referenced in different tags and files.

You can configure the output paths via [assetFileNames option](https://rollupjs.org/guide/en/#outputassetfilenames) (by default `assets/[name]-[hash][extname]` at the time of writing).
The hash in the asset filenames enables long term caching.

#### Disable assets bundling

To turn off bundling assets completely, set the `extractAssets` option to false:

```js
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';

export default {
input: 'index.html',
output: { dir: 'dist' },
plugins: [
html({
extractAssets: false,
}),
],
};
```

#### Enable legacy behavior

For smooth migration we added legacy modes:

- `extractAssets: 'legacy-html'` is the same as 2.x.x behavior when `bundleAssetsFromCss: false`
- `extractAssets: 'legacy-html-and-css'` is the same as 2.x.x behavior when `bundleAssetsFromCss: true`

The 2.x.x behavior was limited to `<link rel="stylesheet" href="..." />` only and the assets referenced in the CSS files were hardcoded to be put into the nested `assets/` dir.

We recommend to use legacy modes only during the migration of large multi-project codebases to 3.x.x in order to temporarily keep the old behavior until all projects can reliably use the new behavior, while at the same time upgrading tools centrally to the new version of `@web/rollup-plugin-html`.

### Handling absolute paths

If your HTML file contains any absolute paths they will be resolved against the current working directory. You can set a different root directory in the config. Input paths will be resolved relative to this root directory as well.
Expand Down Expand Up @@ -361,7 +394,7 @@ export interface RollupPluginHTMLOptions {
/** Transform HTML file before output. */
transformHtml?: TransformHtmlFunction | TransformHtmlFunction[];
/** Whether to extract and bundle assets referenced in HTML. Defaults to true. */
extractAssets?: boolean;
extractAssets?: boolean | 'legacy-html' | 'legacy-html-and-css';
/** Whether to ignore assets referenced in HTML and CSS with glob patterns. */
externalAssets?: string | string[];
/** Define a full absolute url to your site (e.g. https://domain.com) */
Expand Down
56 changes: 56 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions packages/rollup-plugin-html/MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Migration

## From version 2.x.x to 3.x.x

Remove `bundleAssetsFromCss` configuration option, now we bundle assets referenced in CSS by default.

Check all output assets since now we handle all link `rel` types, specifically:

- icon
- apple-touch-icon
- mask-icon
- stylesheet
- manifest
- preload
- prefetch
- modulepreload

If any of them reference external assets or assets that don't need to be bundled, you can exclude such assets using the `externalAssets` configuration option.

For old behavior which is only recommeneded during the migration you can check legacy modes `extractAssets: 'legacy-html'` and `extractAssets: 'legacy-html-and-css'` in the documentation.
3 changes: 3 additions & 0 deletions packages/rollup-plugin-html/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@
"picomatch": "^2.2.2"
},
"devDependencies": {
"@prettier/sync": "^0.6.1",
"@types/html-minifier-terser": "^7.0.0",
"@types/picomatch": "^2.2.1",
"@types/prettier": "^3.0.0",
"prettier": "^3.6.2",
"rollup": "^4.4.0"
}
}
6 changes: 2 additions & 4 deletions packages/rollup-plugin-html/src/RollupPluginHTMLOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export interface RollupPluginHTMLOptions {
transformAsset?: TransformAssetFunction | TransformAssetFunction[];
/** Transform HTML file before output. */
transformHtml?: TransformHtmlFunction | TransformHtmlFunction[];
/** Whether to extract and bundle assets referenced in HTML. Defaults to true. */
extractAssets?: boolean;
/** Whether to extract and bundle assets referenced in HTML and CSS. Defaults to true. */
extractAssets?: boolean | 'legacy-html' | 'legacy-html-and-css';
/** Whether to ignore assets referenced in HTML and CSS with glob patterns. */
externalAssets?: string | string[];
/** Define a full absolute url to your site (e.g. https://domain.com) */
Expand All @@ -43,8 +43,6 @@ export interface RollupPluginHTMLOptions {
absolutePathPrefix?: string;
/** When set to true, will insert meta tags for CSP and add script-src values for inline scripts by sha256-hashing the contents */
strictCSPInlineScripts?: boolean;
/** Bundle assets reference from CSS via `url` */
bundleAssetsFromCss?: boolean;
}

export interface GeneratedBundle {
Expand Down
35 changes: 27 additions & 8 deletions packages/rollup-plugin-html/src/assets/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,18 @@ import { findElements, getTagName, getAttribute } from '@web/parse5-utils';
import { createError } from '../utils.js';
import { serialize } from 'v8';

const hashedLinkRels = ['stylesheet'];
const linkRels = [...hashedLinkRels, 'icon', 'manifest', 'apple-touch-icon', 'mask-icon'];
const assetLinkRels = [
'icon',
'apple-touch-icon',
'mask-icon',
'stylesheet',
'manifest',
'preload',
'prefetch',
'modulepreload',
];
const legacyHashedLinkRels = ['stylesheet'];
const assetMetaProperties = ['og:image'];

function getSrcSetUrls(srcset: string) {
if (!srcset) {
Expand Down Expand Up @@ -41,13 +51,14 @@ function isAsset(node: Element) {
path = extractFirstUrlOfSrcSet(node) ?? '';
}
break;
case 'link':
if (linkRels.includes(getAttribute(node, 'rel') ?? '')) {
case 'link': {
if (assetLinkRels.includes(getAttribute(node, 'rel') ?? '')) {
path = getAttribute(node, 'href') ?? '';
}
break;
}
case 'meta':
if (getAttribute(node, 'property') === 'og:image' && getAttribute(node, 'content')) {
if (assetMetaProperties.includes(getAttribute(node, 'property') ?? '')) {
path = getAttribute(node, 'content') ?? '';
}
break;
Expand All @@ -70,16 +81,24 @@ function isAsset(node: Element) {
}
}

export function isHashedAsset(node: Element) {
export function isHashedAsset(
node: Element,
extractAssets: boolean | 'legacy-html' | 'legacy-html-and-css',
) {
switch (getTagName(node)) {
case 'img':
return true;
case 'source':
return true;
case 'script':
return true;
case 'link':
return hashedLinkRels.includes(getAttribute(node, 'rel')!);
case 'link': {
if (extractAssets === 'legacy-html' || extractAssets === 'legacy-html-and-css') {
return legacyHashedLinkRels.includes(getAttribute(node, 'rel') ?? '');
} else {
return true;
}
}
case 'meta':
return true;
default:
Expand Down
Loading
Loading