-
Notifications
You must be signed in to change notification settings - Fork 4
/
index.js
145 lines (116 loc) · 5.7 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
const { transform } = require('lightningcss');
const { createHash } = require('crypto');
const fs = require("fs/promises");
const { dirname, join } = require("path");
/**
* A generic cssModules plugin for esbuild based on lightningcss
*
* @param {Object=} options
* @param {RegExp=} options.includeFilter
* @param {RegExp=} options.excludeFilter
* @param {import("lightningcss").TransformOptions["visitor"]=} options.visitor
* @param {import("lightningcss").TransformOptions["targets"]=} options.targets
* @param {import("lightningcss").TransformOptions["drafts"]=} options.drafts
* @param {import("lightningcss").TransformOptions["customAtRules"]=} options.customAtRules
* @param {import("lightningcss").CSSModulesConfig["pattern"]=} options.cssModulesPattern
* @param {import("lightningcss").CSSModulesConfig=} options.cssModules
* @return {import("esbuild").Plugin}
*/
const cssModules = (options = {}) => {
return {
name: "css-modules",
setup: ({onLoad, onResolve, initialOptions}) => {
const transpiledCssModulesMap = new Map()
onResolve({filter: /^css-modules:\/\//}, ({path}) => {
return {
namespace: "css-modules",
path,
}
})
onLoad({filter: /.*/, namespace: "css-modules"}, ({path}) => {
const {code, resolveDir} = transpiledCssModulesMap.get(path)
return {
contents: code,
loader: initialOptions.loader?.[".css"] ?? "css",
resolveDir,
}
})
onLoad({filter: options.includeFilter ?? /\.module\.css$/}, async ({path}) => {
if (options.excludeFilter?.test(path)) {
return;
}
const rawCssBuffer = await fs.readFile(path)
const cssModules = { pattern: options.cssModulesPattern ?? `[hash]_[local]`, ...options.cssModules }
const { code, map, exports } = transform({
filename: path,
code: rawCssBuffer,
analyzeDependencies: false,
cssModules,
sourceMap: true,
targets: options.targets,
drafts: options.drafts,
visitor: options.visitor,
customAtRules: options.customAtRules,
// this way the correct relative path for the source map will be generated ;)
projectRoot: join(initialOptions.absWorkingDir || process.cwd(), initialOptions.outdir)
});
if (!exports) {
return;
}
const id = "css-modules:\/\/" + createHash("sha256").update(path).digest('base64url') + '.css'
const finalCode = code.toString("utf8") + `/*# sourceMappingURL=data:application/json;base64,${map.toString("base64")} */`;
transpiledCssModulesMap.set(
id,
{ code: finalCode, resolveDir: dirname(path) }
)
const quote = JSON.stringify;
const escape = (string) => JSON.stringify(string).slice(1, -1)
let contents = "";
/** @type {Map<string, string>} */
const dependencies = new Map()
/** @param {String} path */
const importDependency = (path) => {
if (dependencies.has(path)) {
return dependencies.get(path)
}
const dependenciesName = `dependency_${dependencies.size}`
// prepend dependeny to to the contents
contents = `import ${dependenciesName} from ${quote(path)}\n` + contents;
dependencies.set(path, dependenciesName)
return dependenciesName;
}
contents += `import ${quote(id)}\n`;
contents += `export default {`;
for (const [cssClassReadableName, cssClassExport] of Object.entries(exports)) {
let compiledCssClasses = `"${escape(cssClassExport.name)}`
if (cssClassExport.composes) {
for (const composition of cssClassExport.composes) {
switch (composition.type) {
case "local":
compiledCssClasses += " " + escape(composition.name)
break;
case "global":
compiledCssClasses += " " + escape(composition.name)
break;
case "dependency":
compiledCssClasses += ` " + ${importDependency(composition.specifier)}[${quote(composition.name)}] + "`
break;
}
}
}
compiledCssClasses += `"`
contents += `${quote(cssClassReadableName)}:${compiledCssClasses},`
}
contents += "}"
// https://github.com/evanw/esbuild/issues/2943#issuecomment-1439755408
const emptyishSourceMap = "data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIiJdLCJtYXBwaW5ncyI6IkEifQ==";
contents += `\n//# sourceMappingURL=${emptyishSourceMap}`
return {
contents,
loader: "js",
}
})
}
}
}
module.exports = { cssModules }