93 lines
2.3 KiB
TypeScript
93 lines
2.3 KiB
TypeScript
import { relative, resolve } from "path"
|
|
import { promises as fs } from "fs"
|
|
import type { Plugin } from "esbuild"
|
|
import postcss from "postcss"
|
|
// @ts-expect-error
|
|
import postcssSass from "@csstools/postcss-sass"
|
|
import cssModules from "postcss-modules"
|
|
|
|
type Options = Parameters<typeof cssModules>[0]
|
|
|
|
const PLUGIN_NAME = "esbuild-css-modules"
|
|
|
|
async function buildCSSModule(cssFullPath: string, options: Options) {
|
|
options = {
|
|
localsConvention: "camelCaseOnly",
|
|
...options,
|
|
}
|
|
const source = await fs.readFile(cssFullPath)
|
|
|
|
let classNames = {}
|
|
const { css } = await postcss([
|
|
postcssSass(),
|
|
cssModules({
|
|
getJSON(_, json) {
|
|
classNames = json
|
|
return classNames
|
|
},
|
|
...options,
|
|
}),
|
|
]).process(source, {
|
|
from: cssFullPath,
|
|
map: false,
|
|
})
|
|
|
|
return {
|
|
css,
|
|
classNames,
|
|
}
|
|
}
|
|
|
|
const srcDir = resolve(__dirname, "../../src")
|
|
|
|
const plugin = (options: Options = {}): Plugin => ({
|
|
name: PLUGIN_NAME,
|
|
async setup(build) {
|
|
const memfs = new Map<string, string>()
|
|
const FS_NAMESPACE = PLUGIN_NAME + "-fs"
|
|
|
|
build.onResolve({ filter: /\.modules?\.s?css$/, namespace: "file" }, async args => {
|
|
const res = await build.resolve(args.path, {
|
|
kind: "import-statement",
|
|
resolveDir: args.resolveDir,
|
|
})
|
|
|
|
// This is just the unique ID for this CSS module. We use a relative path to make it easier to debug.
|
|
const path = relative(srcDir, res.path)
|
|
|
|
return {
|
|
path,
|
|
namespace: PLUGIN_NAME,
|
|
pluginData: {
|
|
realPath: res.path,
|
|
},
|
|
}
|
|
})
|
|
|
|
build.onResolve({ filter: /^@css-modules\/.*/ }, ({ path }) => ({
|
|
path,
|
|
namespace: FS_NAMESPACE,
|
|
}))
|
|
|
|
build.onLoad({ filter: /./, namespace: FS_NAMESPACE }, ({ path }) => ({
|
|
contents: memfs.get(path)!,
|
|
loader: "css",
|
|
}))
|
|
|
|
build.onLoad({ filter: /./, namespace: PLUGIN_NAME }, async args => {
|
|
const tmpFilePath = "@css-modules/" + args.path
|
|
const { classNames, css } = await buildCSSModule(args.pluginData.realPath, options)
|
|
memfs.set(tmpFilePath, css)
|
|
|
|
return {
|
|
contents:
|
|
`import ${JSON.stringify(tmpFilePath)};\n` +
|
|
`module.exports = ${JSON.stringify(classNames)};`,
|
|
loader: "js",
|
|
}
|
|
})
|
|
},
|
|
})
|
|
|
|
export { plugin as cssModules }
|