最近在写一个vue项目,无意间出现了这样一个小问题。
一个单页面应用,有视图a和a.css,视图b和b.css。然后经过webpack打包的时候,a.css b.css都会一次性加载进来,这样如果第一次只显示视图a,b.css中的样式和a.css中有冲突的话,就会影响视图a的显示。
其实这个问题很简单嘛,就是给a.css和b.css各自一个命名空间就好啦,保证css中的class名称不会重复。
emmmmm... 感觉有点太粗暴?
还是想了解一下有什么官方解决方案,然后就进行了如下探究。
方法一:vue的style scope
给 <style>
标签加入 scope
属性,这样它的 CSS 只作用于当前组件中的元素。vue通过PostCss来实现这样的功能。
其原理在于为style scope中的class定义属性选择器,同时在使用该class的标签中添加该属性。因此,style scope中定义的class只能在当前组件中使用。 例如:
// 转化前
<style scoped>
.example {
color: red;
}
</style>
<template>
<div class="example">hi</div>
</template>
// 转化后
<style>
.example[data-v-f3f3eg9] {
color: red;
}
</style>
<template>
<div class="example" data-v-f3f3eg9>hi</div>
</template>
仔细想想,这种方法和之前我们自己的简单粗暴的方法好像并没有什么本质上的区别。
并且存在一些缺点:
- 由于本质是使用属性选择器,因此性能不太好
- 如果在style scope中定义的是class 或者id其实还好。要是定义p标签这种,会特别特别慢。
- 如果存在.a .b这样的后代类定义,那么将要去匹配.a的每一个.b后代,并给.b加属性。如果.a的后代是一个递归子组件,则会非常影响性能。
方法二:CSS modules
给 <style>
标签加入 module
属性。
配置:想要使用css modules需要在webpack配置文件中配置:
options: {
cssModules: {
// 设置命名格式
localIdentName: '[name]---[local]---[hash:base64:5]',
camelCase: true
}
}
此时在经过webpack编译时,会将css module中的class按照预设的命名规则生成统一的名字,用于区分使用。 例如:
// components/Button.css
.normal { /* normal 相关的所有样式 */ }
.disabled { /* disabled 相关的所有样式 */ }
/* components/Button.js */
import styles from './Button.css';
console.log(styles);
buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`
// 转化后生成的html为
<button class="button---normal---abc53">Submit</button>
其中 button---normal---abc53 是 CSS Modules 按照 webpack中的 localIdentName 规则自动生成的 class 名。其中的 abc53 是按照给定算法生成的序列码。这样可以保证css名称的一致性。
其实这种方法本质上和上述方法一致,保证css中class的唯一性,全部打包到一个css文件中,在加载的时候一次性全部加载。
方法三:webpack按需加载
感觉最完美的方法应该是webpack按需加载css
但是想要抽出vue中的css的话需要使用ExtractTextPlugin插件,此插件无法实现css的按需加载