esbuild-sass-plugin
Advanced tools
Comparing version 2.0.0-alpha.1 to 2.0.0-alpha.3
@@ -1,28 +0,13 @@ | ||
import { OnLoadResult } from "esbuild"; | ||
import { Importer, Value } from "sass"; | ||
import { sassPlugin } from "./plugin"; | ||
export declare type Type = "css" | "style" | "css-text" | "lit-css"; | ||
export declare type SassPluginOptions = { | ||
import { OnLoadResult } from 'esbuild'; | ||
import { StringOptions } from 'sass'; | ||
import { sassPlugin } from './plugin'; | ||
export declare type Type = 'css' | 'style' | 'css-text' | 'lit-css'; | ||
export declare type SassPluginOptions = StringOptions<'sync'> & { | ||
filter?: RegExp; | ||
importMapper?: (url: string) => string; | ||
implementation?: string; | ||
includePaths?: string[]; | ||
basedir?: string; | ||
type?: Type; | ||
cache?: Map<string, Map<string, CachedResult>> | boolean; | ||
importer?: Importer | Importer[]; | ||
functions?: { | ||
[key: string]: (...args: Value[]) => Value | void; | ||
}; | ||
includePaths?: string[]; | ||
indentedSyntax?: boolean; | ||
indentType?: "space" | "tab"; | ||
indentWidth?: number; | ||
linefeed?: "cr" | "crlf" | "lf" | "lfcr"; | ||
outputStyle?: "compressed" | "expanded"; | ||
sourceMap?: boolean | string; | ||
sourceMapContents?: boolean; | ||
sourceMapEmbed?: boolean; | ||
sourceMapRoot?: string; | ||
cache?: Map<string, CachedResult> | boolean; | ||
transform?: (css: string, resolveDir: string, filePath: string) => string | OnLoadResult | Promise<string | OnLoadResult>; | ||
quietDeps?: boolean; | ||
precompile?: (source: string, path: string) => string; | ||
@@ -32,3 +17,3 @@ }; | ||
export { sassPlugin }; | ||
export { makeModule, postcssModules } from "./utils"; | ||
export { makeModule, postcssModules } from './utils'; | ||
export declare type CachedResult = { | ||
@@ -35,0 +20,0 @@ mtimeMs: number; |
@@ -0,0 +0,0 @@ "use strict"; |
@@ -1,3 +0,3 @@ | ||
import { Plugin } from "esbuild"; | ||
import { SassPluginOptions } from "./index"; | ||
import { Plugin } from 'esbuild'; | ||
import { SassPluginOptions } from './index'; | ||
export declare function sassPlugin(options?: SassPluginOptions): Plugin; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.sassPlugin = void 0; | ||
const fs = __importStar(require("fs")); | ||
const fs_1 = require("fs"); | ||
const path_1 = require("path"); | ||
const utils_1 = require("./utils"); | ||
const sass = __importStar(require("sass")); | ||
const url_1 = require("url"); | ||
let pluginIndex = 0; | ||
const cache_1 = require("./cache"); | ||
const render_1 = require("./render"); | ||
function sassPlugin(options = {}) { | ||
@@ -35,186 +13,47 @@ var _a; | ||
} | ||
const type = (_a = options.type) !== null && _a !== void 0 ? _a : "css"; | ||
if (options["picomatch"] || options["exclude"] || typeof type !== "string") { | ||
console.log("The type array, exclude and picomatch options are no longer supported, please refer to the README for alternatives."); | ||
if (options.includePaths) { | ||
console.log(`'includePaths' option is deprecated, please use 'loadPaths' instead`); | ||
} | ||
function pathResolve({ resolveDir, path, importer }) { | ||
return (0, path_1.resolve)(resolveDir || (0, path_1.dirname)(importer), path); | ||
options.loadPaths = Array.from(new Set([ | ||
...options.loadPaths || (0, utils_1.modulesPaths)(), | ||
...options.includePaths || [] | ||
])); | ||
const type = (_a = options.type) !== null && _a !== void 0 ? _a : 'css'; | ||
if (options['picomatch'] || options['exclude'] || typeof type !== 'string') { | ||
console.log('The type array, exclude and picomatch options are no longer supported, please refer to the README for alternatives.'); | ||
} | ||
function requireResolve({ resolveDir, path, importer }) { | ||
if (!resolveDir) { | ||
resolveDir = (0, path_1.dirname)(importer); | ||
const requireOptions = { paths: ['.', ...options.loadPaths] }; | ||
function resolvePath(basedir, path) { | ||
if (options.importMapper) { | ||
path = options.importMapper(path); | ||
} | ||
const mapper = options.importMapper; | ||
if (mapper) { | ||
path = mapper(path); | ||
if (utils_1.RELATIVE_PATH.test(path)) { | ||
return (0, path_1.resolve)(basedir, path); | ||
} | ||
const paths = options.includePaths ? [resolveDir, ...options.includePaths] : [resolveDir]; | ||
return require.resolve(path, { paths }); | ||
} | ||
function readCssFileSync(path) { | ||
return { css: (0, fs_1.readFileSync)(path, "utf-8"), watchFiles: [path] }; | ||
} | ||
function resolveSassImport(pathname, ext) { | ||
if (ext) { | ||
let filename = pathname + ext; | ||
if (fs.existsSync(filename)) { | ||
return filename; | ||
} | ||
const index = filename.lastIndexOf(path_1.sep); | ||
filename = index >= 0 ? filename.slice(0, index) + path_1.sep + "_" + filename.slice(index + 1) : "_" + filename; | ||
if (fs.existsSync(filename)) { | ||
return filename; | ||
} | ||
return null; | ||
} | ||
else { | ||
return resolveSassImport(pathname, ".scss") | ||
|| resolveSassImport(pathname, ".sass") | ||
|| resolveSassImport(pathname + path_1.sep + "index"); | ||
requireOptions.paths[0] = basedir; | ||
return require.resolve(path, requireOptions); | ||
} | ||
} | ||
function renderSync(path) { | ||
const basedir = (0, path_1.dirname)(path); | ||
let source = fs.readFileSync(path, "utf8"); | ||
if (options.precompile) { | ||
source = options.precompile(source, path); | ||
} | ||
const { css, loadedUrls } = sass.compileString(source, { | ||
importer: { | ||
load(canonicalUrl) { | ||
const filename = path_1.sep === "/" ? canonicalUrl.pathname : canonicalUrl.pathname.slice(1); | ||
let contents = fs.readFileSync(filename, "utf8"); | ||
if (options.precompile) { | ||
contents = options.precompile(contents, filename); | ||
} | ||
switch ((0, path_1.extname)(filename)) { | ||
case ".scss": | ||
return { contents, syntax: "scss" }; | ||
case ".css": | ||
return { contents, syntax: "css" }; | ||
default: | ||
return { contents, syntax: "indented" }; | ||
} | ||
}, | ||
canonicalize(url) { | ||
let filename; | ||
if (url.startsWith("~")) { | ||
filename = require.resolve(url.slice(1), { paths: [basedir] }); | ||
} | ||
else if (url.startsWith("file://")) { | ||
filename = path_1.sep === "/" ? url.slice(7) : url.slice(8); | ||
} | ||
else { | ||
filename = url; | ||
} | ||
if (options.importMapper) { | ||
filename = options.importMapper(filename); | ||
} | ||
filename = (0, path_1.resolve)(basedir, filename); | ||
let ext = filename.lastIndexOf("."); | ||
if (ext >= 0) { | ||
filename = resolveSassImport(filename.slice(0, ext), filename.slice(ext)); | ||
} | ||
else { | ||
filename = resolveSassImport(filename); | ||
} | ||
if (filename) { | ||
return (0, url_1.pathToFileURL)(filename); | ||
} | ||
else { | ||
return null; | ||
} | ||
} | ||
}, | ||
logger: { | ||
warn(message, options) { | ||
console.log(message, options); | ||
}, | ||
debug(message, options) { | ||
console.log(message, options); | ||
} | ||
} | ||
}); | ||
return { | ||
css: css.toString(), | ||
watchFiles: loadedUrls.map(url => url.pathname.slice(1)) | ||
}; | ||
} | ||
const cache = !options.cache | ||
? null | ||
: options.cache instanceof Map | ||
? options.cache | ||
: new Map(); | ||
function collectStats(watchFiles) { | ||
return Promise.all(watchFiles.map(filename => fs_1.promises.stat(filename))); | ||
} | ||
function maxMtimeMs(stats) { | ||
return stats.reduce((max, { mtimeMs }) => Math.max(max, mtimeMs), 0); | ||
} | ||
const RELATIVE_PATH = /^\.\.?\//; | ||
const namespace = `sass-plugin-${pluginIndex++}`; | ||
return { | ||
name: "sass-plugin", | ||
name: 'sass-plugin', | ||
setup: function (build) { | ||
var _a; | ||
const { namespace, sourcemap, watched } = (0, utils_1.getContext)(build.initialOptions); | ||
build.onResolve({ filter: (_a = options.filter) !== null && _a !== void 0 ? _a : /\.(s[ac]ss|css)$/ }, (args) => { | ||
if (RELATIVE_PATH.test(args.path)) { | ||
return { path: pathResolve(args), namespace, pluginData: args }; | ||
} | ||
else { | ||
return { path: requireResolve(args), namespace, pluginData: args }; | ||
} | ||
const { resolveDir, path, importer } = args; | ||
const basedir = resolveDir || (0, path_1.dirname)(importer); | ||
return { path: resolvePath(basedir, path), namespace, pluginData: args }; | ||
}); | ||
let cached; | ||
if (cache) { | ||
cached = (transform) => async ({ path, pluginData: args }) => { | ||
let group = cache.get(args.resolveDir); | ||
if (!group) { | ||
group = new Map(); | ||
cache.set(args.resolveDir, group); | ||
} | ||
try { | ||
let cached = group.get(args.path); | ||
if (cached) { | ||
let watchFiles = cached.result.watchFiles; | ||
let stats = await collectStats(watchFiles); | ||
for (const { mtimeMs } of stats) { | ||
if (mtimeMs > cached.mtimeMs) { | ||
cached.result = await transform(watchFiles[0]); | ||
cached.mtimeMs = maxMtimeMs(stats); | ||
break; | ||
} | ||
} | ||
return cached.result; | ||
} | ||
let result = await transform(path); | ||
group.set(args.path, { | ||
mtimeMs: maxMtimeMs(await collectStats(result.watchFiles)), | ||
result | ||
}); | ||
return result; | ||
} | ||
catch (error) { | ||
group.delete(args.path); | ||
throw error; | ||
} | ||
}; | ||
} | ||
else { | ||
cached = (transform) => ({ path, pluginData: args }) => { | ||
return transform(path); | ||
}; | ||
} | ||
const lastWatchFiles = build.initialOptions.watch ? {} : null; | ||
async function transform(path) { | ||
const renderSync = (0, render_1.createRenderer)(options, sourcemap); | ||
build.onLoad({ filter: /./, namespace }, (0, cache_1.useCache)(options, async (path) => { | ||
var _a; | ||
try { | ||
let { css, watchFiles } = path.endsWith(".css") ? readCssFileSync(path) : renderSync(path); | ||
watchFiles = [...watchFiles]; | ||
if (lastWatchFiles) { | ||
lastWatchFiles[path] = watchFiles; | ||
let { css, watchFiles } = renderSync(path); | ||
if (watched) { | ||
watched[path] = watchFiles; | ||
} | ||
if (options.transform) { | ||
const out = await options.transform(css, (0, path_1.dirname)(path), path); | ||
if (typeof out !== "string") { | ||
if (typeof out !== 'string') { | ||
return { | ||
@@ -232,5 +71,5 @@ contents: out.contents, | ||
} | ||
return type === "css" ? { | ||
return type === 'css' ? { | ||
contents: css, | ||
loader: "css", | ||
loader: 'css', | ||
resolveDir: (0, path_1.dirname)(path), | ||
@@ -240,3 +79,3 @@ watchFiles | ||
contents: (0, utils_1.makeModule)(css, type), | ||
loader: "js", | ||
loader: 'js', | ||
resolveDir: (0, path_1.dirname)(path), | ||
@@ -249,7 +88,6 @@ watchFiles | ||
errors: [{ text: err.message }], | ||
watchFiles: (_a = lastWatchFiles === null || lastWatchFiles === void 0 ? void 0 : lastWatchFiles[path]) !== null && _a !== void 0 ? _a : [path] | ||
watchFiles: (_a = watched === null || watched === void 0 ? void 0 : watched[path]) !== null && _a !== void 0 ? _a : [path] | ||
}; | ||
} | ||
} | ||
build.onLoad({ filter: /./, namespace }, cached(transform)); | ||
})); | ||
} | ||
@@ -256,0 +94,0 @@ }; |
@@ -1,5 +0,19 @@ | ||
import { Type } from "./index"; | ||
import { AcceptedPlugin } from "postcss"; | ||
import PostcssModulesPlugin from "postcss-modules"; | ||
import { OnLoadResult } from "esbuild"; | ||
import { Type } from './index'; | ||
import { AcceptedPlugin } from 'postcss'; | ||
import PostcssModulesPlugin from 'postcss-modules'; | ||
import { BuildOptions, OnLoadResult } from 'esbuild'; | ||
import { Syntax } from 'sass'; | ||
export declare const RELATIVE_PATH: RegExp; | ||
export declare function modulesPaths(): string[]; | ||
export declare function fileSyntax(filename: string): Syntax; | ||
export declare type PluginContext = { | ||
instance: number; | ||
namespace: string; | ||
sourcemap: boolean; | ||
watched: { | ||
[path: string]: string[]; | ||
} | null; | ||
}; | ||
export declare function getContext(buildOptions: BuildOptions): PluginContext; | ||
export declare function sourceMappingURL(sourceMap: any): string; | ||
export declare function makeModule(contents: string, type: Type): string; | ||
@@ -6,0 +20,0 @@ export declare type PostcssModulesParams = Parameters<PostcssModulesPlugin>[0] & { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.postcssModules = exports.makeModule = void 0; | ||
exports.postcssModules = exports.makeModule = exports.sourceMappingURL = exports.getContext = exports.fileSyntax = exports.modulesPaths = exports.RELATIVE_PATH = void 0; | ||
const path_1 = require("path"); | ||
const fs_1 = require("fs"); | ||
exports.RELATIVE_PATH = /^\.\.?\//; | ||
function modulesPaths() { | ||
let path = process.cwd(); | ||
let { root } = (0, path_1.parse)(path); | ||
let found = []; | ||
while (path !== root) { | ||
const filename = (0, path_1.resolve)(path, 'node_modules'); | ||
if ((0, fs_1.existsSync)(filename)) { | ||
found.push(filename); | ||
} | ||
path = (0, path_1.resolve)(path, '..'); | ||
} | ||
return [...found]; | ||
} | ||
exports.modulesPaths = modulesPaths; | ||
function fileSyntax(filename) { | ||
if (filename.endsWith('.scss')) { | ||
return 'scss'; | ||
} | ||
else if (filename.endsWith('.css')) { | ||
return 'css'; | ||
} | ||
else { | ||
return 'indented'; | ||
} | ||
} | ||
exports.fileSyntax = fileSyntax; | ||
const SASS_PLUGIN_CONTEXT = Symbol(); | ||
function getContext(buildOptions) { | ||
let descriptor = Object.getOwnPropertyDescriptor(buildOptions, SASS_PLUGIN_CONTEXT); | ||
if (descriptor === undefined) { | ||
Object.defineProperty(buildOptions, SASS_PLUGIN_CONTEXT, descriptor = { | ||
value: { | ||
instances: 0 | ||
} | ||
}); | ||
} | ||
const instance = descriptor.value.instances++; | ||
return { | ||
instance, | ||
namespace: `sass-plugin-${instance}`, | ||
sourcemap: !!buildOptions.sourcemap, | ||
watched: buildOptions.watch ? {} : null | ||
}; | ||
} | ||
exports.getContext = getContext; | ||
function sourceMappingURL(sourceMap) { | ||
const data = Buffer.from(JSON.stringify(sourceMap), 'utf8').toString('base64'); | ||
return `/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${data} */`; | ||
} | ||
exports.sourceMappingURL = sourceMappingURL; | ||
function requireModule(module, includePaths) { | ||
@@ -20,3 +73,3 @@ try { | ||
export default \` | ||
${cssText.replace(/([$`\\])/g, "\\$1")}\`; | ||
${cssText.replace(/([$`\\])/g, '\\$1')}\`; | ||
`; | ||
@@ -26,6 +79,6 @@ const cssResultModule = cssText => `\ | ||
export default css\` | ||
${cssText.replace(/([$`\\])/g, "\\$1")}\`; | ||
${cssText.replace(/([$`\\])/g, '\\$1')}\`; | ||
`; | ||
const styleModule = cssText => `\ | ||
const css = \`${cssText.replace(/([$`\\])/g, "\\$1")}\`; | ||
const css = \`${cssText.replace(/([$`\\])/g, '\\$1')}\`; | ||
document.head | ||
@@ -37,7 +90,7 @@ .appendChild(document.createElement("style")) | ||
function makeModule(contents, type) { | ||
if (type === "style") { | ||
if (type === 'style') { | ||
return styleModule(contents); | ||
} | ||
else { | ||
return type === "lit-css" ? cssResultModule(contents) : cssTextModule(contents); | ||
return type === 'lit-css' ? cssResultModule(contents) : cssTextModule(contents); | ||
} | ||
@@ -49,4 +102,4 @@ } | ||
const includePaths = (_a = options.includePaths) !== null && _a !== void 0 ? _a : [(_b = options.basedir) !== null && _b !== void 0 ? _b : process.cwd()]; | ||
const postcss = requireModule("postcss", includePaths); | ||
const postcssModulesPlugin = requireModule("postcss-modules", includePaths); | ||
const postcss = requireModule('postcss', includePaths); | ||
const postcssModulesPlugin = requireModule('postcss-modules', includePaths); | ||
return async (source, dirname, path) => { | ||
@@ -64,4 +117,4 @@ let cssModule; | ||
return { | ||
contents: `${makeModule(css, "style")}export default ${cssModule};`, | ||
loader: "js" | ||
contents: `${makeModule(css, 'style')}export default ${cssModule};`, | ||
loader: 'js' | ||
}; | ||
@@ -68,0 +121,0 @@ }; |
{ | ||
"name": "esbuild-sass-plugin", | ||
"version": "2.0.0-alpha.1", | ||
"version": "2.0.0-alpha.3", | ||
"description": "esbuild plugin for sass/scss files supporting both css loader and css result import (lit-element)", | ||
@@ -10,10 +10,8 @@ "main": "lib/index.js", | ||
"sass", | ||
"scss", | ||
"LICENSE", | ||
"README.MD", | ||
"tsconfig.json" | ||
"scss" | ||
], | ||
"files": [ | ||
"lib", | ||
"src" | ||
"LICENSE", | ||
"README.MD" | ||
], | ||
@@ -34,18 +32,5 @@ "author": "Gianluca Romeo <glromeo@gmail.com> (https://github.com/glromeo/esbuild-sass-plugin)", | ||
"build:watch": "tsc --watch", | ||
"test": "nyc mocha **/*.test.ts" | ||
"test": "c8 mocha test/**/*.test.ts", | ||
"coverage": "c8 report --reporter=text-lcov | coveralls" | ||
}, | ||
"nyc": { | ||
"extension": [ | ||
".ts" | ||
], | ||
"exclude": [ | ||
"**/*.d.ts", | ||
"test/**/*.ts" | ||
], | ||
"reporter": [ | ||
"text" | ||
], | ||
"all": false, | ||
"cache": true | ||
}, | ||
"mocha": { | ||
@@ -57,24 +42,15 @@ "require": [ | ||
"dependencies": { | ||
"esbuild": "^0.14.5", | ||
"picomatch": "^2.3.0", | ||
"resolve": "^1.20.0", | ||
"sass": "^1.45.0" | ||
"esbuild": "^0.14.6", | ||
"sass": "^1.45.1" | ||
}, | ||
"devDependencies": { | ||
"@types/chai": "^4.3.0", | ||
"@types/chai-string": "^1.4.2", | ||
"@types/mocha": "^9.0.0", | ||
"@types/node": "^16.11.12", | ||
"@types/resolve": "^1.20.1", | ||
"@types/node": "^17.0.2", | ||
"@types/sass": "^1.43.1", | ||
"chai": "^4.3.4", | ||
"chai-string": "^1.5.0", | ||
"mocha": "^9.1.3", | ||
"nyc": "^15.1.0", | ||
"mocha-toolkit": "^1.0.7", | ||
"postcss": "^8.4.5", | ||
"postcss-modules": "^4.2.2", | ||
"postcss-url": "^10.1.3", | ||
"ts-node": "^10.4.0", | ||
"source-map": "^0.7.3", | ||
"typescript": "^4.5.4" | ||
} | ||
} |
321
README.md
@@ -6,6 +6,8 @@ ![cooltext394785080075403](https://user-images.githubusercontent.com/160981/136289874-26ce7269-ea08-47dd-be31-9bf0ef7a0b8d.png) | ||
A plugin for [esbuild](https://esbuild.github.io/) to handle Sass & SCSS files. | ||
**NOTE:** This is the 2.x branch of the plugin, if you want to use node-sass or an old feature please stick to the 1.x branch | ||
**NOTE:** This is the 2.x branch of the plugin, if you want to use node-sass or an old feature please stick to the 1.x | ||
branch | ||
### Features | ||
* **PostCSS** & **CSS modules** | ||
@@ -19,9 +21,12 @@ * support for **constructable stylesheet** to be used in custom elements or `dynamic style` to be added to the html page | ||
### 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](https://github.com/sass/node-sass) has been removed from the 2.x branch 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. | ||
* `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](https://github.com/sass/node-sass) has been removed from the 2.x branch 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 | ||
```console | ||
@@ -32,14 +37,17 @@ $ npm i esbuild-sass-plugin | ||
### Usage | ||
Just add it to your esbuild plugins: | ||
```javascript | ||
import {sassPlugin} from "esbuild-sass-plugin"; | ||
import {sassPlugin} from 'esbuild-sass-plugin' | ||
await esbuild.build({ | ||
... | ||
plugins: [sassPlugin()] | ||
}); | ||
... | ||
plugins: [sassPlugin()] | ||
}) | ||
``` | ||
this will use `loader: "css"` and your transpiled Sass will be included in index.css. | ||
If you specify `type: "style"` then the stylesheet will be dynamically added to the page. | ||
If you specify `type: "style"` then the stylesheet will be dynamically added to the page. | ||
@@ -50,74 +58,85 @@ 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", | ||
... // other options for sass.renderSync(...) | ||
})] | ||
}); | ||
... | ||
plugins: [sassPlugin({ | ||
type: "css-text", | ||
... // for the options availanle look at 'SassPluginOptions' in index.ts | ||
})] | ||
}) | ||
``` | ||
...and in your module do something like | ||
```javascript | ||
... | ||
import cssText from "./styles.scss"; | ||
... | ||
customElements.define("hello-world", class HelloWorld extends HTMLElement { | ||
import cssText from './styles.scss' | ||
constructor() { | ||
super(); | ||
this.attachShadow({mode: 'open'}); | ||
this.sheet = new CSSStyleSheet(); | ||
this.sheet.replaceSync(cssText); | ||
this.shadowRoot.adoptedStyleSheets = [this.sheet]; | ||
} | ||
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"` | ||
```javascript | ||
... | ||
import styles from "./styles.scss"; | ||
... | ||
import styles from './styles.scss' | ||
@customElement("hello-world") | ||
export default class HelloWorld extends LitElement { | ||
static styles = styles | ||
static styles = styles | ||
render() { | ||
... | ||
} | ||
render() { | ||
... | ||
} | ||
} | ||
``` | ||
Look in the `test` folder for more usage examples. | ||
Look in `test/fixtures` folder for more usage examples. | ||
### Options | ||
The **options** passed to the plugin are a superset of the Sass [Options](https://sass-lang.com/documentation/js-api#options). | ||
The **options** passed to the plugin are a superset of the | ||
Sass [Options](https://sass-lang.com/documentation/js-api#options). | ||
|Option| Type |Default| | ||
|---|-------------------------------|---| | ||
|cache| boolean or Map |true| | ||
|type| `"css"`<br/>`"style"`<br/>`"lit-css"` |`"css"`| | ||
|transform| function |undefined| | ||
|exclude| regex or function |undefined| | ||
|importer| function |undefined| | ||
|importMapper| function |undefined| | ||
| Option | Type | Default | | ||
|--------------|---------------------------------------|----------------------------------------| | ||
| cache | boolean or Map | true (one Map per namespace) | | ||
| type | `"css"`<br/>`"style"`<br/>`"lit-css"` | `"css"` | | ||
| transform | function | undefined | | ||
| loadPaths | [string[]]() | collection of node_modules from cwd up | | ||
| importer | function | undefined | | ||
| importMapper | function | undefined | | ||
### Exclude Option | ||
### Exclude Option | ||
Used to exclude paths from the plugin. It can either be **a simple regex** which applies to the path | ||
Used to exclude paths from the plugin. It can either be **a simple regex** which applies to the path | ||
```javascript | ||
await esbuild.build({ | ||
... | ||
plugins: [sassPlugin({ | ||
exclude: /^http:\/\//, // ignores urls | ||
})] | ||
... | ||
plugins | ||
: | ||
[sassPlugin({ | ||
exclude: /^http:\/\//, // ignores urls | ||
})] | ||
}) | ||
``` | ||
**or a function** which receives the whole set of args that esbuild passes on resolve. | ||
```javascript | ||
await esbuild.build({ | ||
... | ||
plugins: [sassPlugin({ | ||
exclude: ({resolveDir}) => !/\\lit$/.test(resolveDir), // ignores files outside lit directory | ||
})] | ||
... | ||
plugins | ||
: | ||
[sassPlugin({ | ||
exclude: ({resolveDir}) => !/\\lit$/.test(resolveDir), // ignores files outside lit directory | ||
})] | ||
}) | ||
@@ -127,4 +146,8 @@ ``` | ||
### Importer Option | ||
The default importer built in the plugin has been developed to be fast but its scope is limited. For complex import scenarios (e.g pnpm) | ||
this can be replaced by a custom implementation like the very solid [`sass-extended-importer`](https://github.com/wessberg/sass-extended-importer) | ||
The default importer built in the plugin has been developed to be fast but its scope is limited. For complex import | ||
scenarios (e.g pnpm) | ||
this can be replaced by a custom implementation like the very | ||
solid [`sass-extended-importer`](https://github.com/wessberg/sass-extended-importer) | ||
```javascript | ||
@@ -134,22 +157,28 @@ const {createImporter} = require("sass-extended-importer"); | ||
await esbuild.build({ | ||
... | ||
plugins: [sassPlugin({ | ||
importer: createImporter() | ||
})] | ||
..., | ||
plugins:[sassPlugin({ | ||
importer: createImporter() | ||
})] | ||
}) | ||
``` | ||
There's a working example of using `pnpm` with `@material` design in [issue/38](https://github.com/glromeo/esbuild-sass-plugin/tree/main/test/issues/38) | ||
There's a working example of using `pnpm` with `@material` design | ||
in [issue/38](https://github.com/glromeo/esbuild-sass-plugin/tree/main/test/issues/38) | ||
### ImportMapper Option | ||
A function to customize/re-map the import path, both `import` statements in JavaScript/TypeScript code and `@import` | ||
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. | ||
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 | ||
```json | ||
{ | ||
"compilerOptions": { | ||
"baseUrl": ".", | ||
"baseUrl": ".", | ||
"paths": { | ||
"@img/*": ["./assets/images/*"] | ||
"@img/*": [ | ||
"./assets/images/*" | ||
] | ||
} | ||
@@ -159,9 +188,11 @@ } | ||
``` | ||
now you can resolve these paths with `importMapper` | ||
```javascript | ||
await esbuild.build({ | ||
... | ||
plugins: [sassPlugin({ | ||
importMapper: (path) => path.replace(/^@img\//,"./assets/images/") | ||
})] | ||
..., | ||
plugins: [sassPlugin({ | ||
importMapper: (path) => path.replace(/^@img\//, './assets/images/') | ||
})] | ||
}) | ||
@@ -171,5 +202,7 @@ ``` | ||
### Transform Option | ||
```typescript | ||
async (css:string, resolveDir:string?) => string | ||
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.\ | ||
@@ -179,33 +212,41 @@ 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: | ||
```javascript | ||
const postcss = require("postcss"); | ||
const autoprefixer = require("autoprefixer"); | ||
const postcssPresetEnv = require("postcss-preset-env"); | ||
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; | ||
} | ||
})] | ||
}); | ||
..., | ||
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: | ||
A helper function is available to do all the work of calling PostCSS to create a CSS module. The usage is something | ||
like: | ||
```javascript | ||
const {sassPlugin, postcssModules} = require("esbuild-sass-plugin"); | ||
const {sassPlugin, postcssModules} = require('esbuild-sass-plugin') | ||
esbuild.build({ | ||
... | ||
plugins: [sassPlugin({ | ||
transform: postcssModules({ | ||
// ...put here the options for postcss-modules: https://github.com/madyankin/postcss-modules | ||
}) | ||
})] | ||
}); | ||
..., | ||
plugins: [sassPlugin({ | ||
transform: postcssModules({ | ||
// ...put here the options for postcss-modules: https://github.com/madyankin/postcss-modules | ||
}) | ||
})] | ||
}) | ||
``` | ||
> **NOTE:** `postcss` and `postcss-modules` have to be added to your `package.json`. | ||
@@ -215,3 +256,4 @@ | ||
Look into [fixture/css-modules](https://github.com/glromeo/esbuild-sass-plugin/tree/main/test/fixture/css-modules) for the complete example. | ||
Look into [fixture/css-modules](https://github.com/glromeo/esbuild-sass-plugin/tree/main/test/fixture/css-modules) for | ||
the complete example. | ||
@@ -221,93 +263,18 @@ > **NOTE:** Since `v1.5.0` transform can return either a string or an esbuild `LoadResult` object. \ | ||
### Use node-sass instead of Dart Sass | ||
Remember to add the dependency | ||
```console | ||
$ npm i esbuild-sass-plugin node-sass | ||
``` | ||
and to specify the implementation in the options: | ||
```javascript | ||
await esbuild.build({ | ||
... | ||
plugins: [sassPlugin({ | ||
implementation: "node-sass", | ||
... // other options for sass.renderSync(...) | ||
})] | ||
}); | ||
``` | ||
When compared to dart sass, node-sass is not only behind and lacking features but it's also | ||
behaving slightly differently. If you experience [issues](https://github.com/glromeo/esbuild-sass-plugin/issues/34#issue-1029351164) | ||
try and replace the built-in importer with [magic importer](https://www.npmjs.com/package/node-sass-magic-importer) | ||
which is slower but better suited for node-sass | ||
```javascript | ||
const magicImporter = require('node-sass-magic-importer'); | ||
### Benchmarks | ||
await esbuild.build({ | ||
... | ||
plugins: [sassPlugin({ | ||
implementation: "node-sass", | ||
importer: magicImporter() | ||
})] | ||
}); | ||
``` | ||
### Caching | ||
Chaching greatly improves the performance in incremental builds or watch mode. | ||
It must be enabled with `cache: true` in the options. | ||
You can pass your own map instead of `true` if you want to recycle it across different builds. | ||
```javascript | ||
const pluginCache = new Map(); | ||
await esbuild.build({ | ||
... | ||
plugins: [sassPlugin({cache: pluginCache})], | ||
... | ||
}) | ||
``` | ||
### Benchmarks | ||
Given 24 × 24 = 576 lit-element files & 576 imported CSS styles plus the import of the full bootstrap 5.1 | ||
### sass (default implementation) | ||
| | 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 | | ||
#### cache: true | ||
``` | ||
initial build: 3.064s | ||
incremental build: 793.142ms | ||
incremental build: 772.664ms | ||
incremental build: 755.093ms | ||
incremental build: 721.7ms | ||
``` | ||
#### cache: false | ||
``` | ||
initial build: 3.026s | ||
incremental build: 1.849s | ||
incremental build: 1.715s | ||
incremental build: 1.665s | ||
incremental build: 1.640s | ||
``` | ||
(*) node-sass is here just to give a term of comparison ...those samples were taken from 1.8.x | ||
### node-sass (just as a reference ...samples taken from 1.8.x) | ||
#### cache: true | ||
``` | ||
initial build: 1.903s | ||
incremental build: 797.098ms | ||
incremental build: 768.213ms | ||
incremental build: 770.619ms | ||
incremental build: 750.743ms | ||
``` | ||
#### cache: false | ||
``` | ||
initial build: 1.858s | ||
incremental build: 1.689s | ||
incremental build: 1.790s | ||
incremental build: 1.652s | ||
incremental build: 1.682s | ||
``` | ||
[travis-url]: https://travis-ci.com/glromeo/esbuild-sass-plugin | ||
[travis-image]: https://travis-ci.com/glromeo/esbuild-sass-plugin.svg?branch=main |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
2
8
18
42598
469
270
8
1
- Removedpicomatch@^2.3.0
- Removedresolve@^1.20.0
- Removedfunction-bind@1.1.2(transitive)
- Removedhasown@2.0.2(transitive)
- Removedis-core-module@2.15.1(transitive)
- Removedpath-parse@1.0.7(transitive)
- Removedresolve@1.22.8(transitive)
- Removedsupports-preserve-symlinks-flag@1.0.0(transitive)
Updatedesbuild@^0.14.6
Updatedsass@^1.45.1