Skip to content

Repository files navigation

carbon-preprocess-svelte

NPM npm downloads to date

A zero-dependency library providing Svelte preprocessors and build plugins for the Carbon Design System.

Installation

Install carbon-preprocess-svelte as a development dependency.

# npm
npm i -D carbon-preprocess-svelte

# pnpm
pnpm i -D carbon-preprocess-svelte

# Yarn
yarn add -D carbon-preprocess-svelte

# Bun
bun add -D carbon-preprocess-svelte

Usage

  • optimizeImports: Svelte preprocessor that rewrites Carbon Svelte imports to their source path in the script block, making development compile times dramatically faster.
  • optimizeCss: Vite/Rollup plugin that removes unused Carbon styles, resulting in smaller CSS bundles.
  • OptimizeCssPlugin: The corresponding optimizeCss plugin for Webpack and Rspack that removes unused Carbon styles.

optimizeImports

optimizeImports is a Svelte preprocessor that rewrites barrel imports from Carbon components/icons/pictograms packages to their source Svelte code paths. This can significantly speed up development and build compile times while preserving typeahead and autocompletion offered by integrated development environments (IDE) like VS Code.

The preprocessor optimizes imports from the following packages:

- import { Button } from "carbon-components-svelte";
+ import Button from "carbon-components-svelte/src/Button/Button.svelte";

- import { Add } from "carbon-icons-svelte";
+ import Add from "carbon-icons-svelte/lib/Add.svelte";

- import { Airplane } from "carbon-pictograms-svelte";
+ import Airplane from "carbon-pictograms-svelte/lib/Airplane.svelte";

Note

When this preprocessor was first created, there was no workaround to optimize slow cold start times with Vite in development. Today, @sveltejs/vite-plugin-svelte enables prebundleSvelteLibraries: true by default. However, this preprocessor is still useful for non-Vite bundlers, like Rollup and Webpack. Also, it can further improve cold start development times even with prebundleSvelteLibraries: true.

optimizeImports({ experimental: { liveIndex: true } }) builds its component index from your installed carbon-components-svelte instead of the version bundled with this package — see experimental.liveIndex under optimizeCss for details; the behavior is identical here.

SvelteKit

See examples/sveltekit.

// svelte.config.js
import adapter from "@sveltejs/adapter-static";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import { optimizeImports } from "carbon-preprocess-svelte";

/** @type {import('@sveltejs/kit').Config} */
const config = {
  preprocess: [
    // Preprocessors are run in sequence.
    // If using TypeScript, the code must be transpiled first.
    vitePreprocess(),
    optimizeImports(),
  ],
  kit: {
    adapter: adapter(),
  },
};

export default config;

Vite

See examples/vite.

// vite.config.js
import { svelte } from "@sveltejs/vite-plugin-svelte";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import { optimizeImports } from "carbon-preprocess-svelte";

/** @type {import('vite').UserConfig} */
export default {
  plugins: [
    svelte({
      preprocess: [
        // Preprocessors are run in sequence.
        // If using TypeScript, the code must be transpiled first.
        vitePreprocess(),
        optimizeImports(),
      ],
    }),
  ],
};

Rollup

This code is abridged; see examples/rollup for a full set-up.

// rollup.config.js
import svelte from "rollup-plugin-svelte";
import { optimizeImports } from "carbon-preprocess-svelte";

export default {
  plugins: [
    svelte({
      preprocess: [optimizeImports()],
    }),
  ],
};

Webpack

This code is abridged; see examples/webpack for a full set-up.

// webpack.config.mjs
import { optimizeImports } from "carbon-preprocess-svelte";

export default {
  module: {
    rules: [
      {
        test: /\.svelte$/,
        use: {
          loader: "svelte-loader",
          options: {
            hotReload: !PROD,
            preprocess: [optimizeImports()],
            compilerOptions: { dev: !PROD },
          },
        },
      },
    ],
  },
};

Rspack

Rspack implements webpack's plugin and loader APIs, so the set-up is the same as Webpack above (svelte-loader works unchanged). This code is abridged; see examples/rspack for a full set-up.

// rspack.config.mjs
import { optimizeImports } from "carbon-preprocess-svelte";

export default {
  module: {
    rules: [
      {
        test: /\.svelte$/,
        use: {
          loader: "svelte-loader",
          options: {
            hotReload: !PROD,
            preprocess: [optimizeImports()],
            compilerOptions: { dev: !PROD },
          },
        },
      },
    ],
  },
};

optimizeCss

optimizeCss is a Vite plugin that removes unused Carbon styles at build time. The plugin is compatible with Rollup (Vite extends the Rollup plugin API).

How it works

The plugin uses apply: "build" and enforce: "post", so it runs only on production builds and after other plugins.

  1. During transform, it collects absolute paths of imported carbon-components-svelte sources.
  2. During generateBundle, for each emitted CSS file it builds an allowlist of every bx-- class tied to those components via an internal index, plus global selectors like .bx--body.
  3. A PostCSS plugin prunes Carbon (bx--) selectors outside that allowlist:
    • Individual selectors are pruned out of comma-separated lists instead of keeping the whole rule when any one branch matches
    • Every Carbon class in a compound selector (same-element and descendant) must match the allowlist, so importing NumberInput doesn't pull in .bx--modal .bx--number context rules, and Button doesn't pull in Tabs skeleton styles via a shared .bx--skeleton modifier
    • Flatpickr and legacy single-hyphen bx- rules are dropped unless DatePicker (or another flatpickr-based component) is in the bundle
    • Selectors are parsed with parenthesis-awareness, so :is(...) and :not(...) groups are handled instead of naively split on commas
  4. Empty rules are discarded, and the CSS bundles are optimized.

Risk profile: this pruning is validated against a fixture suite covering most Carbon components and common multi-component bundles (see tests/fixtures/optimize-css) with zero unexplained survivors on every scenario, but it shares the blind spot described in the warning below — dynamically constructed and hand-written classes the allowlist can't see.

flowchart TB
  subgraph scan["Module scan"]
    T[transform hook] --> S["Collect imported Carbon<br/>component paths"]
  end
  subgraph emit["Bundle phase"]
    S --> G[generateBundle]
    G --> A["Allowlist bx-- selectors<br/>(index + .bx--body)"]
    A --> P[Prune unused Carbon styles with PostCSS]
    P --> R[Optimize CSS assets]
  end

  class T,G hook
  class S,A data
  class P,R css
Loading
$ vite build

Optimized index-CU4gbKFa.css
- Before: 606.26 kB
+ After:   53.22 kB (-91.22%)

dist/index.html                  0.34 kB │ gzip:  0.24 kB
dist/assets/index-CU4gbKFa.css  53.22 kB │ gzip:  6.91 kB
dist/assets/index-Ceijs3eO.js   53.65 kB │ gzip: 15.88 kB

Note

This is a plugin and not a Svelte preprocessor. It should be added to the list of vite.plugins. For Vite set-ups, this plugin is not run during development and is only executed when building the app (i.e., vite build). For Rollup and Webpack, you should conditionally apply the plugin to only execute when building for production.

SvelteKit

See examples/sveltekit.

// vite.config.js
import { sveltekit } from "@sveltejs/kit/vite";
import { optimizeCss } from "carbon-preprocess-svelte";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [sveltekit(), optimizeCss()],
});

Vite

See examples/vite.

// vite.config.js
import { svelte } from "@sveltejs/vite-plugin-svelte";
import { optimizeCss } from "carbon-preprocess-svelte";

/** @type {import('vite').UserConfig} */
export default {
  plugins: [svelte(), optimizeCss()],
};

Rollup

This code is abridged; see examples/rollup for a full set-up.

// rollup.config.js
import svelte from "rollup-plugin-svelte";
import { optimizeCss } from "carbon-preprocess-svelte";

const production = !process.env.ROLLUP_WATCH;

export default {
  plugins: [
    svelte({
      preprocess: [optimizeImports()],
    }),

    // Only apply the plugin when building for production.
    production && optimizeCss(),
  ],
};

optimizeCss API

optimizeCss({
  /**
   * Set to `true` to suppress the size difference
   * logging between original and optimized CSS.
   * @default false
   */
  silent: true,

  /**
   * By default, pre-compiled Carbon StyleSheets ship `@font-face` rules
   * for all available IBM Plex fonts, many of which are not actually
   * used in Carbon Svelte components.
   *
   * The default behavior is to preserve the following IBM Plex fonts:
   * - IBM Plex Sans (300/400/600-weight and normal-font-style rules)
   * - IBM Plex Mono (400-weight and normal-font-style rules)
   *
   * Set to `true` to disable this behavior and
   * retain *all* IBM Plex `@font-face` rules.
   * @default false
   */
  preserveAllIBMFonts: true,

  /**
   * Class selectors to always keep, regardless of which components are
   * imported. Use for Carbon classes the allowlist misses: hand-written
   * classes in app markup (e.g. `<div class="bx--grid">`) and theme/layout
   * utilities that no component file references.
   *
   * Each entry is either:
   * - a `string`, matched literally as a complete class token. `.bx--grid`
   *   keeps `.bx--grid` and `.bx--grid:hover`, but not `.bx--grid--wide`
   * - a `RegExp`, tested against the whole selector. `/^\.bx--btn--/` keeps
   *   every `.bx--btn--*` variant
   *
   * @default []
   */
  safelist: [".bx--grid", ".bx--aspect-ratio", /^\.bx--btn--/],

  /**
   * Glob patterns (relative to the working directory) of source files to scan
   * for literal `bx--`-prefixed tokens. Every token found is kept. Use when
   * class names are built at runtime. See the warning below.
   *
   * @default undefined
   */
  content: ["src/**/*.{svelte,js,ts}"],

  experimental: {
    /**
     * Experimental. Builds the component index from *this project's*
     * installed `carbon-components-svelte` instead of the version bundled
     * with `carbon-preprocess-svelte`. Useful when your app is ahead of (or
     * behind) the Carbon version this package was last released against —
     * new/renamed components and classes are picked up without waiting on a
     * `carbon-preprocess-svelte` release.
     *
     * Resolved once per build and cached on disk in your project at
     * `node_modules/.cache/carbon-preprocess-svelte/<carbon-version>_<preprocessor-version>.json`,
     * so bumping either package invalidates the cache automatically. Falls back to
     * the bundled static index if the live build fails for any reason
     * (unresolvable `carbon-components-svelte`, unexpected `src` layout,
     * etc.), so enabling it can't turn a working build into a broken one.
     *
     * `optimizeImports` accepts the same option, independently, since it
     * doesn't share a config object with `optimizeCss`.
     *
     * @default false
     */
    liveIndex: true,
  },
});

Warning

Dynamically constructed class names are not detected. The allowlist comes from Carbon component source files you import. It only knows about classes those components reference. Runtime assembly is invisible:

<!-- pruned: optimizer never sees literal `bx--btn--secondary` -->
<button class={`bx--btn--${kind}`}>...</button>

Hand-written Carbon classes in your markup (e.g. <div class="bx--grid">) have the same problem. No imported component references them, so they get pruned. If the plugin "deleted your styles," this is usually why.

Two ways to fix it:

  • safelist: list selectors (or a RegExp) to keep: safelist: [".bx--grid", /^\.bx--btn--/].
  • content: scan your source for literal bx-- prefixes: content: ["src/**/*.{svelte,js,ts}"].

OptimizeCssPlugin

For Webpack and Rspack users, OptimizeCssPlugin is a drop-in replacement for optimizeCss. The plugin API is identical to that of optimizeCss. Similarly, the plugin only runs in production mode. The same OptimizeCssPlugin instance works unchanged with both bundlers since Rspack implements webpack's plugin API.

This code is abridged; see examples/webpack, examples/webpack@svelte-5, or examples/rspack for a full set-up.

// webpack.config.mjs (or rspack.config.mjs)
import { OptimizeCssPlugin } from "carbon-preprocess-svelte";

export default {
  plugins: [new OptimizeCssPlugin()],
};

Examples

Refer to examples for common set-ups.

Contributing

Refer to the contributing guidelines.

License

Apache 2.0

About

Svelte preprocessors for the Carbon Design System

Topics

Resources

Code of conduct

Contributing

Stars

81 stars

Watchers

4 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages