我们提供安全,免费的手游软件下载!
众所周知,在vue中使用scoped可以避免父组件的样式渗透到子组件中。使用了scoped后会给html增加自定义属性
data-v-x
,同时会给组件内CSS选择器添加对应的属性选择器
[data-v-x]
。这篇我们来讲讲vue是如何给CSS选择器添加对应的属性选择器
[data-v-x]
。注:本文中使用的vue版本为
3.4.19
,
@vitejs/plugin-vue
的版本为
5.0.4
。
关注公众号:【前端欧阳】,给自己一个进阶vue的机会
我们先来看个demo,代码如下:
hello world
经过编译后,上面的demo代码就会变成下面这样:
hello world
从上面的代码可以看到在div上多了一个
data-v-c1c19b25
自定义属性,并且css的属性选择器上面也多了一个
[data-v-c1c19b25]
。
可能有的小伙伴有疑问,为什么生成这样的代码就可以避免样式污染呢?
.block[data-v-c1c19b25]
:这里面包含两个选择器。
.block
是一个类选择器,表示class的值包含
block
。
[data-v-c1c19b25]
是一个属性选择器,表示存在
data-v-c1c19b25
自定义属性的元素。
所以只有class包含
block
,并且存在
data-v-c1c19b25
自定义属性的元素才能命中这个样式,这样就能避免样式污染。
并且由于在同一个组件里面生成的
data-v-x
值是一样的,所以在同一组件内多个html元素只要class的值包含
block
,就可以命中
color: red
的样式。
接下来我将通过debug的方式带你了解,vue是如何在css中生成
.block[data-v-c1c19b25]
这样的属性选择器。
还是一样的套路启动一个debug终端。这里以
vscode
举例,打开终端然后点击终端中的
+
号旁边的下拉箭头,在下拉中点击
Javascript Debug Terminal
就可以启动一个
debug
终端。
假如
vue
文件编译为
js
文件是一个毛线团,那么他的线头一定是
vite.config.ts
文件中使用
@vitejs/plugin-vue
的地方。通过这个线头开始
debug
我们就能够梳理清楚完整的工作流程。
我们给上方图片的
vue
函数打了一个断点,然后在
debug
终端上面执行
yarn dev
,我们看到断点已经停留在了
vue
函数这里。然后点击
step into
,断点走到了
@vitejs/plugin-vue
库中的一个
vuePlugin
函数中。我们看到简化后的
vuePlugin
函数代码如下:
function vuePlugin(rawOptions = {}) {
return {
name: "vite:vue",
// ...省略其他插件钩子函数
transform(code, id, opt) {
// ..
}
};
}
@vitejs/plugin-vue
是作为一个
plugins
插件在vite中使用,
vuePlugin
函数返回的对象中的
transform
方法就是对应的插件钩子函数。vite会在对应的时候调用这些插件的钩子函数,vite每解析一个模块都会执行一次
transform
钩子函数。更多vite钩子相关内容
查看官网
。
我们这里只需要看
transform
钩子函数,解析每个模块时调用。
由于解析每个文件都会走到
transform
钩子函数中,但是我们只关注
index.vue
文件是如何解析的,所以我们给
transform
钩子函数打一个条件断点。如下图:
然后点击Continue(F5),
vite
服务启动后就会走到
transform
钩子函数中打的断点。我们可以看到简化后的
transform
钩子函数代码如下:
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
);
}
}
}
首先调用
parseVueRequest
函数解析出当前要处理的文件的
filename
和
query
,在debug终端来看看此时这两个的值。如下图:
从上图中可以看到
filename
为当前处理的vue文件路径,
query
的值为空数组。所以此时代码会走到
transformMain
函数中。
transformMain
函数
将断点走进
transformMain
函数,在我们这个场景中简化后的
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("\n");
return {
code: resolvedCode,
};
}
我们在
通过debug搞清楚.vue文件怎么变成.js文件
文章中已经深入讲解过
transformMain
函数了,所以这篇文章我们不会深入到
transformMain
函数中使用到的每个函数中。
首先调用
createDescriptor
函数根据当前vue文件的code代码字符串生成一个
descriptor
对象,简化后的
createDescriptor
函数代码如下:
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 };
}
首先调用
compiler.parse
方法根据当前vue文件的code代码字符串生成一个
descriptor
对象,此时的
descriptor
对象主要有三个属性
template
、
scriptSetup
、
style
,分别对应的是vue文件中的
模块、
模块、