scoped
Vue scoped通过在DOM结构以及CSS样式上增加唯一不重复的标记data-v-hash的方式,确保样式的唯一性,实现样式私有化模块化的效果。
这个工作通过PostCSS转译实现。
scoped的三条渲染规则:
- 给HTML的DOM节点加一个不重复的data属性(形如data-v-114514)以表示唯一性。
- 在每句CSS选择器末尾(编译后生成的CSS语句)加一个当前组件的data属性选择器(如[data-v-114514])来私有化样式。
- 如果组件内部包含其他组件,只会给其他组件的最外层标签加上当前组件的data属性
![增加了data属性的DOM](/img/loading.gif)
![增加了属性选择器的CSS](/img/loading.gif)
样式穿透
主要用于修改常用Vue组件库(例如Element、Vant、Ant Design等)的默认自带样式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <template> <main> <el-input placeholder="测试" class="ipt"></el-input> </main> </template>
<style scoped lang="scss"> .ipt { width: 300px; margin: 100px 400px; // 这样做因为scoped的机制,是无法应用样式的 .el-input__inner { background-color: green; } :deep(.el-input__inner) { background-color: red; } } </style>
|
![属性选择器被放到.ipt后面而非.el-input__inner后面](/img/loading.gif)
源码
在Vue源码(/package/compiler-sfc/src/compileStyle.ts
)中可以看到scoped和样式穿透的源码。
compiler-sfc用于处理.Vue单文件组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| export function doCompileStyle( options: SFCAsyncStyleCompileOptions ): SFCStyleCompileResults | Promise<SFCStyleCompileResults> {
const plugins = (postcssPlugins || []).slice() plugins.unshift(cssVarsPlugin({ id: shortId, isProd })) if (trim) { plugins.push(trimPlugin()) } if (scoped) { plugins.push(scopedPlugin(longId)) } }
|
在/package/compiler-sfc/src/style/pluginScoped.ts
中可以看到对PostCSS的使用。
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
|
const scopedPlugin: PluginCreator<string> = (id = '') => { const keyframes = Object.create(null) const shortId = id.replace(/^data-v-/, '')
return { postcssPlugin: 'vue-sfc-scoped', Rule(rule) { processRule(id, rule) }, AtRule(node) { if ( /-?keyframes$/.test(node.name) && !node.params.endsWith(`-${shortId}`) ) { keyframes[node.params] = node.params = node.params + '-' + shortId } }, OnceExit(root) { if (Object.keys(keyframes).length) { root.walkDecls(decl => { if (animationNameRE.test(decl.prop)) { decl.value = decl.value .split(',') .map(v => keyframes[v.trim()] || v.trim()) .join(',') } if (animationRE.test(decl.prop)) { decl.value = decl.value .split(',') .map(v => { const vals = v.trim().split(/\s+/) const i = vals.findIndex(val => keyframes[val]) if (i !== -1) { vals.splice(i, 1, keyframes[vals[i]]) return vals.join(' ') } else { return v } }) .join(',') } }) } } } }
const processedRules = new WeakSet<Rule>()
function processRule(id: string, rule: Rule) { if ( processedRules.has(rule) || (rule.parent && rule.parent.type === 'atrule' && /-?keyframes$/.test((rule.parent as AtRule).name)) ) { return } processedRules.add(rule) rule.selector = selectorParser(selectorRoot => { selectorRoot.each(selector => { rewriteSelector(id, selector, selectorRoot) }) }).processSync(rule.selector) }
|