vue3如何避免样式污染的方法示例 |
本文标签:vue3?样式污染 众所周知,在vue中使用scoped可以避免父组件的样式渗透到子组件中 。使用了scoped后会给html增加自定义属性 先看个demo 代码如下: <template> <div class="block">hello world</div> </template> <style scoped> .block { color: red; } </style> 经过编译后,上面的demo代码就会变成下面这样: <template> <div data-v-c1c19b25 class="block">hello world</div> </template> <style> .block[data-v-c1c19b25] { color: red; } </style> 从上面的代码可以看到在div上多了一个 那有人就会好奇,为什么生成这样的代码就可以避免样式污染呢?
所以只有class包含 接下来我将通过debug的方式带你了解,vue是如何在css中生成.block[data-v-c1c19b25]这样的属性选择器 。 @vitejs/plugin-vue还是一样的套路启动一个debug终端 。这里以 假如 vuePlugin函数我们给上方图片的 function vuePlugin(rawOptions = {}) { return { name: "vite:vue", // ...省略其他插件钩子函数 transform(code, id, opt) { // .. } }; }
我们这里只需要看 由于解析每个文件都会走到 然后点击Continue(F5), function transform(code, id, opt) { const { filename, query } = parseVueRequest(id); if (!query.vue) { return transformMain( code, filename, options.value, this, ssr, customElementFilter.value(filename) ); } else { const descriptor = getDescriptor(filename); if (query.type === "style") { return transformStyle( code, descriptor, Number(query.index || 0), options.value ); } } } 首先调用 从上图中可以看到 transformMain函数将断点走进 async function transformMain(code, filename, options) { const { descriptor } = createDescriptor(filename, code, options); const { code: templateCode } = await genTemplateCode( descriptor // ...省略 ); const { code: scriptCode } = await genScriptCode( descriptor // ...省略 ); const stylesCode = await genStyleCode( descriptor // ...省略 ); const output = [scriptCode, templateCode, stylesCode]; let resolvedCode = output.join(" "); return { code: resolvedCode, }; } 首先调用 const cache = new Map(); function createDescriptor( filename, source, { root, isProduction, sourceMap, compiler, template } ) { const { descriptor, errors } = compiler.parse(source, { filename, sourceMap, templateParseOptions: template?.compilerOptions, }); const normalizedPath = slash(path.normalize(path.relative(root, filename))); descriptor.id = getHash(normalizedPath + (isProduction ? source : "")); cache.set(filename, descriptor); return { descriptor, errors }; } 首先调用 然后调用 import { createHash } from "node:crypto"; function getHash(text) { return createHash("sha256").update(text).digest("hex").substring(0, 8); } 从上面的代码可以看出id是根据vue文件的路径调用node的 然后在 接着在 编译后的render函数如下图: 从上图中可以看到template模块已经编译成了render函数 编译后的js代码如下图: 从上图中可以看到script模块已经编译成了一个名为 编译后的style代码如下图: 从上图中可以看到style模块已经编译成了一个import语句 。 最后就是使用换行符 想必细心的同学已经发现有地方不对啦,这里的style模块编译后是一条import语句,并不是真正的css代码 。这条import语句依然还是import导入的
还记得前面讲过的 第二次执行transform钩子函数当在浏览器中执行vue文件编译后的js文件时会触发
function transform(code, id, opt) { const { filename, query } = parseVueRequest(id); if (!query.vue) { return transformMain( code, filename, options.value, this, ssr, customElementFilter.value(filename) ); } else { const descriptor = getDescriptor(filename); if (query.type === "style") { return transformStyle( code, descriptor, Number(query.index || 0), options.value ); } } } 由于此时的 function getDescriptor(filename) { const _cache = cache; if (_cache.has(filename)) { return _cache.get(filename); } } 我们在第一次执行 由于 transformStyle函数接着将断点走进 async function transformStyle(code, descriptor, index, options) { const block = descriptor.styles[index]; const result = await options.compiler.compileStyleAsync({ ...options.style, filename: descriptor.filename, id: `data-v-${descriptor.id}`, source: code, scoped: block.scoped, }); return { code: result.code, }; } 从上面的代码可以看到 在调用
其中的
直到进入 @vue/compiler-sfc接着将断点走进 function compileStyleAsync(options) { return doCompileStyle({ ...options, isAsync: true, }); } 从上面的代码可以看到实际干活的是 doCompileStyle函数接着将断点走进 import postcss from "postcss"; function doCompileStyle(options) { const { filename, id, scoped = false, postcssOptions, postcssPlugins, } = options; const source = options.source; const shortId = id.replace(/^data-v-/, ""); const longId = `data-v-${shortId}`; const plugins = (postcssPlugins || []).slice(); if (scoped) { plugins.push(scopedPlugin(longId)); } const postCSSOptions = { ...postcssOptions, to: filename, from: filename, }; let result; try { result = postcss(plugins).process(source, postCSSOptions); return result.then((result) => ({ code: result.css || "", // ...省略 })); } catch (e: any) { errors.push(e); } } 在 其中的 接着就是判断 最后就是执行 可能有的小伙伴对
在我们这里主要就是用到了 在执行 从上图可以看到此时的css代码还是和我们源代码是一样的,并没有css选择属性选择器 scopedPlugin插件
const scopedPlugin = (id = "") => { return { postcssPlugin: "vue-sfc-scoped", Rule(rule) { processRule(id, rule); }, // ...省略 }; }; 这里的id就是我们在 在我们这个场景中只需要关注 我们这里需要在使用了scoped后给css选择器添加对应的属性选择器 给 从上图中可以看到此时 processRule函数将断点走进 import selectorParser from "postcss-selector-parser"; function processRule(id: string, rule: Rule) { rule.selector = selectorParser((selectorRoot) => { selectorRoot.each((selector) => { rewriteSelector(id, selector, selectorRoot); }); }).processSync(rule.selector); } 前面我们讲过
在我们这里 我们接下来看 为什么这里需要去遍历呢? 答案是css选择器可以这样写: 在each遍历中会调用 rewriteSelector函数将断点走进 function rewriteSelector(id, selector) { let node; const idToAdd = id; selector.each((n) => { node = n; }); selector.insertAfter( node, selectorParser.attribute({ attribute: idToAdd, value: idToAdd, raws: {}, quoteMark: `"`, }) ); } 在 在这里 比如我们这里要执行的 我们再来看看要插入的节点, 所以这里就是在 我们在debug终端来看看执行 将断点逐层走出,直到 原来 总结这篇文章我们讲了当使用scoped后,vue是如何给组件内CSS选择器添加对应的属性选择器
到此这篇关于vue3是如何避免样式污染的的文章就介绍到这了,更多相关vue3 样式污染内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持! |