Skip to content

Instantly share code, notes, and snippets.

@lukastaegert
Last active July 21, 2022 03:21
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lukastaegert/fa8f7bbdc2caf4fb6797730f05e3f616 to your computer and use it in GitHub Desktop.
Save lukastaegert/fa8f7bbdc2caf4fb6797730f05e3f616 to your computer and use it in GitHub Desktop.
Rollup plugins to emit chunks and CSS assets from an HTML file and add an updated HTML file to the build, and to emit assets from a JS file.
export const rollupLogo = "./logo.svg";
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" type="text/css" href="styles.css" />
<title>Plugin demo</title>
</head>
<body>
<script src="index.js" type="module"></script>
</body>
</html>
import { rollupLogo } from "./assets.js";
const image = document.createElement("img");
image.src = rollupLogo;
document.body.appendChild(image);
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
{
"type": "module",
"scripts": {
"build": "rollup -c"
},
"dependencies": {
"libxmljs2": "^0.25.1",
"rollup": "^2.6.0"
}
}
import * as fs from "fs";
import * as path from "path";
import libXml from "libxmljs2";
// This plugin takes the path of an HTML file, scans for script tags and emits
// those as chunks. Then it scans for stylesheets and emits those as assets. At
// last, it creates a rewritten HTML file with the paths to the new chunks and
// assets (by default containing content-hashed file names) and adds it to the
// bundle as well.
export default function buildFromHtml(htmlFile) {
const fileName = path.resolve(htmlFile);
const doc = libXml.parseHtml(fs.readFileSync(fileName, "utf8"));
const scriptFileReferences = new Map();
const styleFileReferences = new Map();
return {
name: "generate-html",
buildStart() {
for (const tag of doc.find("//script")) {
scriptFileReferences.set(
tag,
this.emitFile({
type: "chunk",
id: tag.attr("src").value(),
})
);
}
for (const tag of doc.find("//link[@rel='stylesheet']")) {
styleFileReferences.set(
tag,
this.emitFile({
type: "asset",
name: path.basename(tag.attr("href").value()),
source: fs.readFileSync(
path.resolve(path.dirname(fileName), tag.attr("href").value())
),
})
);
}
},
generateBundle() {
for (const [tag, referenceId] of scriptFileReferences) {
tag.attr("src", this.getFileName(referenceId));
}
for (const [tag, referenceId] of styleFileReferences) {
tag.attr("href", this.getFileName(referenceId));
}
this.emitFile({
type: "asset",
source: doc.toString(false),
fileName: "index.html",
});
},
};
}
import fs from "fs";
import path from "path";
import { rollup } from "rollup";
// This plugin takes the path of a JavaScript file that exports some constants
// which are interpreted as relative paths to assets. These files are added to
// the Rollup bundle and the file is rewritten to point to the new assets (by
// default with content-hashed file names).
export default function emitAssetsFromFile(assetsFile) {
const fileName = path.resolve(assetsFile);
let assetFileFound = false;
return {
name: "emit-assets",
async load(id) {
if (id === fileName) {
assetFileFound = true;
// This will only run if you are using Node 13+ and either the fileName
// has an .mjs extension or you have "type": "module" in your
// package.json. Otherwise replace import with getExportsFromFile
// below.
// const assets = await getExportsFromFile(fileName);
const assets = await import(fileName);
return Object.keys(assets)
.map((assetKey) => {
const assetFileName = path.resolve(assets[assetKey]);
return `export const ${assetKey} = import.meta.ROLLUP_FILE_URL_${this.emitFile(
{
type: "asset",
source: fs.readFileSync(path.resolve(assetFileName)),
name: path.basename(assetFileName),
}
)};`;
})
.join("\n");
}
},
buildEnd() {
if (assetFileFound === false) {
throw new Error(
`Expected assets file "${fileName}" was not part of the module graph.`
);
}
},
};
}
// Helper to convert an ESM file to CJS, turn it into a function and run it to
// get its exports.
async function getExportsFromFile(fileName) {
const getAssets = new Function(
"module",
"exports",
(
await (await rollup({ input: fileName })).generate({ format: "cjs" })
).output[0].code
);
const module = { exports: {} };
getAssets(module, module.exports);
return module.exports;
}
import buildFromHtml from "./rollup-plugin-build-from-html.js";
import emitAssetsFromFile from "./rollup-plugin-emit-assets-from-file.js";
// See how we do not need an `input` option here at all!
// All chunks are created from the HTML file.
export default {
output: {
dir: "dist",
format: "esm",
},
plugins: [buildFromHtml("index.html"), emitAssetsFromFile("assets.js")],
};
body {
color: blue;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment