Skip to content

Commit

Permalink
feat: Add support namedExports
Browse files Browse the repository at this point in the history
  • Loading branch information
dungjk committed May 20, 2024
1 parent e6519ee commit 5daa8fd
Show file tree
Hide file tree
Showing 13 changed files with 214 additions and 49 deletions.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,42 @@ In order for `quietDeps` to correctly identify external dependencies the `url` o
> 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.
### namedExports
Type: `boolean` `function`<br>
Default: `false`

Use named exports alongside default export.

You can supply a function to control how exported named is generated:

```js
namedExports(name) {
// Maybe you simply want to convert dash to underscore
return name.replace(/-/g, '_')
}
```
If you set it to `true`, the following will happen when importing specific classNames:
- dashed class names will be transformed by replacing all the dashes to `$` sign wrapped underlines, eg. `--` => `$__$`
- js protected names used as your style class names, will be transformed by wrapping the names between `$` signs, eg. `switch` => `$switch$`

All transformed names will be logged in your terminal like:

```bash
Exported "new" as "$new$" in test/fixtures/named-exports/style.css
```

The original will not be removed, it's still available on `default` export:

```js
import style, { class$_$name, class$__$name, $switch$ } from './style.css'
console.log(style['class-name'] === class$_$name) // true
console.log(style['class--name'] === class$__$name) // true
console.log(style['switch'] === $switch$) // true
```

### pnpm

There's a working example of using `pnpm` with `@material` design
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
},
"dependencies": {
"resolve": "^1.22.8",
"safe-identifier": "^0.4.2",
"sass": "^1.71.1"
},
"devDependencies": {
Expand All @@ -52,10 +53,10 @@
"postcss": "^8.4.35",
"postcss-modules": "^6.0.0",
"postcss-url": "^10.1.3",
"sass-embedded": "^1.71.1",
"source-map": "^0.7.4",
"ts-node": "^10.9.2",
"typescript": "^5.3.3",
"sass-embedded": "^1.71.1"
"typescript": "^5.3.3"
},
"peerDependencies": {
"esbuild": ">=0.20.1",
Expand Down
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {StringOptions} from 'sass'
import {sassPlugin} from './plugin'

export type Type = 'css' | 'local-css' | 'style' | 'css-text' | 'lit-css' | ((cssText: string, nonce?: string) => string)
export type NamedExport = boolean | ((name: string) => string);

export type SassPluginOptions = StringOptions<'sync'|'async'> & {

Expand Down Expand Up @@ -81,6 +82,11 @@ export type SassPluginOptions = StringOptions<'sync'|'async'> & {
* To enable the sass-embedded compiler
*/
embedded?: boolean

/**
* Use named exports alongside default export.
*/
namedExports?: NamedExport;
}

export default sassPlugin
Expand Down
52 changes: 34 additions & 18 deletions src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {OnLoadResult, Plugin} from 'esbuild'
import {dirname} from 'path'
import {SassPluginOptions} from './index'
import {getContext, makeModule, modulesPaths, parseNonce, posixRelative, DEFAULT_FILTER} from './utils'
import {useCache} from './cache'
import {createRenderer} from './render'
import { OnLoadResult, Plugin } from 'esbuild'
import { dirname } from 'path'
import { SassPluginOptions } from './index'
import { getContext, makeModule, modulesPaths, parseNonce, posixRelative, DEFAULT_FILTER, ensureClassName } from './utils'
import { useCache } from './cache'
import { createRenderer } from './render'

/**
*
Expand All @@ -29,7 +29,7 @@ export function sassPlugin(options: SassPluginOptions = {}): Plugin {

return {
name: 'sass-plugin',
async setup({initialOptions, onResolve, onLoad, resolve, onStart, onDispose}) {
async setup({ initialOptions, onResolve, onLoad, resolve, onStart, onDispose }) {

options.loadPaths = Array.from(new Set([
...options.loadPaths || modulesPaths(initialOptions.absWorkingDir),
Expand All @@ -42,8 +42,8 @@ export function sassPlugin(options: SassPluginOptions = {}): Plugin {
} = getContext(initialOptions)

if (options.cssImports) {
onResolve({filter: /^~.*\.css$/}, ({path, importer, resolveDir}) => {
return resolve(path.slice(1), {importer, resolveDir, kind: 'import-rule'})
onResolve({ filter: /^~.*\.css$/ }, ({ path, importer, resolveDir }) => {
return resolve(path.slice(1), { importer, resolveDir, kind: 'import-rule' })
})
}

Expand All @@ -57,13 +57,13 @@ export function sassPlugin(options: SassPluginOptions = {}): Plugin {
if (transform) {
const namespace = 'esbuild-sass-plugin'

onResolve({filter: /^css-chunk:/}, ({path, resolveDir}) => ({
onResolve({ filter: /^css-chunk:/ }, ({ path, resolveDir }) => ({
path,
namespace,
pluginData: {resolveDir}
pluginData: { resolveDir }
}))

onLoad({filter: /./, namespace}, ({path, pluginData: {resolveDir}}) => ({
onLoad({ filter: /./, namespace }, ({ path, pluginData: { resolveDir } }) => ({
contents: cssChunks[path],
resolveDir,
loader: 'css'
Expand All @@ -72,9 +72,9 @@ export function sassPlugin(options: SassPluginOptions = {}): Plugin {

const renderSass = await createRenderer(options, options.sourceMap ?? sourcemap, onDispose)

onLoad({filter: options.filter ?? DEFAULT_FILTER}, useCache(options, fsStatCache, async path => {
onLoad({ filter: options.filter ?? DEFAULT_FILTER }, useCache(options, fsStatCache, async path => {
try {
let {cssText, watchFiles, warnings} = await renderSass(path)
let { cssText, watchFiles, warnings } = await renderSass(path)
if (!warnings) {
warnings = []
}
Expand All @@ -94,7 +94,7 @@ export function sassPlugin(options: SassPluginOptions = {}): Plugin {
watchDirs: out.watchDirs || []
}
}
let {contents, pluginData} = out
let { contents, pluginData } = out
if (type === 'css') {
let name = posixRelative(path)
cssChunks[name] = contents
Expand All @@ -103,11 +103,27 @@ export function sassPlugin(options: SassPluginOptions = {}): Plugin {
contents = makeModule(String(contents), 'style', nonce)
} else {
return {
errors: [{text: `unsupported type '${type}' for postCSS modules`}]
errors: [{ text: `unsupported type '${type}' for postCSS modules` }]
}
}

let exportConstants = "";
if (options.namedExports && pluginData.exports) {
const json = JSON.parse(pluginData.exports);
const getClassName =
typeof options.namedExports === "function"
? options.namedExports
: ensureClassName;
Object.keys(json).forEach((name) => {
const newName = getClassName(name);
exportConstants += `export const ${newName} = ${JSON.stringify(
json[name]
)};\n`;
});
}

return {
contents: `${contents}export default ${pluginData.exports};`,
contents: `${contents}${exportConstants}export default ${pluginData.exports};`,
loader: 'js',
resolveDir,
watchFiles: [...watchFiles, ...(out.watchFiles || [])],
Expand All @@ -134,7 +150,7 @@ export function sassPlugin(options: SassPluginOptions = {}): Plugin {

} catch (err: any) {
return {
errors: [{text: err.message}],
errors: [{ text: err.message }],
watchFiles: watched[path] ?? [path]
}
}
Expand Down
38 changes: 23 additions & 15 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {SassPluginOptions, Type} from './index'
import {AcceptedPlugin, Postcss} from 'postcss'
import { SassPluginOptions, Type } from './index'
import { AcceptedPlugin, Postcss } from 'postcss'
import PostcssModulesPlugin from 'postcss-modules'
import {BuildOptions, OnLoadResult} from 'esbuild'
import {Syntax} from 'sass'
import {parse, relative, resolve} from 'path'
import {existsSync} from 'fs'
import {SyncOpts} from 'resolve'
import { BuildOptions, OnLoadResult } from 'esbuild'
import { Syntax } from 'sass'
import { parse, relative, resolve } from 'path'
import { existsSync } from 'fs'
import { SyncOpts } from 'resolve'
import { identifier } from 'safe-identifier'

const cwd = process.cwd()

Expand All @@ -17,7 +18,7 @@ export const posixRelative = require('path').sep === '/'

export function modulesPaths(absWorkingDir?: string): string[] {
let path = absWorkingDir || process.cwd()
let {root} = parse(path)
let { root } = parse(path)
let found: string[] = []
while (path !== root) {
const filename = resolve(path, 'node_modules')
Expand Down Expand Up @@ -77,11 +78,11 @@ function requireTool(module: string, basedir?: string) {
} catch (ignored) {
}
if (basedir) try {
return require(require.resolve(module, {paths: [basedir]}))
return require(require.resolve(module, { paths: [basedir] }))
} catch (ignored) {
}
try {
return require(require.resolve(module, {paths: [process.cwd()]}))
return require(require.resolve(module, { paths: [process.cwd()] }))
} catch (e) {
try {
return require(module) // extra attempt at finding a co-located tool
Expand Down Expand Up @@ -116,7 +117,7 @@ document.head
export {css};
`

export function makeModule(contents: string, type: Type, nonce?: string):string {
export function makeModule(contents: string, type: Type, nonce?: string): string {
switch (type) {
case 'style':
return styleModule(contents, nonce)
Expand Down Expand Up @@ -157,7 +158,7 @@ export function postcssModules(options: PostcssModulesParams, plugins: AcceptedP

let cssModule

const {css} = await postcss([
const { css } = await postcss([
postcssModulesPlugin({
...(options as Parameters<PostcssModulesPlugin>[0]),
getJSON(cssFilename: string, json: { [name: string]: string }, outputFileName?: string): void {
Expand All @@ -166,11 +167,11 @@ export function postcssModules(options: PostcssModulesParams, plugins: AcceptedP
}
}),
...plugins
]).process(source, {from: path, map: false})
]).process(source, { from: path, map: false })

return {
contents: css,
pluginData: {exports: cssModule},
pluginData: { exports: cssModule },
loader: 'js'
}
}
Expand All @@ -187,7 +188,7 @@ export function createResolver(options: SassPluginOptions = {}, loadPaths: strin
let cached = cache[pkgfile]
if (!cached) {
const pkg = JSON.parse(readFileSync(pkgfile) as string)
cached = cache[pkgfile] = {main: pkg[prefer] || pkg.main}
cached = cache[pkgfile] = { main: pkg[prefer] || pkg.main }
}
return cached
}
Expand Down Expand Up @@ -226,3 +227,10 @@ export function createResolver(options: SassPluginOptions = {}, loadPaths: strin
}
}
}

const escapeClassNameDashes = (name: string) =>
name.replace(/-+/g, (match) => `$${match.replace(/-/g, "_")}$`);
export const ensureClassName = (name: string) => {
const escaped = escapeClassNameDashes(name);
return identifier(escaped);
};
Loading

0 comments on commit 5daa8fd

Please sign in to comment.