前端必备技能 webpack - 4. webpack处理CSS资源
每篇文章纯属个人经验观点,如有错误疏漏欢迎指正。转载请附带作者信息及出处。
点这里查看 webpack 系列文章目录
博客中的代码位于 码云Git仓库,如有需要可自行前往下载。
前端必备技能 webpack - 4. webpack处理CSS资源
因为 webpack
本身只具有识别 JS
的能力,所以涉及到其他资源,需要我们通过 loader
来进行特殊处理,针对不同的样式资源,需要以下几个 loader
:
style-loader
:用于加载css
文件,会通过<style>
标签将相应内容插入到页面中;css-loader
:用于处理.css
文件,帮助webpack
识别资源;less-loader
:用于处理.less
文件,将.less
文件编译成.css
文件;sass-loader
:用于处理.sass/.scss
文件,将.sass
文件编译成.css
文件;postcss-loader
:通过PostCSS
处理.css/.sss
文件;stylus-loader
:用于处理Stylus
文件;
PostCSS 和 Stylus:
PostCSS
官网地址:https://www.postcss.com.cn/
Stylus
官网地址:https://stylus.bootcss.com/
这里推荐张鑫旭大大提供翻译的 Stylus
中文文档: Stylus 中文文档
一、打包资源
我们先创建项目并进行相应的初始化,同时创建一个简单的 webpack
配置文件,对创建项目过程有疑问的同学,可以查看 前端必备技能 webpack - 2. webpack环境安装 的第四部分。
// webpack.config.js
// 引入 NodeJS 中的 Path 模块暴露的 resolve 方法来处理路径问题
// 同样可以使用 join 方法
const { resolve } = require('path');
// 采用 CommonJS 语法暴露配置文件
module.exports = {
// 开发模式
mode: "development",
// 入口文件
entry: "./src/index.js",
// 打包出口
output: {
// 设置打包文件名
filename: "[name]-[chunkhash:8].js",
// 设置打包后的文件路径
// __dirname 为 NodeJs 中的变量,代表当前文件的绝对目录
path: resolve(__dirname, "dist")
},
// loader 配置
module: {
rules: [
// 详细配置
]
},
plugins: []
}
这里推荐先将需要的 loader
都装好,将下列命令中的 loadername
替换为相应名称即可:
npm i loadername -D
准备工作完成后,我们来尝试打包各种类型的样式文件吧。
1.1 打包 .css 资源
在 src
下新建一个 index.css
文件,简单书写一些样式,并在入口文件 index.js
中将资源引入:
// index.js
import "./index.css";
对于普通的 css
资源,只需要引入 style-loader
和 css-loader
即可处理。所以没安装 loader
的需要先进行安装:
npm i style-loader css-loader -D
接下来我们对 webpack.config.js
进行修改:
rules: [
{
// 使用正则匹配所有 .css 结尾的文件
test: /\.css$/,
// 注意 loader 的处理顺序是从下到上,从右到左
// 需要特别注意, `loader` 在引入时对顺序是有要求的!!!
use: [
// style-loader 会通过 style 标签将 JS 文件中的 css 资源添加到页面中
"style-loader",
// css-loader 会将 css 文件变成 CommonJS 的模块资源,加载到 JS 文件中
"css-loader"
],
}
]
然后打包:
打包完成后,我们可以手动创建一个 html
文件将生成的 js
文件引入,查看效果是否正常。
1.2 打包 .less 资源
同样在 src
下新建一个 index.less
文件,并将其引入到 index.js
中:
我们知道 less
文件是不能被浏览器直接解析的,需要编译成 css
文件。webpack
同样需要将其进行编译,这里需要借助 less
和 less-loader
:
npm i less less-loader -D
接下来我们修改配置文件:
rules: [
// 详细配置
{
test: /\.less$/,
// less-loader: 将 .less 文件编译成 .css 文件
use: ["style-loader", "css-loader", "less-loader"]
}
]
然后打包:
1.3 打包 .sass 资源
sass
和 less
的处理步骤十分类似,只不过负责编译 sass
文件需要: sass
和 sass-loader
:
npm i sass sass-loader -D
同样修改配置文件以匹配 sass
文件:
rules: [
// 详细配置
{
test: /\..sass$/,
// sass-loader: 将 .sass 文件编译成 .css 文件
use: ["style-loader", "css-loader", "sass-loader"]
}
]
然后打包:
1.4 总结
关于 PostCSS
和 Stylus
文件的处理这里就不加掩饰,我们可以通过总结一下上方三种样式资源的处理方式,得出关于资源编译的特性,无论处理任何资源都可以按照这个步骤来进行
- Step 1:下载安装用于处理资源的相关
loader
; - Step 2: 通过
module.rules.resource
提供的各种方式来匹配需要处理的资源; - Step 3: 使用
module.rules.use
等来加载负责处理匹配到资源的loader
; - Step 4: 如有需要,可以通过
options
来设置对loader
的配置;
需要特别注意, loader
在引入时对顺序是有要求的!!!
以 .sass
文件为例,sass-loader
负责将 .sass
文件编译为 .css
文件;接着 css-loader
将 .css
文件转化为 CommonJS
模块加载到相应的 js
文件中;最后 style-loader
将 js
文件中相应的 css
通过 <style>
标签加载到页面中,所以 use
中的内容要严格按照这个顺序:
use: ["style-loader", "css-loader", "sass-loader"]
最后附上最终的 webpack.config.js
文件代码:
/**
* @filename: webpack.config.js
* @Author: wang
* @Description: webpack的配置文件,采用 CommonJS 语法
*/
// 引入 NodeJS 中的 Path 模块暴露的 resolve 方法来处理路径问题
// 同样可以使用 join 方法来拼接路径
const { resolve } = require('path');
// CommonJS 语法暴露配置文件
module.exports = {
// 开发模式
mode: "development",
// 入口文件
entry: {
index: "./src/index.js"
},
// 打包出口
output: {
// 设置打包后的文件名为 入口名 - chunkhash 前八位 .js
filename: '[name]-[chunkhash:8].js',
// 设置打包后的文件位于 dist 文件夹
// __dirname 为 NodeJs 中的变量,代表当前文件的绝对目录
path: resolve(__dirname, "dist")
},
// loader 配置
module: {
rules: [
// 详细配置
{
// 使用正则匹配所有 .css 结尾的文件
test: /\.css$/,
// 通过 style-loader 和 css-loader 处理匹配到的文件
// loader 的执行顺序是从下到上的 千万注意 loader 的书写顺序
use: [
// 通过 style 标签将 JS 文件中的 css 资源添加到页面的 head 中
"style-loader",
// 将 css 文件变成 CommonJS 的模块资源,加载到 JS 文件中
"css-loader"
],
},
{
test: /\.less$/,
// less-loader: 将 .less 文件编译成 .css 文件
use: ["style-loader", "css-loader", "less-loader"]
},
{
test: /\.scss$/,
// sass-loader: 将 .sass 文件编译成 .css 文件
use: ["style-loader", "css-loader", "sass-loader"]
},
]
},
// 插件
plugins: []
}
项目目录及package.json
:
二、提取
2.1 提取样式到单独 css 文件
我们发现,webpack
在打包完成后,css
资源被打包到了 js
当中,并没有将表现层和行为层进行分离,显然,这并不符合我们的开发习惯,在慢网速下也会由于 js
文件加载缓慢造成样式丢失等问题。这个时候,我们需要借助一个插件将 css
文件单独提取出来:
npm i mini-css-extract-plugin -D
安装完毕后我们修改一下项目的目录,并创建一些文件:
接下来修改配置文件,将插件引入进来:
/**
* @filename: webpack.config.js
* @Author: wang
* @Description: webpack的配置文件,采用 CommonJS 语法
*/
// 与 loader 不同,使用插件需要提前引入
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { resolve } = require('path');
module.exports = {
// 开发模式
mode: "development",
// 多入口文件
entry: {
a: "./src/js/a.js",
b: "./src/js/b.js"
},
// 打包出口
output: {
filename: '[name]-[chunkhash:8].js',
path: resolve(__dirname, "dist")
},
// loader 配置
module: {
rules: [
{
test: /\.css$/,
use: [
// 这里我们需要使用 MiniCssExtractPlugin 取代 style-loader
// style-loader : 将 css 通过 style 标签添加到页面中
// MiniCssExtractPlugin.loader: 将 JS 文件中的 css 资源提取到单独的 css 文件中
//MiniCssExtractPlugin.loader,
{
loader:MiniCssExtractPlugin.loader,
options:{
// 如果打包时背景图片等资源的路径与开发时不一致 可以通过 publicPath 设置公共路径
publicPath:""
}
},
"css-loader"
],
},
]
},
// 插件
plugins: [
// 插件都需要通过 new 的方式来调用
new MiniCssExtractPlugin({
// 设置生成的文件路径及文件名
filename: "/static/css/[name]-[chunkhash:8].css"
})
]
}
搞定,我们来执行一下打包命令:
我们发现 a.css
和 b.css
都被打包成了单独的文件,但 c.css
却被分别打包进了两个文件内。
2.2 打包公共样式
在工作中我们常常会将公共部分提取出来,单独引用着一个文件,在上方的实例中,c.css
就是一个公共资源,应该单独打包,而不是分别打包到每个引用他的文件内,这里需要借助另外一个插件: SplitChunksPlugin
;
SplitChunksPlugin
是 webpack4.0
以上版本的内置插件,无须安装,可以直接配置使用:
module.exports = {
// 省略......
optimization: {
splitChunks: {
cacheGroups: {
//打包公共模块
commons: {
// initial 表示提取每个入口文件中的公共部分
chunks: 'initial',
// 提取的 chunk 最少被引用一次才允许被提取
minChunks: 1,
// 设置提取公共部分最小的大小
// 假设值为 1* 1024 则表示 1KB 以下的文件不参与提取
minSize: 0,
// 提取出来的文件命名
name: 'common'
}
}
}
}
}
我们删除掉之前打包出的文件夹,重新进行打包:
这时候,我们已经成功的将公共样式文件单独打包成了一个文件,最后的 webpack.config.js
文件代码:
/**
* @filename: webpack.config.js
* @Author: wang
* @Description: 通过 mini-css-extract-plugin 提取样式资源到单独的文件
*/
// 与 loader 不同,使用插件需要提前引入
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { resolve } = require('path');
module.exports = {
// 开发模式
mode: "development",
// 入口文件
entry: {
a: "./src/js/a.js",
b: "./src/js/b.js"
},
// 打包出口
output: {
filename: '[name]-[chunkhash:8].js',
path: resolve(__dirname, "dist/static/js")
},
// loader 配置
module: {
rules: [
{
test: /\.css$/,
use: [
// 这里我们需要使用 MiniCssExtractPlugin 取代 style-loader
// style-loader : 将 css 通过 style 标签添加到页面中
// MiniCssExtractPlugin.loader: 将 JS 文件中的 css 资源提取到单独的 css 文件中
MiniCssExtractPlugin.loader,
"css-loader"
],
},
]
},
// 插件
plugins: [
// 插件都需要通过 new 的方式来调用
new MiniCssExtractPlugin({
// 设置生成的文件路径及文件名
filename: "../css/[name]-[chunkhash:8].css"
})
],
optimization: {
splitChunks: {
cacheGroups: {
//打包公共模块
commons: {
// initial 表示提取每个入口文件中的公共部分
chunks: 'initial',
// 设置公共部分提取的最少文件数为 2
minChunks: 2,
// 设置提取公共部分最小的大小
// 假设值为 1* 1024 则表示 1KB 以下的文件不参与提取
minSize: 0,
// 提取出来的文件命名
name: 'common'
}
}
}
}
}
三、压缩
为了减少文件大小,提高访问速度,一般我们会在上线时对文件进行压缩处理,压缩 css
文件时,需要安装一个插件:
npm i optimize-css-assets-webpack-plugin -D
安装完毕后,我们只需要安装插件的使用方法将其引入即可:
// 与 loader 不同,使用插件需要提前引入
// 提取 css 插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 压缩 css 插件
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const { resolve } = require('path');
module.exports = {
// 省略......
plugins: [
// 插件都需要通过 new 的方式来调用
new MiniCssExtractPlugin({
// 设置生成的文件路径及文件名
// filename: resolve(__dirname, "./css/[name]-[chunkhash:8].css")
filename: "../css/[name]-[chunkhash:8].css"
}),
// 直接引入即可
new OptimizeCssAssetsPlugin()
]
}
四、消除无用 css
我们在开发时一般都会引入各种框架来辅助开发,而我们往往只会用到其中的一部分,而不是所有的样式,这就造成了代码冗余。就算是我们自己写的样式,有时候也会因为页面更新等原因导致忘记删除无用的部分,现在我们可以借用 webpack
将无用部分直接去除:
npm i purifycss-webpack purify-css -D
因为涉及到 css
的引用,所以我们还需要对 html
资源进行处理:
npm i html-webpack-plugin -D
关于使用 html-webpack-plugin
打包处理 html
的内容,我们在后面的章节另行介绍,这里先知道这个插件用于打包 html
内容,自动引入文件即可。
引入完毕后,我们给 a.css | b.css
随意添加一些样式,:
然后修改配置文件:
// 省略......
plugins: [
// 打包 html 资源
new HtmlWebpackPlugin({
// 复制 './src/a.html' 文件结构
template: './src/a.html',
// 在打包生成的页面中自动引入 a 模块输出的所有资源
chunks: ['a'],
// 设置打包路径及文件名
filename: resolve(__dirname,"./dist/a.html")
}),
new HtmlWebpackPlugin({
template: './src/b.html',
chunks: ['b'],
filename: resolve(__dirname,"./dist/b.html")
}),
// 去除无用 css
new PurifyPlugin({
// 通过 NodeJs 中的 glob 模块扫码 html 文件,找出没有用到的样式
paths: glob.sync(resolve(__dirname, 'src/*.html'))
})
],
修改完成后打包查看输出结果:
我们发现直接随意添加的两个类名由于并没有在页面中引入,所以直接被 webpack
干掉了。
五、兼容性处理
我们知道有些属性在各个浏览器中拥有不同的前缀,我们在开发时需要将每一个浏览器的前缀都加上,例如说我们常用的弹性布局 display:flex
,为了处理兼容性常常需要写成以下形式:
.box1{
display: -moz-box; /* Firefox */
display: -ms-flexbox; /* IE10 */
display: -webkit-box; /* Safari */
display: -webkit-flex; /* Chrome, WebKit */
display: flex;
}
这让我们在开发时还需要手动添加额外的属性,感觉上十分的麻烦,所以这时候,我们可以选择通过 webpack
来处理:
npm i postcss postcss-loader postcss-preset-env -D
在之前版本中,相关配置可以直接写在 loader
中:
// 省略...
rules: [
{
loader: 'postcss-loader',
options: {
// 固定写法 使用 postcss 方式识别
ident: 'postcss',
plugins: () => [
// 引用 postcss-preset-env 插件
require('postcss-preset-env')()
]
}
}
]
但目前版本的 postcss
需要将配置分别写在 loader
和 plugins
中:
// 省略...
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
// postcss-loader 需要在 css-loader 前, less/sass-loader 后引用
"postcss-loader"
],
},
]
}
plugins: [
require('postcss-preset-env')()
]
修改完配置文件后,还需要我们提供一个相应的配置文件,决定如何处理兼容性,之前版本可以直接在 package.json
中声明 browserslist
属性即可:
"browserslist": [
"defaults",
"not ie < 10",
"last 2 versions",
"> 1%",
"iOS 7",
"last 3 iOS versions"
]
但更新后需要在项目根目录新建一个 postcss.config.js
的配置文件来进行设置:
/**
* @filename: postcss.config.js
* @Author: wang
* @Description: postcss 配置文件 指示那些情况下需要处理 那些情况不需要处理兼容性
*/
module.exports = {
plugins: [
require('autoprefixer')({
"browsers": [
"defaults",
"not ie < 11",
"last 2 versions",
"> 1%",
"iOS 7",
"last 3 iOS versions"
]
})
]
}
如果还是想将相应内容写到 package.json
中,则需要将 postcss.config.js
修改为:
/**
* @filename: postcss.config.js
* @Author: wang
* @Description: postcss 配置文件 指示那些情况下需要处理 那些情况不需要处理兼容性
*/
module.exports = {
plugins: [
require('autoprefixer')()
]
}
打包完成后,我们发现 webpack
已经为我们自动加上了前缀。
以上就是本篇博客的内容,博客内容较长,建议无基础的同学分段阅读。关于对 css
资源的处理,大体上就是以上的内容,博客中的代码位于 码云Git仓库,如有需要可自行前往下载。
下一篇博客我们来介绍一下 webpack
对 js
资源的处理。
感谢大家的观看,点赞和收藏,我们下篇博客再见。
每篇文章纯属个人经验观点,如有错误疏漏欢迎指正。
转载请附带作者信息及出处。您的评论和关注是我更新的动力!
下面是我的个人微信公众号,我会在里面定期更新前端的相关技术文章,欢迎大家前来讨论学习。如果有什么问题需要老王帮忙或者想看关于某个主题的文章,也可以通过留言等方式来联系老王。
都看到这里了,三连一下呗~~~。点个收藏,少个 Bug 。