Gathering detailed insights and metrics for esbuild-sass-plugin
Gathering detailed insights and metrics for esbuild-sass-plugin
Gathering detailed insights and metrics for esbuild-sass-plugin
Gathering detailed insights and metrics for esbuild-sass-plugin
npm install esbuild-sass-plugin
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
155 Stars
273 Commits
40 Forks
2 Watching
5 Branches
21 Contributors
Updated on 18 Oct 2024
JavaScript (53.55%)
CSS (42.46%)
TypeScript (3.39%)
SCSS (0.28%)
HTML (0.27%)
Shell (0.03%)
Sass (0.03%)
Cumulative downloads
Total Downloads
Last day
-18.6%
26,113
Compared to previous day
Last week
2.8%
158,736
Compared to previous week
Last month
-1.3%
681,058
Compared to previous month
Last year
108.4%
7,248,753
Compared to previous year
3
2
A plugin for esbuild to handle Sass & SCSS files.
dynamic style
to be added to the html pageembedded
to true
1$ npm i esbuild-sass-plugin
Just add it to your esbuild plugins:
1import {sassPlugin} from 'esbuild-sass-plugin' 2 3await esbuild.build({ 4 ... 5 plugins: [sassPlugin()] 6})
You can pass a series of options to the plugin that are a superset of Sass
compile string options.
The following are the options specific to the plugin with their defaults whether provided:
Option | Type | Default |
---|---|---|
filter | regular expression (in Go syntax) | /.(s[ac]ss|css)$/ |
type | "css" "style" "lit-css" "css-text" (css:string,nonce?:string)=>string | "css" |
cache | boolean or Map | true (there is one Map per namespace) |
transform | function | |
loadPaths | string[] | [] |
precompile | function | |
importMapper | function | |
cssImports | boolean | false |
nonce | string | |
prefer | string | preferred package.json field |
quietDeps | boolean | false |
embedded | boolean | false |
Two main options control the plugin: filter which has the same meaning of filter in esbuild | ||
allowing to select the URLs handled by a plugin instance and then type that's what specifies how the css should be rendered and imported. |
filter
The default filter is quite simple but also quite permissive. When specifying a custom regex bear in mind that this is in Go syntax
If you have URLs in your imports and you want the plugin to ignore them you can't just a filter expression like:
/^(?!https?:).*\.(s[ac]ss|css)$/
because Go regex engine doesn't support lookarounds but you can use esbuild'sexternal
option to ignore these imports or try a solution like this one.
You can try to list multiple plugin instances in order so that the most specific RegEx come first:
1await esbuild.build({ 2 ... 3 plugins: [ 4 sassPlugin({ 5 filter: /\.module\.scss$/, 6 transform: postcssModules() 7 }), 8 sassPlugin({ 9 filter: /\.scss$/ 10 }), 11 ], 12 ... 13})
embedded
This option enables the usage of the faster sass-embedded
and is false by default just for compatibility reason.
Make sure that the
sass-embedded
has been installed as a peer dependency or add it manually to your project if your package manager doesn't do that for you then set this option totrue
and enjoy the speed boost!
type
The example in Usage uses the default type css
and will use esbuild CSS loader so your transpiled Sass
will be in index.css
alongside your bundle.
In all other cases esbuild
won't process the CSS content which instead will be handled by the plugin.
if you want
url()
resolution or other processing you have to usepostcss
like in this example
NOTE: Since version 2.7.0
the css
type works also with postcss, CSS modules and more in general
with any transformation function by keeping an internal cache of CSS chunks (virtual CSS files)
importing them in the module wrapping the contents
type: "local-css"
This mode uses esbuild's built-in CSS modules support (i.e. the local-css
loader).
Use this for lightweight Sass integration that then leverages esbuild's built-in CSS processing features:
1await esbuild.build({ 2 ... 3 plugins: [ 4 sassPlugin({ 5 filter: /\.module\.scss$/, 6 type: 'local-css' 7 }), 8 sassPlugin({ 9 filter: /\.scss$/ 10 type: 'css' 11 }), 12 ], 13 ... 14})
type: "style"
In this mode the stylesheet will be in the javascript bundle and will be dynamically added to the page when the bundle is loaded.
type: "css-text"
You can use this mode if you want to use the resulting css text as a string import
1await esbuild.build({ 2 ... 3 plugins: [sassPlugin({ 4 type: "css-text", 5 ... // for the options availanle look at 'SassPluginOptions' in index.ts 6 })] 7})
...and in your module do something like
1import cssText from './styles.scss' 2 3customElements.define('hello-world', class HelloWorld extends HTMLElement { 4 constructor() { 5 super(); 6 this.attachShadow({mode: 'open'}); 7 this.sheet = new CSSStyleSheet(); 8 this.sheet.replaceSync(cssText); 9 this.shadowRoot.adoptedStyleSheets = [this.sheet]; 10 } 11}
type: "lit-css"
Or you can import a lit-element css result using type: "lit-css"
1import styles from './styles.scss' 2 3@customElement("hello-world") 4export default class HelloWorld extends LitElement { 5 6 static styles = styles 7 8 render() { 9 ... 10 } 11}
type: 'function'
You can now provide your own module factory as type. It has to be a function that receives 2 parameters the css text and the nonce token and returns the source content to be added in place of the import.
Look in test/fixtures
folder for more usage examples.
cache
The cache is enabled by default and can be turned off with cache: false
.
Each plugin instance creates and maintain its own cache (as a Map) and this cache lives for the duration of the build.
If you want to pass a Map to preserve the cache amongst subsequent builds bear in mind that sharing the very same cache
between different instances might work just fine or it might lead to issues if the contents are incompatible.
If you are not sure of what to do just keep a separate Map for each plugin instance.
cssImports
when this is set to true
the plugin rewrites the node-modules relative URLs starting with the ~
prefix so that
esbuild can resolve them similarly to what css-loader
does.
Although this practice is kind of deprecated nowadays some packages out there still use this notation (e.g.
formio
)
so I added this feature to help in cases like this one.
nonce
in presence of Content-Security-Policy
(CSP)
the nonce
option allows to specify the nonce attribute for the dynamically generated <style>
If the nonce
string is a field access starting with window
, process
or globalThis
it is left in the code without quotes.
1sassPlugin({ 2 type: 'style', 3 nonce: 'window.__esbuild_nonce__' 4})
This allows to define it globally or to leave it for a subsequent build to resolve it using esbuild define.
1define: {'window.__esbuild_nonce__': '"12345"'}
prefer
when this option is specified it allows to import npm packages which have sass
or style
fields preferring those to main
.
NOTE: This is an experimental feature
- it replaces the internal use of
require.resolve
with browserifyresolve.sync
- it only applies to import prefixed by
~
importMapper
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
1{ 2 "compilerOptions": { 3 "baseUrl": ".", 4 "paths": { 5 "@img/*": [ 6 "./assets/images/*" 7 ] 8 } 9 } 10}
now you can resolve these paths with importMapper
1await esbuild.build({ 2 ..., 3 plugins: [sassPlugin({ 4 importMapper: (path) => path.replace(/^@img\//, './assets/images/') 5 })] 6})
precompile
url(...)
sIf 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 (make sure to handle \
in Windows)
1const path = require('path') 2 3await esbuild.build({ 4 ..., 5 plugins: [sassPlugin({ 6 precompile(source, pathname) { 7 const basedir = path.dirname(pathname) 8 return source.replace(/(url\(['"]?)(\.\.?\/)([^'")]+['"]?\))/g, `$1${basedir}/$2$3`) 9 } 10 })] 11})
Look for a complete example in the precompile fixture.
Prepending a variable for a specific pathname
:
1const context = { color: "blue" } 2 3await esbuild.build({ 4 ..., 5 plugins: [sassPlugin({ 6 precompile(source, pathname) { 7 const prefix = /\/included\.scss$/.test(pathname) ? ` 8 $color: ${context.color}; 9 ` : env 10 return prefix + source 11 } 12 })] 13})
Prepending an @import
of globals file only for the root file that triggered the compilation (to avoid nested files from importing it again):
1const context = { color: "blue" } 2 3await esbuild.build({ 4 ..., 5 plugins: [sassPlugin({ 6 precompile(source, pathname, isRoot) { 7 return isRoot ? `@import '/path/to/globals.scss';\n${source}` : source 8 } 9 })] 10})
transform
1async (this: SassPluginOptions, css: string, resolveDir?: string) => Promise<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.
NOTE: Since
v1.5.0
transform can return either a string or an esbuildLoadResult
object.
This is whatpostcssModules
uses to pass Javascript modules to esbuild bypassing the plugin output altogether.
The simplest use case is to invoke PostCSS like this:
1const postcss = require('postcss') 2const autoprefixer = require('autoprefixer') 3const postcssPresetEnv = require('postcss-preset-env') 4 5esbuild.build({ 6 ..., 7 plugins: [sassPlugin({ 8 async transform(source, resolveDir) { 9 const {css} = await postcss([autoprefixer, postcssPresetEnv({stage: 0})]).process(source) 10 return css 11 } 12 })] 13}) 14
A helper function is available to do all the work of calling PostCSS to create a CSS module. The usage is something like:
1const {sassPlugin, postcssModules} = require('esbuild-sass-plugin') 2 3esbuild.build({ 4 ..., 5 plugins: [sassPlugin({ 6 transform: postcssModules({ 7 // ...put here the options for postcss-modules: https://github.com/madyankin/postcss-modules 8 }) 9 })] 10}) 11
postcssModules
produces Javascript modules which are handled by esbuild's js
loader
postcssModules
also accepts an optional array of plugins for PostCSS as second parameter.
Look into fixture/css-modules for the complete example.
NOTE:
postcss
andpostcss-modules
have to be added to yourpackage.json
.
In order for quietDeps
to correctly identify external dependencies the url
option is defaulted to the importing file path URL.
The
url
option creates problems when importing source SASS files from 3rd party modules in which case the best workaround is to avoidquietDeps
and mute the logger if that's a big issue.
Type: boolean
function
Default: false
Use named exports alongside default export.
You can supply a function to control how exported named is generated:
1namedExports(name) { 2 // Maybe you simply want to convert dash to underscore 3 return name.replace(/-/g, '_') 4}
If you set it to true
, the following will happen when importing specific classNames:
$
sign wrapped underlines, eg. --
=> $__$
$
signs, eg. switch
=> $switch$
All transformed names will be logged in your terminal like:
1Exported "new" as "$new$" in test/fixtures/named-exports/style.css
The original will not be removed, it's still available on default
export:
1import style, { class$_$name, class$__$name, $switch$ } from './style.css' 2console.log(style['class-name'] === class$_$name) // true 3console.log(style['class--name'] === class$__$name) // true 4console.log(style['switch'] === $switch$) // true
There's a working example of using pnpm
with @material
design
in issue/38
Windows 11 Pro - i7-490K CPU @ 4.00GHz - RAM 32GB - SSD 500GB
Given 24 × 24 = 576 lit-element files & 576 imported CSS styles plus the import of the full bootstrap 5.1
sass-embedded | sass-embedded (no cache) | dart sass | dart sass (no cache) | |
---|---|---|---|---|
initial build | 731.312ms | 779.363ms | 2.450s | 2.450s |
rebuild (.ts change) | 170.35ms | 188.861ms | 179.125ms | 1.710s |
rebuild (.ts change) | 155.802ms | 167.413ms | 176.849ms | 1.576s |
rebuild (.scss change) | 203.746ms | 160.601ms | 188.164ms | 1.575s |
rebuild (.scss change) | 152.733ms | 144.754ms | 145.835ms | 1.520s |
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
packaging workflow detected
Details
Reason
Found 6/21 approved changesets -- score normalized to 2
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
34 existing vulnerabilities detected
Details
Score
Last Scanned on 2024-11-25
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More