css modules原理学习

整体思路

  1. 识别 :local 包裹的类名,将其进行hash化,保证不污染全局

  2. :local 包裹的类名放在 :export 中,这个是icss的规范,算是css规范的超集。这样就相当于 module.exports ,外面使用时,可以通过 styles.xxx 的方式来拿到hash后的类名。

    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
    .guang {
    color: blue;
    }
    ._input_css_amSA5i__dong{
    color: green;
    }
    ._input_css_amSA5i__dongdong{
    color: green;
    }
    ._input_css_amSA5i__dongdongdong{
    color: red;
    }
    @keyframes _input_css_amSA5i__guangguang {
    from {
    width: 0;
    }
    to {
    width: 100px;
    }
    }

    :export {
    dong: _input_css_amSA5i__dong;
    dongdong: _input_css_amSA5i__dongdong;
    dongdongdong: _input_css_amSA5i__dongdongdong _input_css_amSA5i__dong _input_css_amSA5i__dongdong;
    guangguang: _input_css_amSA5i__guangguang;
    }

AST 解析

前面提高的,识别 :local()global() 标记,并且对其进行hash化,就是通过AST实现的。

这些可以通过 astexplorer.net 来可视化的查看转义后的AST:

Untitled.png

整个转换的过程是:

具体代码这里 https://github.com/QuarkGluonPlasma/postcss-plugin-exercize ,或者展开下面:

代码
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
const selectorParser = require("postcss-selector-parser");
function generateScopedName(name) {
const randomStr = Math.random().toString(16).slice(2);
return `_${randomStr}__${name}`;
};
const plugin = (options = {}) => {
return {
postcssPlugin: "my-postcss-modules-scope",
Once(root, helpers) {
const exports = {};
function exportScopedName(name) {
const scopedName = generateScopedName(name);

exports[name] = exports[name] || [];

if (exports[name].indexOf(scopedName) < 0) {
exports[name].push(scopedName);
}
return scopedName;
}
function localizeNode(node) {
switch (node.type) {
case "selector":
node.nodes = node.map(localizeNode);
return node;
case "class":
return selectorParser.className({
value: exportScopedName(
node.value,
node.raws && node.raws.value ? node.raws.value : null
),
});
case "id": {
return selectorParser.id({
value: exportScopedName(
node.value,
node.raws && node.raws.value ? node.raws.value : null
),
});
}
}
}
function traverseNode(node) {
switch (node.type) {
case "root":
case "selector": {
node.each(traverseNode);
break;
}
case "id":
case "class":
exports[node.value] = [node.value];
break;
case "pseudo":
if (node.value === ":local") {
const selector = localizeNode(node.first, node.spaces);

node.replaceWith(selector);

return;
}
}
return node;
}
// 处理 :local 选择器
root.walkRules((rule) => {
const parsedSelector = selectorParser().astSync(rule);
rule.selector = traverseNode(parsedSelector.clone()).toString();
rule.walkDecls(/composes|compose-with/i, (decl) => {
const localNames = parsedSelector.nodes.map((node) => {
return node.nodes[0].first.first.value;
})
const classes = decl.value.split(/\s+/);
classes.forEach((className) => {
const global = /^global\(([^)]+)\)$/.exec(className);

if (global) {
localNames.forEach((exportedName) => {
exports[exportedName].push(global[1]);
});
} else if (Object.prototype.hasOwnProperty.call(exports, className)) {
localNames.forEach((exportedName) => {
exports[className].forEach((item) => {
exports[exportedName].push(item);
});
});
} else {
throw decl.error(
`referenced class name "${className}" in ${decl.prop} not found`
);
}
});

decl.remove();
});
});
// 处理 :local keyframes
root.walkAtRules(/keyframes$/i, (atRule) => {
const localMatch = /^:local\((.*)\)$/.exec(atRule.params);

if (localMatch) {
atRule.params = exportScopedName(localMatch[1]);
}
});
// 生成 :export rule
const exportedNames = Object.keys(exports);

if (exportedNames.length > 0) {
const exportRule = helpers.rule({ selector: ":export" });

exportedNames.forEach((exportedName) =>
exportRule.append({
prop: exportedName,
value: exports[exportedName].join(" "),
raws: { before: "\n " },
})
);
root.append(exportRule);
}
},
};
};
plugin.postcss = true;
module.exports = plugin;

参考文档

bookmark

link_preview

bookmark