Building: Rollup Plugin HTML
Plugin for bundling HTML files. Bundles module scripts and linked assets in HTML files and injects the hashed filenames.
Installation
npm install --save-dev @web/rollup-plugin-html
Usage
Single page
If you have a single HTML page, you can set it as rollup input. This will be used by the HTML plugin as input for the plugin.
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
export default {
input: 'index.html',
output: { dir: 'dist' },
plugins: [html()],
};
Multiple pages
If all pages share the same config, you can use a glob pattern to match multiple HTML files.
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
export default {
input: 'pages/*.html',
output: { dir: 'dist' },
plugins: [html()],
};
If your pages cannot be matched with a single glob you can set the input directly on HTML plugin.
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
export default {
output: { dir: 'dist' },
plugins: [html({ input: ['index.html', 'static/page.html'] })],
};
If each input should be bundled with a different config, you can create multiple instances of the HTML plugin.
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
export default {
output: { dir: 'dist' },
plugins: [
// add multiple HTML plugins
html({ input: 'index.html' }),
html({ input: 'static/page.html' }),
],
};
HTML as string
If your HTML file does not exist on disk, you can provide it as a string as well.
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
export default {
output: { dir: 'dist' },
plugins: [
html({
input: {
html: '<html><body><script type="module" src="./app.js"></script></body></html>',
// defaults to index.html
name: 'foo.html',
},
}),
],
};
This can also be set as an array:
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
export default {
output: { dir: 'dist' },
plugins: [
html({
input: [
{ html: '<html><body><script type="module" src="./app.js"></script></body></html>' },
{ html: '<html><body><script type="module" src="./foo.js"></script></body></html>' },
],
}),
],
};
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.
By default rollup will hash the asset filenames, enabling long term caching. You can customize the filename pattern using the assetFileNames option in your rollup config.
To turn off bundling assets completely, set the extractAssets
option to false:
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
export default {
input: 'index.html',
output: { dir: 'dist' },
plugins: [
html({
extractAssets: false,
}),
],
};
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.
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
export default {
input: 'index.html',
output: { dir: 'dist' },
plugins: [
// add HTML plugin
html({ rootDir: path.join(process.cwd(), '_site') }),
],
};
Preserving directory structure
To preserve the directory structure of HTML files you can set the flattenOutput
option to false. The directory structure relative to the root dir will be preserved.
In the example below, the following files:
_site/index.html
_site/pages/page-b.html
_site/pages/bar/page-c.html
Will be output as:
dist/index.html
dist/pages/page-b.html
dist/pages/bar/page-c.html
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
export default {
input: 'pages/**/*.html',
output: { dir: 'dist' },
plugins: [
// add HTML plugin
html({ rootDir: path.join(process.cwd(), '_site'), flattenOutput: false }),
],
};
Transforming HTML files and assets
You can add transform functions to modify the HTML page and assets in the build, for example to inject scripts or minification.
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
export default {
input: 'index.html',
output: { dir: 'dist' },
plugins: [
// add HTML plugin
html({
transformHtml: [html => html.replace('<body>', '<body><script>...</script>')],
transformAsset: [(content, filePath) => {
if (filePath.endsWith('.svg')) {
// content is a buffer, you can turn it a string for utf-8 assets
const svgContent = content.toString('utf-8');
return /* transform the SVG */;
}
if (filePath.endsWith('.png')) {
return /* transform the PNG */;
}
},
}),
],
};
Minification
Set the minify option to do default HTML minificiation. If you need custom options, you can implement your own minifier using the transformHtml
option.
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
export default {
input: 'index.html',
output: { dir: 'dist' },
plugins: [
// add HTML plugin
html({
minify: true,
}),
],
};
Social Media Tags
Some social media tags require full absolute URLs (e.g. https://domain.com/guide/).
By providing an absoluteBaseUrl
the plugin can make sure all appropriate URLs are processed.
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
export default {
input: 'index.html',
output: { dir: 'dist' },
plugins: [
html({
absoluteBaseUrl: 'https://domain.com',
}),
],
};
The following tags will be processed:
<!-- FROM -->
<meta property="og:image" content="./images/image-social.png" />
<link rel="canonical" href="/guides/" />
<meta property="og:url" content="/guides/" />
<!-- TO -->
<meta property="og:image" content="https://domain.com/assets/image-social-xxx.png" />
<link rel="canonical" href="https://domain.com/guides/" />
<meta property="og:url" content="https://domain.com/guides/" />
You can disable this behavior by removing the absoluteBaseUrl
or setting absoluteSocialMediaUrls
to false.
Inject a Service Worker
In order to enable PWA support you can enable the injection of a service worker registration code block.
Note: This does not create the service worker
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
export default {
input: 'index.html',
plugins: [
html({
injectServiceWorker: true,
serviceWorkerPath: '/file/system/path/to/service-worker.js',
}),
],
};
Strict CSP for inline scripts
To prevent XSS, there is a rule in Content-Security-Policy guidelines called script-src.
Some servers will (rightfully so) set this value to 'self', sometimes adding a whitelist of other origins e.g. for Google Analytics.
This makes it impossible for inline scripts to execute. There's an ugly way around that, by setting CSP rule unsafe-inline
.
There's also a proper workaround, which is by either using hashes or a nonce to allow inline scripts to run. Quite often, rollup plugins will insert inline scripts, e.g. to load polyfills, SystemJS or other common use cases.
In this plugin, you can pass the option strictCSPInlineScripts
and set it to true.
The plugin will then scan HTML assets for inline scripts and turn its contents into a sha256 hash.
These hashes are then inserted in a CSP meta
tag in the HTML asset, enabling these inline scripts to run even under strict CSP rules.
Caveat: If you set CSP rules in your Response headers on the server end, this will override the CSP meta tag from the client.
Type definitions
import { OutputChunk, OutputOptions, OutputBundle } from 'rollup';
export interface InputHTMLOptions {
/** The html source code. If set, overwrites path. */
html?: string;
/** Name of the HTML files when using the html option. */
name?: string;
/** Path to the HTML file, or glob to multiple HTML files. */
path?: string;
}
export interface RollupPluginHTMLOptions {
/** HTML file(s) to use as input. If not set, uses rollup input option. */
input?: string | InputHTMLOptions | (string | InputHTMLOptions)[];
/** HTML file glob patterns or patterns to ignore */
exclude?: string | string[];
/** Whether to minify the output HTML. */
minify?: boolean;
/** Whether to preserve or flatten the directory structure of the HTML file. */
flattenOutput?: boolean;
/** Directory to resolve absolute paths relative to, and to use as base for non-flatted filename output. */
rootDir?: string;
/** Path to load modules and assets from at runtime. */
publicPath?: string;
/** Transform asset source before output. */
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;
/** Define a full absolute url to your site (e.g. https://domain.com) */
absoluteBaseUrl?: string;
/** Whether to set full absolute urls for ['meta[property=og:image]', 'link[rel=canonical]', 'meta[property=og:url]'] or not. Requires a absoluteBaseUrl to be set. Default to true. */
absoluteSocialMediaUrls?: boolean;
/** Should a service worker registration script be injected. Defaults to false. */
injectServiceWorker?: boolean;
/** File system path to the generated service worker file */
serviceWorkerPath?: string;
/** Prefix to strip from absolute paths when resolving assets and scripts, for example when using a base path that does not exist on disk. */
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;
}
export interface GeneratedBundle {
name: string;
options: OutputOptions;
bundle: OutputBundle;
}
export interface EntrypointBundle extends GeneratedBundle {
entrypoints: {
// path to import the entrypoint, can be used in an import statement
// or script tag directly
importPath: string;
// associated rollup chunk, useful if you need to get more information
// about the chunk. See the rollup docs for type definitions
chunk: OutputChunk;
}[];
}
export interface TransformHtmlArgs {
// the rollup bundle to be injected on the page. if there are multiple
// rollup output options, this will reference the first bundle
//
// if one of the input options was set, only the bundled module script contained
// in the HTML input are available to be injected in both the bundle and bundles
// options
bundle: EntrypointBundle;
// the rollup bundles to be injected on the page. if there is only one
// build output options, this will be an array with one option
bundles: Record<string, EntrypointBundle>;
htmlFileName: string;
}
export type TransformHtmlFunction = (
html: string,
args: TransformHtmlArgs,
) => string | Promise<string>;
export type TransformAssetFunction = (
content: Buffer,
filePath: string,
) => string | Buffer | Promise<string | Buffer>;