整体思路
识别 :local
包裹的类名,将其进行hash化,保证不污染全局
将 :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:
整个转换的过程是:
具体代码这里 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; } 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(); }); }); root.walkAtRules(/keyframes$/i, (atRule) => { const localMatch = /^:local\((.*)\)$/.exec(atRule.params);
if (localMatch) { atRule.params = exportScopedName(localMatch[1]); } }); 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