{"id":13670558,"url":"https://github.com/glromeo/esbuild-sass-plugin","last_synced_at":"2026-03-09T04:08:35.378Z","repository":{"id":43284672,"uuid":"334933943","full_name":"glromeo/esbuild-sass-plugin","owner":"glromeo","description":"esbuild plugin for sass","archived":false,"fork":false,"pushed_at":"2025-01-29T23:05:54.000Z","size":18846,"stargazers_count":160,"open_issues_count":12,"forks_count":42,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-15T03:05:13.788Z","etag":null,"topics":["css","css-modules","esbuild","esbuild-plugins","lit-element","node-sass","postcss","sass"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/glromeo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-01T11:54:50.000Z","updated_at":"2025-05-12T04:50:51.000Z","dependencies_parsed_at":"2023-12-19T01:45:43.261Z","dependency_job_id":"2d5b67b0-ee70-4195-8125-55795a497760","html_url":"https://github.com/glromeo/esbuild-sass-plugin","commit_stats":{"total_commits":235,"total_committers":24,"mean_commits":9.791666666666666,"dds":"0.25106382978723407","last_synced_commit":"f0e58c9a4a24e249c6da3bcf7e8c769b74eb7ac4"},"previous_names":[],"tags_count":56,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glromeo%2Fesbuild-sass-plugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glromeo%2Fesbuild-sass-plugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glromeo%2Fesbuild-sass-plugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glromeo%2Fesbuild-sass-plugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/glromeo","download_url":"https://codeload.github.com/glromeo/esbuild-sass-plugin/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254264765,"owners_count":22041793,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","css-modules","esbuild","esbuild-plugins","lit-element","node-sass","postcss","sass"],"created_at":"2024-08-02T09:00:45.410Z","updated_at":"2026-03-09T04:08:30.349Z","avatar_url":"https://github.com/glromeo.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"![cooltext394785080075403](https://user-images.githubusercontent.com/160981/136289874-26ce7269-ea08-47dd-be31-9bf0ef7a0b8d.png)\n![image](https://github.com/glromeo/esbuild-sass-plugin/assets/160981/6a686a7c-ddd0-499f-b98d-03e607aac0a7)\n\n[![Build Status][travis-image]][travis-url]\n\nA plugin for [esbuild](https://esbuild.github.io/) to handle Sass \u0026 SCSS files.\n\n### Features\n* **PostCSS** \u0026 **CSS modules**\n* support for **constructable stylesheet** to be used in custom elements or `dynamic style` to be added to the html page\n* Support for **[Sass Embedded](https://github.com/sass/sass/issues/3296) Async API**. (thanks to @NathanBeddoeWebDev)\n* caching\n* **url rewriting**\n* pre-compiling (to add **global resources** to the sass files)\n\n### Breaking Changes (...maybe)\n* It turned out that sass-embedded is not available on every platform (this sucks!) so, in order to improve the compatibility of the\nplugin I had to make it a peer dependency. Once installed, it can be used by setting the new option `embedded` to `true`\n\n### Install\n\n```console\n$ npm i esbuild-sass-plugin\n```\n\n### Usage\n\nJust add it to your esbuild plugins:\n\n```javascript\nimport {sassPlugin} from 'esbuild-sass-plugin'\n\nawait esbuild.build({\n  ...\n  plugins: [sassPlugin()]\n})\n```\n\n### Options\n\nYou can pass a series of **options** to the plugin that are a superset of Sass\n[compile string options](https://sass-lang.com/documentation/js-api/interfaces/StringOptionsWithImporter). \\\nThe following are the options specific to the plugin with their defaults whether provided:\n\n| Option              | Type                                                                                              | Default                                  |\n|---------------------|---------------------------------------------------------------------------------------------------|------------------------------------------|\n| filter              | regular expression (in Go syntax)                                                                 | \u003ccode\u003e/\\.(s[ac]ss\u0026vert;css)$/\u003c/code\u003e     |\n| type                | `\"css\"`\u003cbr/\u003e`\"style\"`\u003cbr/\u003e`\"lit-css\"`\u003cbr/\u003e`\"css-text\"` \u003cbr/\u003e `(css:string,nonce?:string)=\u003estring` | `\"css\"`                                  |\n| cache               | boolean or Map                                                                                    | `true` (there is one Map per namespace)  |\n| transform           | function                                                                                          |                                          |\n| loadPaths           | [string[]](https://sass-lang.com/documentation/js-api/interfaces/Options#loadPaths)               | []                                       |\n| precompile          | function                                                                                          |                                          |\n| importMapper        | function                                                                                          |                                          |\n| cssImports          | boolean                                                                                           | false                                    |\n| nonce               | string                                                                                            |                                          |\n| prefer              | string                                                                                            | preferred package.json field             |\n| quietDeps           | boolean                                                                                           | false                                    |\n| silenceDeprecations | [string[]](https://sass-lang.com/documentation/js-api/interfaces/deprecations/)                   | []                                       |\n| embedded            | boolean                                                                                           | false                                    | \nTwo main options control the plugin: `filter` which has the same meaning of filter in [esbuild](https://esbuild.github.io/plugins/#on-load) \nallowing to select the URLs handled by a plugin instance and then `type` that's what specifies how the css should be rendered and imported. \n\n### `filter`\nThe default filter is quite simple but also quite permissive. When specifying a custom regex bear in mind that this\nis in [Go syntax](https://pkg.go.dev/regexp/syntax)\n\n\u003e If you have URLs in your imports and you want the plugin to ignore them you can't just a filter expression like:\n`/^(?!https?:).*\\.(s[ac]ss|css)$/` because *Go regex engine doesn't support lookarounds* but you can use \n\u003e **esbuild**'s `external` option to ignore these imports or try a [solution like this one](https://esbuild.github.io/plugins/#on-resolve).\n\nYou can try to list multiple plugin instances in order so that the most specific RegEx come first: \n```javascript\nawait esbuild.build({\n  ...\n  plugins: [\n    sassPlugin({\n      filter: /\\.module\\.scss$/,\n      transform: postcssModules()\n    }),\n    sassPlugin({\n      filter: /\\.scss$/\n    }),\n  ],\n  ...   \n})\n```\n\n### `embedded`\n\nThis option enables the usage of the faster `sass-embedded` and is **false** by default just **for compatibility reason**.\n\n\u003e Make sure that the `sass-embedded` has been installed as a peer dependency \n\u003e or add it manually to your project if your package manager doesn't do that for you \n\u003e then set this option to `true` and enjoy the speed boost!\n\n### `type`\n\nThe example in [Usage](#usage) uses the default type `css` and will use esbuild CSS loader so your transpiled Sass \nwill be in `index.css` alongside your bundle.\n\nIn all other cases `esbuild` won't process the CSS content which instead will be handled by the plugin.\n\u003e if you want `url()` resolution or other processing you have to use `postcss` like in [this example](https://github.com/glromeo/esbuild-sass-plugin/issues/92#issuecomment-1219209442) \n\n**NOTE:** Since version `2.7.0` the `css` type works also with postcss, CSS modules and more in general \nwith any transformation function by keeping an internal cache of CSS chunks (virtual CSS files) \nimporting them in the module wrapping the contents\n\n#### `type: \"local-css\"`\nThis mode uses esbuild's built-in CSS modules support (i.e. the [`local-css` loader](https://esbuild.github.io/content-types/#local-css)).\nUse this for lightweight Sass integration that then leverages esbuild's [built-in CSS processing features](https://esbuild.github.io/content-types/#css):\n\n```javascript\nawait esbuild.build({\n  ...\n  plugins: [\n    sassPlugin({\n      filter: /\\.module\\.scss$/,\n      type: 'local-css'\n    }),\n    sassPlugin({\n      filter: /\\.scss$/\n      type: 'css'\n    }),\n  ],\n  ...   \n})\n```\n\n#### `type: \"style\"`\nIn this mode the stylesheet will be in the javascript bundle \nand will be dynamically added to the page when the bundle is loaded.\n\n#### `type: \"css-text\"`\nYou can use this mode if you want to use the resulting css text as a string import\n\n```javascript\nawait esbuild.build({\n  ...\n  plugins: [sassPlugin({\n    type: \"css-text\",\n    ...   // for the options availanle look at 'SassPluginOptions' in index.ts\n  })]\n})\n```\n\n...and in your module do something like\n\n```javascript\nimport cssText from './styles.scss'\n\ncustomElements.define('hello-world', class HelloWorld extends HTMLElement {\n  constructor() {\n    super();\n    this.attachShadow({mode: 'open'});\n    this.sheet = new CSSStyleSheet();\n    this.sheet.replaceSync(cssText);\n    this.shadowRoot.adoptedStyleSheets = [this.sheet];\n  }\n}\n```\n\n#### `type: \"lit-css\"`\nOr you can import a **lit-element** css result using `type: \"lit-css\"`\n\n```javascript\nimport styles from './styles.scss'\n\n@customElement(\"hello-world\")\nexport default class HelloWorld extends LitElement {\n\n  static styles = styles\n\n  render() {\n    ...\n  }\n}\n```\n\n#### `type: 'function'`\n\nYou can now provide your own module factory as type. It has to be a function that receives 2 parameters\nthe css text and the nonce token and returns the source content to be added in place of the import.\n\nLook in `test/fixtures` folder for more usage examples.\n\n### `cache`\nThe cache is enabled by default and can be turned off with `cache: false`. \nEach plugin instance creates and maintain its own cache (as a Map) and this cache lives for the duration of the build. \nIf you want to pass a Map to preserve the cache amongst subsequent builds bear in mind that sharing the very same cache \nbetween different instances might work just fine or it might lead to issues if the contents are incompatible. \n\u003e If you are not sure of what to do just keep a separate Map for each plugin instance.\n\n### `cssImports`\nwhen this is set to `true` the plugin rewrites the node-modules relative URLs starting with the `~` prefix so that\nesbuild can resolve them similarly to what `css-loader` does. \n\u003e Although this practice is [kind of deprecated nowadays](https://webpack.js.org/loaders/sass-loader/#resolving-import-at-rules) \n\u003e some packages out there still use this notation (e.g. `formio`)\n\u003e \\\n\u003e so I added this feature to help in cases [like this one](https://github.com/glromeo/esbuild-sass-plugin/issues/74).\n\n### `nonce`\nin presence of Content-Security-Policy \n[(CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src) \nthe `nonce` option allows to specify the nonce attribute for the dynamically generated `\u003cstyle\u003e`\n\nIf the `nonce` string is a field access starting with `window`, `process` or `globalThis` it is left in the code without quotes.\n```javascript\nsassPlugin({\n  type: 'style',\n  nonce: 'window.__esbuild_nonce__'\n})\n```\nThis allows to define it globally or to leave it for a subsequent build to resolve it using [esbuild define](https://esbuild.github.io/api/#define).\n```javascript\ndefine: {'window.__esbuild_nonce__': '\"12345\"'}\n```\n\n### `prefer`\nwhen this option is specified it allows to import npm packages which have `sass` or `style` fields preferring those to `main`.\n\n\u003e **NOTE**: This is an experimental feature\n\u003e * it replaces the internal use of `require.resolve` with browserify `resolve.sync`\n\u003e * it only applies to import prefixed by `~` \n\n### `importMapper`\n\nA function to customize/re-map the import path, both `import` statements in JavaScript/TypeScript code and `@import`\nin Sass/SCSS are covered.   \nYou can use this option to re-map import paths like tsconfig's `paths` option.\n\ne.g. given this `tsconfig.json` which maps image files paths\n\n```json\n{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@img/*\": [\n        \"./assets/images/*\"\n      ]\n    }\n  }\n}\n```\n\nnow you can resolve these paths with `importMapper`\n\n```javascript\nawait esbuild.build({\n  ...,\n  plugins: [sassPlugin({\n    importMapper: (path) =\u003e path.replace(/^@img\\//, './assets/images/')\n  })]\n})\n```\n\n### `precompile`\n\n#### - Rewriting relative `url(...)`s\nIf your sass reference resources with relative urls (see [#48](https://github.com/glromeo/esbuild-sass-plugin/issues/48))\nesbuild will struggle to rewrite those urls because it doesn't have idea of the imports that the Sass compiler \nhas gone through. Fortunately the new importer API allows to rewrite those relative URLs in absolute ones which \nthen esbuild will be able to handle.\n\nHere is an example of how to do the `url(...)` rewrite ([make sure to handle `\\` in *Windows*](https://github.com/glromeo/esbuild-sass-plugin/issues/58))\n```javascript\nconst path = require('path')\n\nawait esbuild.build({\n  ...,\n  plugins: [sassPlugin({\n    precompile(source, pathname) {\n      const basedir = path.dirname(pathname)\n      return source.replace(/(url\\(['\"]?)(\\.\\.?\\/)([^'\")]+['\"]?\\))/g, `$1${basedir}/$2$3`)\n    }\n  })]\n})\n```\n\n#### - Globals and other Shims (like sass-loader's additionalData)\nLook for a complete example in the [precompile](https://github.com/glromeo/esbuild-sass-plugin/tree/main/test/fixture/precompile) fixture.\n\nPrepending a variable for a specific `pathname`:\n```javascript\nconst context = { color: \"blue\" }\n\nawait esbuild.build({\n  ...,\n  plugins: [sassPlugin({\n    precompile(source, pathname) {\n      const prefix = /\\/included\\.scss$/.test(pathname) ? `\n            $color: ${context.color};\n          ` : env\n      return prefix + source\n    }\n  })]\n})\n```\n\nPrepending an `@import` of globals file only for the root file that triggered the compilation (to avoid nested files from importing it again):\n```javascript\nconst context = { color: \"blue\" }\n\nawait esbuild.build({\n  ...,\n  plugins: [sassPlugin({\n    precompile(source, pathname, isRoot) {\n      return isRoot ? `@import '/path/to/globals.scss';\\n${source}` : source\n    }\n  })]\n})\n```\n\n### `transform`\n\n```typescript\nasync (this: SassPluginOptions, css: string, resolveDir?: string) =\u003e Promise\u003cstring\u003e\n``` \n\nIt's a function which will be invoked before passing the css to esbuild or wrapping it in a module.\\\nIt can be used to do **PostCSS** processing and/or to create **modules** like in the following examples.\n\n\u003e **NOTE:** Since `v1.5.0` transform can return either a string or an esbuild `LoadResult` object. \\\n\u003e This is what `postcssModules` uses to pass Javascript modules to esbuild bypassing the plugin output altogether.\n\n#### - PostCSS\n\nThe simplest use case is to invoke PostCSS like this:\n\n```javascript\nconst postcss = require('postcss')\nconst autoprefixer = require('autoprefixer')\nconst postcssPresetEnv = require('postcss-preset-env')\n\nesbuild.build({\n  ...,\n  plugins: [sassPlugin({\n    async transform(source, resolveDir) {\n      const {css} = await postcss([autoprefixer, postcssPresetEnv({stage: 0})]).process(source)\n      return css\n    }\n  })]\n})\n\n```\n\n#### - CSS Modules\n\nA helper function is available to do all the work of calling PostCSS to create a CSS module. The usage is something\nlike:\n\n```javascript\nconst {sassPlugin, postcssModules} = require('esbuild-sass-plugin')\n\nesbuild.build({\n  ...,\n  plugins: [sassPlugin({\n    transform: postcssModules({\n      // ...put here the options for postcss-modules: https://github.com/madyankin/postcss-modules\n    })\n  })]\n})\n\n```\n`postcssModules` produces Javascript modules which are handled by esbuild's `js` loader\n\n`postcssModules` also accepts an optional array of plugins for PostCSS as second parameter.\n\nLook into [fixture/css-modules](https://github.com/glromeo/esbuild-sass-plugin/tree/main/test/fixture/css-modules) for\nthe complete example.\n\n\u003e **NOTE:** `postcss` and `postcss-modules` have to be added to your `package.json`.\n\n### quietDeps\n\nIn order for `quietDeps` to correctly identify external dependencies the `url` option is defaulted to the importing file path URL.\n\n\u003e The `url` option creates problems when importing source SASS files from 3rd party modules in which case the best workaround is to avoid `quietDeps` and [mute the logger](https://sass-lang.com/documentation/js-api/interfaces/StringOptionsWithImporter#logger) if that's a big issue.\n\n### `silenceDeprecations`\n\nThis option accepts an array of properties for deprecations that will omitted from the logs during a build.\n\n### namedExports\n\nType: `boolean` `function`\u003cbr\u003e\nDefault: `false`\n\nUse named exports alongside default export.\n\nYou can supply a function to control how exported named is generated:\n\n```js\nnamedExports(name) {\n  // Maybe you simply want to convert dash to underscore\n  return name.replace(/-/g, '_')\n}\n```\n\nIf you set it to `true`, the following will happen when importing specific classNames:\n\n- dashed class names will be transformed by replacing all the dashes to `$` sign wrapped underlines, eg. `--` =\u003e `$__$`\n- js protected names used as your style class names, will be transformed by wrapping the names between `$` signs, eg. `switch` =\u003e `$switch$`\n\nAll transformed names will be logged in your terminal like:\n\n```bash\nExported \"new\" as \"$new$\" in test/fixtures/named-exports/style.css\n```\n\nThe original will not be removed, it's still available on `default` export:\n\n```js\nimport style, { class$_$name, class$__$name, $switch$ } from './style.css'\nconsole.log(style['class-name'] === class$_$name) // true\nconsole.log(style['class--name'] === class$__$name) // true\nconsole.log(style['switch'] === $switch$) // true\n```\n\n### pnpm\n\nThere's a working example of using `pnpm` with `@material` design\nin [issue/38](https://github.com/glromeo/esbuild-sass-plugin/tree/main/test/issues/38)\n\n### Benchmarks\n**Windows 11** Pro - **i7-490K** CPU @ **4.00**GHz - RAM **32**GB - SSD **500**GB\n\nGiven 24 × 24 = 576 lit-element files \u0026 576 imported CSS styles plus the import of the full bootstrap 5.1\n\n|                        | sass-embedded  | sass-embedded (no cache) | dart sass | dart sass (no cache) |\n|------------------------|----------------|--------------------------|-----------|----------------------|\n| **initial build**      | 731.312ms      | 779.363ms                | 2.450s    | 2.450s               |\n| rebuild (.ts change)   | 170.35ms       | 188.861ms                | 179.125ms | 1.710s               |\n| rebuild (.ts change)   | 155.802ms      | 167.413ms                | 176.849ms | 1.576s               |\n| rebuild (.scss change) | 203.746ms      | 160.601ms                | 188.164ms | 1.575s               |\n| rebuild (.scss change) | 152.733ms      | 144.754ms                | 145.835ms | 1.520s               |\n\n\n[travis-url]: https://app.travis-ci.com/glromeo/esbuild-sass-plugin\n[travis-image]: https://app.travis-ci.com/glromeo/esbuild-sass-plugin.svg?branch=main\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglromeo%2Fesbuild-sass-plugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fglromeo%2Fesbuild-sass-plugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglromeo%2Fesbuild-sass-plugin/lists"}