Skip to content

Commit 549eaf7

Browse files
committed
feat: support additional assets in css
1 parent 47728ce commit 549eaf7

File tree

13 files changed

+183
-16
lines changed

13 files changed

+183
-16
lines changed

docs/docs/building/rollup-plugin-html.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,45 @@ export default {
125125
};
126126
```
127127

128+
#### Including assets referenced from css
129+
130+
If your css files reference other assets via `url`, like for example:
131+
132+
```css
133+
body {
134+
background-image: url('images/star.gif');
135+
}
136+
137+
/* or */
138+
@font-face {
139+
src: url('fonts/font-bold.woff2') format('woff2');
140+
/* ...etc */
141+
}
142+
```
143+
144+
You can enable the `bundleAssetsFromCss` option:
145+
146+
```js
147+
rollupPluginHTML({
148+
bundleAssetsFromCss: true,
149+
// ...etc
150+
});
151+
```
152+
153+
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.:
154+
155+
```css
156+
body {
157+
background-image: url('assets/star-P4TYRBwL.gif');
158+
}
159+
160+
/* or */
161+
@font-face {
162+
src: url('assets/font-bold-f0mNRiTD.woff2') format('woff2');
163+
/* ...etc */
164+
}
165+
```
166+
128167
### Handling absolute paths
129168

130169
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.

packages/rollup-plugin-html/src/RollupPluginHTMLOptions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ export interface RollupPluginHTMLOptions {
4141
absolutePathPrefix?: string;
4242
/** When set to true, will insert meta tags for CSP and add script-src values for inline scripts by sha256-hashing the contents */
4343
strictCSPInlineScripts?: boolean;
44+
/** Bundle assets reference from CSS via `url` */
45+
bundleAssetsFromCss?: boolean;
4446
}
4547

4648
export interface GeneratedBundle {

packages/rollup-plugin-html/src/output/emitAssets.ts

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,22 @@ export interface EmittedAssets {
1111
hashed: Map<string, string>;
1212
}
1313

14-
function shouldHandleFontFile(url: string) {
14+
const allowedFileExtensions = [
15+
// https://www.w3.org/TR/html4/types.html#:~:text=ID%20and%20NAME%20tokens%20must,tokens%20defined%20by%20other%20attributes.
16+
/.*\.svg(#[A-Za-z][A-Za-z0-9\-_:.]*)?/,
17+
/.*\.png/,
18+
/.*\.jpg/,
19+
/.*\.jpeg/,
20+
/.*\.webp/,
21+
/.*\.gif/,
22+
/.*\.avif/,
23+
/.*\.woff2/,
24+
/.*\.woff/,
25+
];
26+
27+
function shouldHandleAsset(url: string) {
1528
return (
16-
(url.endsWith('.woff2') || url.endsWith('.woff')) &&
29+
allowedFileExtensions.some(f => f.test(url)) &&
1730
!url.startsWith('http') &&
1831
!url.startsWith('data') &&
1932
!url.startsWith('#') &&
@@ -69,34 +82,42 @@ export async function emitAssets(
6982

7083
let ref: string;
7184
let basename = path.basename(asset.filePath);
72-
const emittedFonts = new Map();
85+
const emittedExternalAssets = new Map();
7386
if (asset.hashed) {
74-
if (basename.endsWith('.css')) {
87+
if (basename.endsWith('.css') && options.bundleAssetsFromCss) {
7588
let updatedCssSource = false;
7689
const { code } = await transform({
7790
filename: basename,
7891
code: asset.content,
7992
minify: false,
8093
visitor: {
8194
Url: url => {
82-
if (shouldHandleFontFile(url.url)) {
95+
// Support foo.svg#bar
96+
const [filePath, idRef] = url.url.split('#');
97+
98+
if (shouldHandleAsset(url.url)) {
8399
// Read the font file, get the font from the source location on the FS using asset.filePath
84-
const fontLocation = path.resolve(path.dirname(asset.filePath), url.url);
85-
const fontContent = fs.readFileSync(fontLocation);
100+
const assetLocation = path.resolve(path.dirname(asset.filePath), filePath);
101+
const assetContent = fs.readFileSync(assetLocation);
86102

87103
// Avoid duplicates
88-
if (!emittedFonts.has(fontLocation)) {
104+
if (!emittedExternalAssets.has(assetLocation)) {
89105
const fontFileRef = this.emitFile({
90106
type: 'asset',
91-
name: path.basename(url.url),
92-
source: fontContent,
107+
name: path.basename(filePath),
108+
source: assetContent,
93109
});
94110
const emittedFontFilePath = path.basename(this.getFileName(fontFileRef));
95-
emittedFonts.set(fontLocation, emittedFontFilePath);
111+
emittedExternalAssets.set(assetLocation, emittedFontFilePath);
96112
// Update the URL in the original CSS file to point to the emitted font file
97-
url.url = emittedFontFilePath;
113+
url.url = `assets/${
114+
idRef ? `${emittedFontFilePath}#${idRef}` : emittedFontFilePath
115+
}`;
98116
} else {
99-
url.url = emittedFonts.get(fontLocation);
117+
const emittedFontFilePath = emittedExternalAssets.get(assetLocation);
118+
url.url = `assets/${
119+
idRef ? `${emittedFontFilePath}#${idRef}` : emittedFontFilePath
120+
}`;
100121
}
101122
}
102123
updatedCssSource = true;

packages/rollup-plugin-html/src/rollupPluginHTML.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export function rollupPluginHTML(pluginOptions: RollupPluginHTMLOptions = {}): R
7272
if (pluginOptions.strictCSPInlineScripts) {
7373
strictCSPInlineScripts = pluginOptions.strictCSPInlineScripts;
7474
}
75+
pluginOptions.bundleAssetsFromCss = !!pluginOptions.bundleAssetsFromCss;
7576

7677
if (pluginOptions.input == null) {
7778
// we are reading rollup input, so replace whatever was there
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
a
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)