esbuild-sass-plugin

An esbuild plugin that compiles Sass and SCSS to CSS with caching, source maps, and CSS Modules support.

Library
npm
v3.7.0
166stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
48/100Fair
Development Activity4
Maintenance44
Community64
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
72/100Good
Architecture78
Code Quality80
Innovation75
Learning Curve55

esbuild-sass-plugin plugs Sass and SCSS compilation directly into an esbuild build pipeline, so .scss/.sass imports are resolved, compiled, and cached without a separate build step. It wraps both the pure-JS sass compiler and the faster sass-embedded binary behind one interface, and hooks into esbuild’s onResolve/onLoad API to intercept stylesheet imports, resolve ~-prefixed node_modules paths, and rewrite the resulting CSS as whichever output shape the consumer needs.

Beyond plain CSS output, the plugin supports PostCSS transforms and CSS Modules (via a postcssModules helper with named-export generation), constructable stylesheets for custom elements, Lit-element css results, and inline <style> injection for direct browser use. A per-instance, mtime-aware cache avoids recompiling unchanged files across incremental builds, and a precompile hook lets consumers inject shared variables or partials before Sass ever sees the source.

What You Get

  • Sass/SCSS compilation wired into esbuild’s onResolve/onLoad plugin hooks, with automatic .scss/.css/.sass/index fallback resolution
  • A choice of compiler backend — the pure-JS sass package by default, or the faster sass-embedded binary via the embedded option
  • Multiple output modes (css, local-css, style, css-text, lit-css, or a custom module-factory function) for CSS loader, CSS Modules, inline style injection, string import, or Lit css results
  • PostCSS and CSS Modules support through a bundled postcssModules helper that also generates named exports for each class
  • An mtime-based per-instance cache (or a caller-supplied Map) so unchanged files skip recompilation across incremental/watch builds
  • A precompile hook and importMapper option for injecting shared Sass variables/partials and remapping import paths (e.g. to mirror tsconfig path aliases)

Common Use Cases

  • Adding Sass/SCSS support to an esbuild-based frontend build without switching to Webpack or Vite
  • Bundling Lit-element or Web Component libraries that need css-tagged stylesheets or constructable stylesheets produced from SCSS source
  • Enabling CSS Modules with class-name-safe named exports in an esbuild pipeline via the built-in postcssModules transform
  • Speeding up large Sass codebases in watch mode using the plugin’s file-cache plus the sass-embedded compiler backend
  • Injecting global Sass variables or environment-specific partials into every stylesheet via the precompile hook, replacing sass-loader’s additionalData pattern

Under The Hood

Architecture The plugin is organized into small, single-responsibility modules rather than one monolithic file: plugin.ts wires the public sassPlugin(options) factory into esbuild’s onResolve/onLoad/onDispose hooks and orchestrates output-mode branching (css/local-css/style/lit-css/custom transform); render.ts isolates Sass compilation behind a Compiler abstraction that can point at either the synchronous sass package or the async sass-embedded binary without the rest of the plugin caring which; cache.ts implements an mtime-aware memoization layer that wraps any onLoad callback; and utils.ts holds path resolution, module-wrapping, and identifier-safety helpers. The index.ts entry point is a thin re-export surface, keeping the public API to one function and a handful of types. This separation means the compiler backend, caching strategy, and esbuild integration can each change independently — swapping sass for sass-embedded, for instance, only touches render.ts.

Tech Stack Written in TypeScript (strict mode, compiled via tsc to CommonJS lib/) against the esbuild plugin API as a peer dependency (>=0.27.3), with sass as a direct dependency and sass-embedded as an optional peer for the faster compiler path. PostCSS and postcss-modules are lazily require’d only when a consumer opts into CSS Modules, keeping them out of the default dependency graph. Module resolution reuses the resolve package to honor package.json exports/style/sass fields when resolving ~-prefixed imports. Tests run under Jest with ts-jest, and two GitHub Actions workflows (test.yml, publish.yml) cover CI and npm publishing.

Code Quality The project has an extensive, scenario-driven test suite rather than isolated unit tests: unit.test.ts and e2e.test.ts build real esbuild bundles against dozens of fixture projects (CSS Modules, Lit-element, named exports, PnP resolution, React, watch mode, warnings, precompile, benchmark), and bugfixes.test.ts encodes regressions for specific historical issues, comparing output against committed snapshots. TypeScript strict mode is enabled project-wide, error paths return typed esbuild errors/warnings objects rather than throwing past the plugin boundary, and CI runs the full suite on every change. No dedicated linter/formatter config is present, but the fixture-and-snapshot testing style gives strong confidence for a plugin whose correctness is inherently about matching esbuild’s expected output shapes.

API Design The entire public surface is one factory function, sassPlugin(options), returning a standard esbuild Plugin — there is no separate registration step, config file, or CLI to learn. Sensible defaults (type: 'css', a permissive default filter regex) mean the common case is just plugins: [sassPlugin()], while advanced behavior (CSS Modules, custom output types, caching, source rewriting) is opt-in through additional fields on the same options object, avoiding API sprawl as features accumulated across the plugin’s five major versions. Deprecated options (includePaths, the old type array/exclude/picomatch flags) still log an actionable console message pointing at their replacement rather than silently changing behavior.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search