6除了JavaScript外,在打包方面另一个重要的工作就是样式处理
1.分离样式文件
我们可以利用style-loader和css-loader,通过JS引用CSS的方式打包样式,可以更清晰地描绘模块间的依赖关系。但是很多时候,我们的CSS样式不是单独写在一个文件里的,而是通过style标签通过类的形式引入的。一般来说,我们希望生产环境下样式存在于CSS文件中而不是style标签中,因为文件的形式更有利于客户端进行缓存。webpack社区有专门的插件:extract-text-webpack-plugin(适用于webpack4之前的版本)和mini-css-extract-plugin(适用于webpack4及以上的版本),他们的作用就是专门用于提取样式到css文件
1.1 extract-text-webpack-plugin
我们通过npm安装之后,来看一下是如何使用插件的
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
entry: './app.js',
output: {
filename: 'bundle.js'
},
mode: 'development',
module: {
rules: [
{
test: /\.css$/,
use:ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader'
})
}
]
},
plugins:[
new ExtractTextPlugin('bundle.css')
]
}
在module.rules中我们设置了处理CSs文件的规则,其中的use字段没有直接传入loader,因为并没有这个功能的loader,我们要使用的是插件extract-text-webpack-plugin,因此use使用了插件的extract方法包了一层。内部的fallback属性用于指定当插件无法提取样式时采用的loader(具体意思后面会介绍),use(extract里面的)用于指定在提取样式之前采用哪些loader来进行预先处理,这里就用了css-loader来处CSS文件的@import和url()函数。除此之外,还要在webpack的plugins配置中添加该插件,并传入提取后的资源文件名。
简要介绍一下webpack的plugins配置。plugins用于接收一个插件数组,我们可以使用webpack内部的插件,也可以加载外部的插件。webpack为插件提供了各种API,使其可以在打包的各个环节添加一些额外任务。
换句话说,我们可以在webpack的plugins配置中注册插件,然后在module.rules里面检测到相应的文件类型,然后use这个插件,这个插件本身会用到其他loader
1.2 多样式文件的处理(多入口)
样式的提取是以资源入口开始的整个chunk为单位的(chunk:对一组有依赖关系的模块的封装)。假设我们的应用从index.js开始一层层引入了几百个模块,也许其中许多模块都引入了各自的样式,但是最终打包出来的文件中只有一个CSS文件,因为他们都来自同一个入口模块
上一个例子我们将bundle.css作为文件名传给了extract-text-webpack-plugin,但当工程有多个入口时就会发生重名问题,我们要像动态配置output.filename一样配置CSS文件
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
entry: {
foo:'./src/script/foo.js',
bar:'./src/script/bar.js',
},
output: {
filename: '[name].js'
},
mode: 'development',
module: {
rules: [
{
test: /\.css$/,
use:ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader'
})
}
]
},
plugins:[
new ExtractTextPlugin('[name].css')
]
}
无论是output.filename还是传入ExtractTextPlugin的name都是指chunk的名字,即entry中我们为每一个入口分配的名字(foo、bar)
可以看到不仅仅JS文件被打包出来了,CSS文件也被单独打包出来了,而且名字都是chunk的名字
1.2 mini-css-extract-plugin
mini-css-extract-plugin可以理解为extract-text-webpack-plugin的升级版,从webpack4开始官方就推荐使用该插件进行样式提取。
mini-css-extract-plugin最重要的特点就是支持按需加载CSS。举个例子,a.js通过import()函数异步加载b.js,b.js里面加载了style.css,那么css最终只能被同步加载。但是现在mini-css-extract-plugin会单独打包出一个0.css,这个css文件将有a.js通过动态插入link标签的方式加载,换句话来说,异步加载的CSs文件或者通过异步加载JS文件里的CSS文件,都会被打包进0.css这个文件里面,而extract-text-webpack-plugin做不到
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry:'./app.js',
output: {
filename: '[name].js'
},
mode: 'development',
module: {
rules: [
{
test: /\.css$/,
use:[
{
loader:MiniCssExtractPlugin.loader,
options:{
publicPath: '../'
}
},
'css-loader'
]
}
]
},
plugins:[
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css'
})
]
}
在配置上mini-css-extract-plugin和extract-text-webpack-plugin有以下几点不同
- loader的规则设置不同,并且mini-css-extract-plugin支持配置publicPath,用来指定异步CSS的加载路径
- 不需要设置fallback
- 在plugins设置中,除了指定同步加载的CSS资源名(filename),还要指定异步加载的CSS资源名(chunkFilename)。
打包的实际情况如下
2. 样式预处理
样式预处理是指我们开发时经常会用到一些样式预编译语言,比如SCSS、Less等,在打包时我们要将这些预编译语言转化为CSS
2.1 Sass与SCSS
类似于我们装babel-loader时还要安装bebel-core,loader本身只是编译核心库与Webpack的连接器,因此我们除了要安装sass-loader以外还要安装node-sass,node-sass是真正用来编译SCSS的
module: {
rules: [
{
test: /\.scss$/,
use:['style-loader',
{
loader:'css-loader',
options:{soucrceMap: true}
},
{
loader: 'sass-loader',
options:{sourceMap:true}
}
]
}
]
}
在使用sass-loader和css-loader时使用sourceMap,是因为这两个loader会改变源码的结构,因此开启sourceMap来查看源码
2.2 Less
Less同样也是CSS的一种扩展,也需要按照loader和其本身的编译模块
module: {
rules: [
{
test: /\.less$/,
use:['style-loader',
{
loader:'css-loader',
options:{soucrceMap: true}
},
{
loader: 'sass-loader',
options:{sourceMap:true}
}
]
}
]
}
3. postCSS
严格来说,PostCSS不能算是CSS的预编译器,他只是一个编译插件的容器。他的工作模式是接收样式源代码并交由编译插件处理,然后输出CSS,最后输出的CSS取决于使用了哪些插件。
直接按照postcss-loader即可
module: {
rules: [
{
test: /\.css$/,
use:['style-loader',
'css-loader',
'postcss-loader'
]
}
]
}
postcss-loader如果不和css-loader搭配使用,那么不建议使用CSS中的@import语句,否则会产生冗余代码。
除此之外,PostCSS要求必须有一个单独的配置文件,我们需要在根目录下创建一个postcss.config.js。我们正是在这个文件夹下添加特性的,让我们看一看可以用PostCSS来做哪些事情吧。
3.1 自动前缀
PostCSS一个最广泛的场景就是与AutoPrefixer结合,为CSS自动添加厂商前缀。
const autoprefixer = require('autoprefixer')
module.exports = {
plugins:[
new autoprefixer({
grid:true,
browsers:[
'> 1%',
'last 3 versions',
'android 4.2',
'ie 8'
]
})
]
}
我们可以在autoprefixer中添加需要支持的特性(如gird)以及兼容哪些浏览器(browsers),由于我们指定了gird:true,因此我们可以为gird特性添加IE前缀。
3.2 stylelint
stylelint是一个CSS的质量检测工具,类型eslint,我们可以添加各种规则来统一项目的代码风格
const stylelint = require('stylelint')
module.exports = {
plugins:[
stylelint({
config:{
rules:{
'declaration-no-important':true
}
}
})
]
}
这里我们添加了declaration-no-important这样一条规则,当我们的代码中出现了‘!important’时就会给出警告
3.3 CSSNext
PostCSS可以和CSSNext结合使用,让我们可以在应用中使用最新的CSS特性
const postcsscssnext = require('postcss-cssnext')
module.exports = {
plugins:[
postcsscssnext({
//指定支持的浏览器
browsers:[
'> 1%',
'last 2 versions'
]
})
]
}
配置好后,我们就可以使用CSSNext的特性了,PostCSS会将CSSNext翻译为浏览器能接受的属性和形式
4. CSS Modules
CSS Modules是近年来比较流行的一种开发模式,其理念就是将CSS模块化,让CSS也有模块的特点,具体如下:
- 每个CSS文件中的样式都有单独的作用域,不会和外界发生冲突
- 对CSS进行依赖管理,可以通过相对路径引入CSS文件
- 可以通过composes轻松复用其他CSS模块
使用CSS Modules不需要安装额外模块,只需要开启css-loader的modules配置项即可
module: {
rules: [
{
test: /\.css$/,
use:['style-loader',
{
loader: 'css-loader',
options:{
modules: true,
loaclIdentName: '[name]__[local]__[hash:base64:5] '
}
}
]
}
]
}
loaclIdentName配置项用来指明CSS代码中的类名会如何来翻译
- name指代的是模块名,即文件名
- local指代的是原本的选择器标识符
- hash:base64:5指代的是一个5位的hash值,这个hash值是根据模块名和标识符计算的,因此不同模块中相同的标识符也不会造成样式冲突
在使用CSS Modules的时候要注意,之前我们只是直接将CSS文件引入就可以了,但使用CSS Modules时CSS文件会导出一个对象,我们需要把这个对象中的属性添加到HTML标签上