前言
由于最近使用到esbuild
这个打包工具编写插件,而我的插件需要编写样式。在实际的运用过程中,我发现插件的样式类名可能和被使用项目中的一些类名发生冲突。这个时候我就想借鉴一下 vue 中出现的样式类scoped
的概念。
具体思路
首先呢,要想实现样式类的隔离
那么就要了解下属性选择器。下面就是很简单的一句解释。
CSS 属性选择器匹配那些具有特定属性或属性值的元素。
其实呢就是 dom 上有个属性然后配合着标签或者类名来实现的。例如:
1 2 3 4 5 6 7 8 9 10
| a[title] { color: purple; }
a[href="https://example.org"] { color: green; }
|
那么 vue 就相当于你自定义一个属性data-v-sjdgdf
然后类名后面跟上这个属性就能实现样式隔离
实际操作
esbuild
只是一个打包工具,所以我们第一步就是要获取插件中的 dom 元素,由于我是采用了 jsx 的方式编写插件,具体可以参考我的文章:基于 esbuild 搭建组件开发框架**。**第二步就是处理编写的 css 样式文件,然后将类名后面加上随机生成的字符串。
随机字符串
1 2 3 4 5 6 7 8 9
| const generateString = (len = 6) => { const characters = `abcdefghijklmnopqrstuvwxyz0123456789`; let result = ""; for (let i = 0; i < len; i++) { result += characters.charAt(Math.floor(Math.random() * characters.length)); } return result; }; const generateStr = generateString();
|
生成一个 6 位长度的随机字符串。
Dom 元素打包编辑
我原先是使用 babel 来转义 jsx 的语法的。所以这里不介绍涉及此处的插件,具体的看我前面提到的文章那里面有缺失的插件。
1
| pnpm install @babel/types -w -D
|
这个是读取 dom 节点的插件。然后我们编写 dom 属性添加插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const types = require("@babel/types"); const addProperty = () => ({ visitor: { JSXOpeningElement(path) { const { name, attributes } = path.node; let flag = attributes.some( (attr) => attr.type === "JSXAttribute" && attr.name.name === "className" );
if (flag) { path.node.attributes.push( types.jsxAttribute( types.jsxIdentifier(`data-v-${generateStr}`), types.stringLiteral("") ) ); } }, }, });
module.exports.addProperty = addProperty;
|
代码很简单,里面也有注释,就不详细解释了。
然后加入到之前写的 jsxTransform 解析插件里面去,具体放在此处
1 2 3 4 5 6 7 8 9 10
| ...... const { addProperty } = require("./cssAttribute"); build.onLoad({ filter: /(?:\.jsx|\.tsx)$/ }, async (args) => { const jsx = await fs.promises.readFile(args.path, "utf8"); const result = babel.transformSync(jsx, { plugins: [plugin, addProperty()], presets: [presetEnv, presetTypescript], filename: args.path }); return { contents: result.code }; }); ......
|
这里完成后重新打包文件就能看到页面上的 dom 节点处会出现 data-v-随机字符串。
css 样式文件处理
同样的借助插件解析 css 文件
1
| pnpm install postcss -w -D
|
然后编写如下的插件,也是很简单的插件内容
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
| const postcss = require("postcss"); const cssScopedPlugin = () => ({ name: "css-scoped-plugin", setup(build) { const fs = require("fs"); const plugin = { postcssPlugin: "css-parser", Rule(node) { node.selectors = node.selectors.map( (selector) => `${selector}[data-v-${generateStr}]` ); }, };
build.onLoad({ filter: /(?:\.css|\.scss)$/ }, async (args) => { const cssFile = await fs.promises.readFile(args.path, "utf8"); let result = await postcss([plugin]).process(cssFile, { from: undefined, }); return { contents: result.css, loader: "css", }; }); }, });
|
之后载入到 esbuild 的文件插件属性中,下面是部分代码
1 2 3 4
| const { cssScopedPlugin } = require("../plugins/cssAttribute"); context({ plugins: [cssScopedPlugin(), cssPlugin.sassPlugin(), svgBuilder()], });
|
这里我放在了 sass 文件解析插件的前面,因为我发现在后面貌似不起作用。可能是我的项目有 BUG 啥的。
到这里就完成了 css 样式隔离的问题,实际效果如下
![6dddfa9583f0cac9b69fb.png]()