What is esbuild-sass-plugin?
The esbuild-sass-plugin is an npm package that integrates Sass/SCSS compilation into the esbuild bundler. It allows developers to seamlessly compile Sass/SCSS files into CSS during the build process, leveraging the speed and efficiency of esbuild.
What are esbuild-sass-plugin's main functionalities?
Basic Sass/SCSS Compilation
This feature allows you to compile Sass/SCSS files into CSS as part of the esbuild process. The code sample demonstrates how to set up esbuild with the esbuild-sass-plugin to bundle JavaScript and compile Sass/SCSS files.
const esbuild = require('esbuild');
const sassPlugin = require('esbuild-sass-plugin').sassPlugin;
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/bundle.js',
plugins: [sassPlugin()]
}).catch(() => process.exit(1));
Custom Sass Options
This feature allows you to pass custom options to the Sass compiler. The code sample shows how to configure the plugin to include additional paths and enable indented syntax for Sass files.
const esbuild = require('esbuild');
const sassPlugin = require('esbuild-sass-plugin').sassPlugin;
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/bundle.js',
plugins: [sassPlugin({
includePaths: ['./src/styles'],
indentedSyntax: true
})]
}).catch(() => process.exit(1));
Source Maps
This feature enables source map generation for easier debugging of Sass/SCSS files. The code sample demonstrates how to configure esbuild to generate source maps along with the compiled CSS.
const esbuild = require('esbuild');
const sassPlugin = require('esbuild-sass-plugin').sassPlugin;
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/bundle.js',
sourcemap: true,
plugins: [sassPlugin()]
}).catch(() => process.exit(1));
Other packages similar to esbuild-sass-plugin
sass
The 'sass' package is a standalone Sass compiler that can be used with various build tools. Unlike esbuild-sass-plugin, it does not integrate directly with esbuild but can be used in conjunction with other tools to achieve similar results.
node-sass
The 'node-sass' package is another popular Sass compiler that uses the LibSass library. It is similar to the 'sass' package but is often used in older projects. It does not integrate directly with esbuild but can be used with other build tools.
sass-loader
The 'sass-loader' package is a loader for webpack that compiles Sass/SCSS files. It is similar in functionality to esbuild-sass-plugin but is designed specifically for webpack rather than esbuild.
A plugin for esbuild to handle Sass & SCSS files.
Features
- PostCSS & CSS modules
- support for constructable stylesheet to be used in custom elements or
dynamic style
to be added to the html page - uses the new Dart Sass Js API.
- caching
- url rewriting
- pre-compiling (to add global resources to the sass files)
Breaking Changes
type
has been simplified and now accepts only a string. If you need different types in a project you can use more
than one instance of the plugin. You can have a look at the exclude fixture for an example_ where lit CSS
and CSS modules are both used in the same app- The support for node-sass has been removed and for good.
Sadly, node-sass is at a dead end and so it's 1.x. I don't exclude updates or fixes on it but it's down in the list of
my priorities.
Install
$ npm i esbuild-sass-plugin
Usage
Just add it to your esbuild plugins:
import {sassPlugin} from 'esbuild-sass-plugin'
await esbuild.build({
...
plugins: [sassPlugin()]
})
this will use esbuild loader: "css"
and your transpiled Sass will be in index.css
alongside your bundle.
There are two main options that control the plugin: filter
which has the same meaning of filter in esbuild
onLoad and type
that's what specifies how the css should be
rendered and imported.
If you specify type: "style"
then the stylesheet will be in the bundle
and will be dynamically added to the page when the bundle is loaded.
If you want to use the resulting css text as a string import you can use type: "css-text"
await esbuild.build({
...
plugins: [sassPlugin({
type: "css-text",
...
})]
})
...and in your module do something like
import cssText from './styles.scss'
customElements.define('hello-world', class HelloWorld extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
this.sheet = new CSSStyleSheet();
this.sheet.replaceSync(cssText);
this.shadowRoot.adoptedStyleSheets = [this.sheet];
}
}
Or you can import a lit-element css result using type: "lit-css"
import styles from './styles.scss'
@customElement("hello-world")
export default class HelloWorld extends LitElement {
static styles = styles
render() {
...
}
}
Look in test/fixtures
folder for more usage examples.
Options
The options passed to the plugin are a superset of Sass
compile string options.
Option | Type | Default |
---|
filter | regular expression | /.(s[ac]ss|css)$/ |
cache | boolean or Map | true (there is one Map per namespace) |
type | "css"
"style"
"lit-css" | "css" |
transform | function | undefined |
loadPaths | string[] | [] |
importer | function | built in importer |
precompile | function | undefined |
importMapper | function | undefined |
pnpm
There's a working example of using pnpm
with @material
design
in issue/38
ImportMapper Option
A function to customize/re-map the import path, both import
statements in JavaScript/TypeScript code and @import
in Sass/SCSS are covered.
You can use this option to re-map import paths like tsconfig's paths
option.
e.g. given this tsconfig.json
which maps image files paths
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@img/*": [
"./assets/images/*"
]
}
}
}
now you can resolve these paths with importMapper
await esbuild.build({
...,
plugins: [sassPlugin({
importMapper: (path) => path.replace(/^@img\//, './assets/images/')
})]
})
Precompile
url(...)
rewrite
If your sass reference resources with relative urls (see #48)
esbuild will struggle to rewrite those urls because it doesn't have idea of the imports that the Sass compiler
has gone through. Fortunately the new importer API allows to rewrite those relative URLs in absolute ones which
then esbuild will be able to handle.
Here is an example of how to do the url(...)
rewrite
await esbuild.build({
...,
plugins: [sassPlugin({
precompile(source, pathname) {
const basedir = path.dirname(pathname)
return source.replace(/(url\(['"]?)(\.\.?\/)([^'")]+['"]?\))/g, `$1${basedir}/$2$3`)
}
})]
})
Globals and other Shims (like sass-loader's additionalData)
Look for a complete example in the precompile fixture
const context = { color: "blue" }
await esbuild.build({
...,
plugins: [sassPlugin({
precompile(source, pathname) {
const prefix = /\/included\.scss$/.test(pathname) ? `
$color: ${context.color};
` : env
return prefix + source
}
})]
})
Transform
async (css: string, resolveDir?: string) => string
It's a function which will be invoked before passing the css to esbuild or wrapping it in a module.
It can be used to do PostCSS processing and/or to create modules like in the following examples.
PostCSS
The simplest use case is to invoke PostCSS like this:
const postcss = require('postcss')
const autoprefixer = require('autoprefixer')
const postcssPresetEnv = require('postcss-preset-env')
esbuild.build({
...,
plugins: [sassPlugin({
async transform(source, resolveDir) {
const {css} = await postcss([autoprefixer, postcssPresetEnv({stage: 0})]).process(source)
return css
}
})]
})
CSS Modules
A helper function is available to do all the work of calling PostCSS to create a CSS module. The usage is something
like:
const {sassPlugin, postcssModules} = require('esbuild-sass-plugin')
esbuild.build({
...,
plugins: [sassPlugin({
transform: postcssModules({
})
})]
})
NOTE: postcss
and postcss-modules
have to be added to your package.json
.
postcssModules
also accepts an optional array of plugins for PostCSS as second parameter.
Look into fixture/css-modules for
the complete example.
NOTE: Since v1.5.0
transform can return either a string or an esbuild LoadResult
object.
This gives the flexibility to implement that helper function.
Benchmarks
Given 24 × 24 = 576 lit-element files & 576 imported CSS styles plus the import of the full bootstrap 5.1
| dart sass | dart sass (no cache) | node-sass* | node-sass (no cache) |
---|
initial build | 2.946s | 2.945s | 1.903s | 1.858s |
rebuild (.ts change) | 285.959ms | 1.950s | 797.098ms | 1.689s |
rebuild (.ts change) | 260.791ms | 1.799s | 768.213ms | 1.790s |
rebuild (.scss change) | 234.152ms | 1.801s | 770.619ms | 1.652s |
rebuild (.scss change) | 267.857ms | 1.738s | 750.743ms | 1.682s |
(*) node-sass is here just to give a term of comparison ...those samples were taken from 1.8.x