实际 compileTemplate 函数在处理内容时, 编译函数使用的是 query 中的 compiler 或 vue-template-compiler, 后者会将模板文本转换成为 JavaScript 渲染函数, 大致如下:
从HTML模版转换为AST(虚拟语法树)
AST优化,处理静态模版与动态模板
生成JS函数,用于在运行时运行时生成纯HTML
代码分别对应:
// vue-template-compiler/build.js/createCompilerCreator var ast = parse(template.trim(), options) optimize(ast, options) var code = generate(ast, options)
先前我们的组件ID在 parse 阶段解析开始标签时就会被推入内部储存的数据结构中:
function elementToOpenTagSegments (el, state) { var segments = [{ type: RAW, value: ("<" + (el.tag)) }] // _scopedId if (state.options.scopeId) { segments.push({ type: RAW, value: (" " + (state.options.scopeId)) }) } segments.push({ type: RAW, value: ">" }) return segments }
先前我们的HTML模板 <div></div> 中开始标签会被转换成如下数据结构:
[ { type: RAW, value: '<div' }, { type: RAW, value: 'class=lionad' }, { type: RAW, value: 'data-v-xxxxxx' }, { type: RAW, value: '>' }, ]
样式模板处理
与 HTML Template 解析的过程类似, 通过 Webpack 将样式模板转交 stylePostLoader 进行处理, 处理逻辑主要引用了 @vue/component-compiler-utils 中的 compileStyle 部分, 后者对样式模板进行解析的过程中, 将会对含 scoped 标记的模板引入插件 stylePlugins/scoped.js, scoped.js 将 data-v-xxxxxx 添加到选择器末尾的过程如下:
selectors.each((selector) => { selector.each((n) => { if (n.value === '::v-deep' || n.value === '>>>' || n.value === '/deep/') { return false; } }); selector.insertAfter(node, selectorParser.attribute({ attribute: id })) })
题外话, 通过以上代码, 我们发现当当前处理到三种特定类型选择器会终止循环, 停止将 data-v-xxx 添加到选择器末尾:
伪类 ::v-deep
选择器 >>>
选择器 /deep/
我们可以利用这个特征, 在组件中写样式穿透, 即内部组件影响外部组件样式 (ε=ε=ε=┏(゜ロ゜;)┛ 主动样式污染), 当然这在特定的情境下是有用的, 比如当我们想主动覆盖第三方UI组件框架的样式, 却不想引入新的CSS文件, 或不想写非 Scoped CSS 模板的时候.