HTML Bundle Style Plugin
A plugin for staticbolt that bundles <link rel="stylesheet"> and <style> tags in HTML files by inlining their @import tree. Bundling is opt-in per tag via the bundle attribute.
Installation
The plugin ships as part of @staticbolt/core. You do not need to install anything else.
Usage
In your .staticbolt.ts configuration file:
import { defineConfig } from "@staticbolt/core";import { htmlBundleStylePlugin } from "@staticbolt/core/plugins";
export default defineConfig({ plugins: [htmlBundleStylePlugin()],});Bundling a stylesheet
Add the bundle attribute to a <link rel="stylesheet">, a <link rel="preload" as="style"> or a <style> tag to have it bundled at build time. Bundling only happens in production. During development the plugin strips its attributes and bundles nothing. The attributes never reach the output either way.
External stylesheet (<link>)
The bundle goes to a new file, {filename}.bundle.css next to the original, and href points at it instead. The source file is never modified and stays in the output as it is.
<!-- Input --><link rel="stylesheet" href="./styles.css" bundle />
<!-- Output --><link rel="stylesheet" href="./styles.bundle.css" />A stylesheet linked from several pages is bundled once, and every page points at the same file.
Inline style tag
The plugin resolves the @import statements and replaces the tag's content with the fully inlined CSS.
<!-- Input --><style bundle> @import "./variables.css"; @import "./components.css";</style>
<!-- Output --><style> :root { --color: #fff; } .btn { color: var(--color); }</style>Output path
bundle-out overrides where a <link> bundle is written. The value always resolves from the output directory, no matter where the HTML file lives.
<link rel="stylesheet" href="./styles.css" bundle bundle-out="./css/main.css" />
<!-- Output: <outdir>/css/main.css --><link rel="stylesheet" href="../css/main.css" />If the target path has already been bundled in this build, the plugin skips the work and points href at it.
Externals
Glob patterns can keep an @import as an import instead of inlining it. They match against the resolved path of the import, at any depth. Relative paths, aliases and packages are all matchable.
externals/externalsExcludeplugin options set the defaults.bundle-externals/bundle-externals-excludeattributes override them for one tag, as semicolon-separated globs.externalsExcludewins overexternals. That lets you carve an exception out of a broad pattern.
<link rel="stylesheet" href="./styles.css" bundle bundle-externals="**/node_modules/**" />A kept @import is hoisted to the top with its conditions folded in, and its path is rewritten relative to where the bundle ends up. The file it points at is not part of the bundle. It has to reach the output some other way.
The default is []. Everything is inlined, including packages such as normalize.css.
How imports are resolved
Imports resolve like every other source in the build: relative paths, path aliases (e.g. ~/styles/base.css) and packages (@import "normalize.css", @import "@scope/pkg/styles.css"). Imported files come from the build, so the transforms that other plugins applied to them are kept, and their url() references are rebased onto the bundle's location. They stay in the output as separate files.
layer(),supports()and media conditions wrap the imported rules. Nested imports nest their conditions.- A file imported more than once under the same conditions is kept once, where it was imported last. That is the same cascade a browser ends up with. Cyclic imports are cut.
- Imports of other origins (
https://…,//…,data:) are kept and hoisted to the top, with their conditions folded in. - An import that cannot be resolved is reported and kept. An
@importplaced after other rules is reported and left alone, since a browser ignores it too.
Combining with inline
Register this plugin before htmlInlineStylePlugin(). A <link> carrying both bundle and inline is then bundled first, so the inlined content is the bundle. The bundle file itself still lands in the output. analyzeOutputPlugin({ skipUnusedFiles: true }) keeps it out when nothing references it.
<link rel="stylesheet" href="./styles.css" bundle inline />Plugin Options
externals
- Type:
string[] - Default:
[]
Glob patterns for imports to keep as external @imports, matched against the resolved import path. They apply when bundle-externals is not set on the tag.
externalsExclude
- Type:
string[] - Default:
[]
Glob patterns that exempt imports from being external even if they match externals. They apply when bundle-externals-exclude is not set on the tag.
bundleAttribute
- Type:
string - Default:
"bundle"
The attribute name that opts a tag into bundling.
bundleOutAttribute
- Type:
string - Default:
"bundle-out"
The attribute name that sets the output path of a <link> bundle, relative to the output directory.
externalsAttribute
- Type:
string - Default:
"bundle-externals"
The attribute name that sets the externals globs for one tag, semicolon-separated.
externalsExcludeAttribute
- Type:
string - Default:
"bundle-externals-exclude"
The attribute name that sets the externals exemptions for one tag, semicolon-separated.