-
Notifications
You must be signed in to change notification settings - Fork 2
/
css-module.mjs
58 lines (40 loc) · 1.58 KB
/
css-module.mjs
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
// This loader provides a basic facsimile of CSS Modules intended for testing.
// Use something like esbuild to handle this in production.
import postcss from 'postcss';
import { stripExtras } from './parse-filename.mjs';
export async function resolve(specifier, ctx, nextResolve) {
const nextResult = await nextResolve(specifier);
if (!stripExtras(specifier).endsWith('.module.css')) return nextResult;
return {
...ctx,
format: 'cssmodule',
url: nextResult.url,
};
}
export async function load(url, ctx, nextLoad) {
const nextResult = await nextLoad(url, ctx);
if (ctx.format !== 'cssmodule') return nextResult;
const rawSource = '' + nextResult.source;
const parsed = parseCssToObject(rawSource);
return {
format: 'json',
source: JSON.stringify(parsed),
};
}
function parseCssToObject(rawSource) {
const output = new Map(); // Map is best for mutation
const postcssResult = postcss.parse(rawSource).toJSON();
for (const rule of postcssResult.nodes) parseCssToObjectRecursive(rule, output);
return Object.fromEntries(output);
}
function parseCssToObjectRecursive(node, output) {
if (node.type === 'rule') {
const classnames = node.selector.match(SELECTOR_TO_CLASS_NAME_RGX) ?? new Array();
for (const classname of classnames) output.set(classname, classname);
}
if (node.nodes) for (const child of node.nodes) parseCssToObjectRecursive(child, output);
}
/**
* Grab any classnames from a selector, which may have non-classnames anywhere within the selector.
*/
const SELECTOR_TO_CLASS_NAME_RGX = /(?<=\.)-?[_a-zA-Z]+[_a-zA-Z0-9-]*/g;