npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@localnerve/web-component-build

v4.0.2

Published

A library to help build web components

Readme

Web Component Build

Assembles a web component from css, html, and js parts, assists user build flexibility

npm version Verify Coverage Status

Assembles a web component from its parts, allows developers to author the component's parts in separate files.
The parts are processed and written to an output directory, then exposed to a calling build process.

Why This Exists

  1. Author web components in separate JS, CSS, and HTML files
  2. Expose CSS for the web component to builds for computing CSP hashes
  3. Expose HTML for the web component to builds for companion templates and/or DSD for SSR builds
  4. Enable/ease paying these conveniences forward in web component distribution packages

Examples

New here? The examples/ directory has seven self-contained examples — each with its own fixtures, build script, and README — covering the most common ways to build a web component with this library. Clone the repo and run any one from the root:

node examples/js-css-html/build.mjs   # the canonical js + css + html build
  • js-css-html — minified css + html injected into a JS token; all three outputs written
  • pure-js — javascript-only components (no templates)
  • pure-css — minify a stylesheet for distribution / CSP hashes
  • inline-style-no-html — css injected as a bare <style> payload, no html file
  • link-href — reference an external stylesheet with a <link> tag instead of inlining css
  • multi-template — several authored states (default / empty / error) and the sharedMultiTemplate option, with a renderable demo page
  • trusted-types — components authored against the Trusted Types helpers, with an XSS-probe demo

Processing Map

The following is a table of some of the possible input, processing, and output combos. See options for detailed explanations.

| input | processing | output | | ----- | ---------- | ------ | | javascript | minify javascript | javascript | | css | minify css | css | | html | minify html | html | | css, html | minify css, prepend style tag to html, minify html | css, html | | css, html, cssHref | minfy css, prepend style tag to html, prepend link tag to html, minify html | css, html | | cssHref, html | prepend link tag to html, minify html | html | | javascript, css | minify css, merge style tag into javascript, minify javascript | css, javascript | | javascript, css, html | minify css, prepend style tag to html, minify html, merge into javascript, minify javascript | css, html, javascript | | javascript, css, html, cssHref | minify css, prepend style tag to html, prepend link tag to html, minify html, merge into javascript, minify javascript | css, html, javascript | | javascript, html | minify html, merge into javascript, minify javascript | html, javascript | | javascript, html, cssHref | prepend link tag to html, minify html, merge into javascript, minify javascript | html, javascript | | javascript, cssHref | add link tag to javascript, minify javascript | javascript |

By default, html minification minifies any css found therein.

Templates

The templates array is how html (and css/link) are described to the build. A component can carry several HTML templates (e.g. default / error / empty states), each referenced by its own distinct token in the javascript:

const result = await build(outputDir, {
  jsPath: '/some/path/file.js',
  cssPath: '/some/path/file.css',          // shared; embedding follows `sharedMultiTemplate` (default "first")
  templates: [
    { name: 'default', htmlPath: '/some/path/default.html', token: '__TPL_DEFAULT__' },
    { name: 'error',   htmlPath: '/some/path/error.html',   token: '__TPL_ERROR__' }
  ]
});
// result.html -> { default: {name, path, getHtml}, error: {...} } keyed by template name

Each entry takes name (output filename, defaults to the input basename), htmlPath, token (String or RegExp), and optional per-template cssPath / cssLinkHref overrides that fall back to the shared values. A template may omit htmlPath (then only its css/link payload is injected). Pure javascript or css builds pass no templates.

By default (sharedMultiTemplate: "first") the shared cssPath/cssLinkHref are embedded only in the first template that uses them, so several templates of one component placed into a single shadow root do not duplicate the css. Set sharedMultiTemplate: "every" to embed the shared styles in each template's output instead, keeping every html self-contained (needed when a template may ship alone, e.g. per-state SSR or standalone fragments). Per-template cssPath/cssLinkHref overrides are always embedded in their own template.

Injection is syntax-aware: markup is spliced into the token's string/template literal with escaping for that context, so it may safely contain quotes, backticks, ${, or backslashes.

Tokens MUST be unique in the javascript source. The injector locates each token by its first occurrence in the file — if a token string also appears in a comment, a log message, or any other place, injection targets that occurrence instead (and throws when it isn't inside a string/template literal). Pick tokens that can only ever appear as the replacement placeholder (e.g. __MY_COMPONENT_TPL__), and don't write them anywhere else in the file.

Trusted Types Helpers

In addition to build, this package exports a small set of browser runtime helpers so that web components can be authored to work with and without Trusted Types enforcement (CSP require-trusted-types-for 'script').

export { escapeHtml, getTrustedPolicy, trustedHtml }

The idea: a component registers its own named policy for the static, author-controlled markup it injects into sinks (innerHTML, etc.), escapes any user-influenced values before composing them, and falls back to plain-string passthrough in browsers (or builds) that do not enforce Trusted Types. Import these helpers directly into your component source — they are small, dependency-free, and get tree-shaken/inlined into the final bundle at build time (a consuming component does not need a runtime dependency on this package).

escapeHtml(input)

Applies the industry-standard 6-character escape — & < > " ' \`` → & < > " ' `— for safe interpolation into HTML markup. Escaping quotes and the backtick (not just</>) is what makes the result safe to reuse inside an **attribute value**, not only element content (matches he.escape). null/undefinedyield''`. Use it for any user-influenced value before composing it into markup.

import { escapeHtml } from '@localnerve/web-component-build';
const html = `<li>${escapeHtml(key)}: ${escapeHtml(value)}</li>`;
// safe to interpolate into text OR a quoted attribute value

getTrustedPolicy(name, hooks)

Gets (creating once) a named Trusted Type policy. A name may only be created once without the CSP allow-duplicates keyword, so repeated calls reuse the instance. Returns null when Trusted Types is unavailable (passthrough mode). The default hooks are pass-through createHTML/createScriptURL, appropriate for author-controlled content that has already been escaped where needed.

import { getTrustedPolicy } from '@localnerve/web-component-build';
const policy = getTrustedPolicy('my-component'); // null if TT unavailable

trustedHtml(policyName, html)

Converts author-controlled (or pre-escaped) markup into a value safe to pass to an HTML injection sink under Trusted Types enforcement. Returns a TrustedHTML when enforced, otherwise the input string unchanged (passthrough for dev builds / browsers without Trusted Types).

import { trustedHtml } from '@localnerve/web-component-build';
shadowRoot.innerHTML = trustedHtml('my-component', '<div>…static template…</div>');

The policy name must be allowlisted in the site's CSP trusted-types directive, e.g. trusted-types default my-component;. A build step can compute that allowlist from your sources — see @localnerve/trusted-types-rules. See editable-object for a complete, real-world example of a component built on these helpers.

Usage

  // Sample usage, all options specified
  import {build} from '@localnerve/web-component-build';
  const outputDir = 'some/path/output';

  const result = await build(outputDir, {
    cssPath: '/some/path/file.css',
    cssLinkHref: '//some/path/file.css',
    jsPath: '/some/path/file.js',
    templates: [
      { name: 'index', htmlPath: '/some/path/file.html', token: '__REPLACEMENT_IN_JS__' }
    ],
    terserOptions: { /* terser options */ },
    htmlminOptions: { /* html-minifier options */ },
    cleancssOptions: { /* clean-css options */ },
    minifySkip: false
  });
  // html, js, and css written to `outputDir`
  
  // Retrieve processed content
  const [js, css, html] = await Promise.all([
    result.getJs(), result.getCss(), result.html.index.getHtml()
  ]);

  // Retrieve output paths
  const [jsPath, cssPath, htmlPath] =
    [result.jsPath, result.cssPath, result.html.index.path];

API

This library exports a single function that takes an output directory and processing options, returns a result object.

build (outputDir, options): Result

outputDir {String}, required

Full path to the output directory where css, html, and javascript output are written. The directory must already exist — build() throws upfront if it doesn't (or is not a directory), and never creates or cleans it itself. Creating it is the caller's job (fs.mkdir(outputDir, { recursive: true })); cleaning stale outputs between builds is up to your build pipeline too.

Options {Object}, optional*

* Not really. One or more of cssPath, jsPath, and/or htmlPath must be supplied. They have no default, so if no options are supplied, this library throws an exception.

  • cssPath {String} - Full path to the input css file
    If supplied:

    • css will be minified using cleancssOptions
    • css will be wrapped in a style tag
    • css will be inserted into the javascript file if jsReplacement and jsPath are supplied and no htmlPath supplied
    • css will be prepended to the html file if htmlPath is supplied
  • cssLinkHref {String} - link href to a stylesheet resource to be referenced by the web component
    If supplied:

    • href will be wrapped in a link tag
    • resulting link will be prepended to the html file if htmlPath supplied
    • resulting link will be inserted into the javascript file if no htmlPath supplied and jsReplacement and jsPath supplied
  • sharedMultiTemplate {String} - How SHARED cssPath/cssLinkHref are embedded across templates. Defaults to "first": shared styles are embedded only in the first template that uses them, so several templates of one component placed into a single shadow root do not duplicate the css (later templates carry markup only). Use "every" to embed the shared styles in each template's output, keeping every html self-contained (needed when a template may ship alone, e.g. per-state SSR/standalone fragments). Per-template cssPath/cssLinkHref overrides are always embedded in their own template, in either mode. Any other value throws.

  • jsPath {String} - Full path to the input javascript file

  • templates {Array} - Zero or more templates, each an object with:

    • name {String} - Output filename without extension (written as ${name}.html). Defaults to the input basename.
    • htmlPath {String} - Full path to the input html for this template.
    • token {String|RegExp} - The placeholder in the javascript to replace. See pattern. Must be unique in the source file — the first occurrence is the one replaced, so a token string mentioned in a comment or other literal redirects injection there.
    • cssPath {String} - Optional per-template css override (falls back to the shared cssPath).
    • cssLinkHref {String} - Optional per-template link href override (falls back to the shared cssLinkHref).
      Shared cssPath/cssLinkHref follow the sharedMultiTemplate mode: embedded in the first template that uses them by default ("first"), or in every template when sharedMultiTemplate: "every". Per-template overrides are always embedded in their own template. A token with no jsPath throws, as do duplicate resolved output names and invalid sharedMultiTemplate values. The flat htmlPath/jsReplacement options were removed in v4 and now throw a migration error.
  • terserOptions {Object} - The javascript minifier options object
    Defaults:

    {
      ecma: 2022
    }
  • htmlminOptions {Object} - The html minifier options object
    Defaults:

    {
      minifyJS: true,
      minifyCSS: true,
      collapseWhitespace: true,
      removeAttributeQuotes: true,
      removeComments: true
    }
  • cleancssOptions {Object} - The css minifier options object
    Defaults (same as clean-css defaults)

  • minifySkip {Boolean} - True to skip all minifications, defaults to false

  • deprecationWarnings {Boolean} - False to disable deprecation warnings, defaults to true. If omitted, deprecation warnings are suppressed by defining environment variable WEB_COMPONENT_BUILD_NO_DEPRECATION_WARNINGS

Result {Object}

The output of the build process. Allows access to the output paths and full output content. Format:

  • cssPath {String}, The full path to the output css

  • jsPath {String}, The full path to the output javascript

  • getCss {asyncFunction}, gets the output css

  • getJs {asyncFunction}, gets the output javascript

  • html {Object}, A map keyed by template name. Each entry: { name, path, getHtml } where path is the full path to that template's output html and getHtml (async) returns its content.

License