第一节:webpack打包、压缩及兼容性处理
一.前言
当我们使用vue-cli3创建项目时,会自动生成相应的webpack配置,不过明白webpack的原理和基本设置方法对我们局部修改某些webpack配置还是很有必要的;
二.为什么需要构建工具?
- 转换
ES6
语法; - 转换
JSX
; CSS
前缀补全/预处理器;- 压缩混淆;
- 图片压缩;
三.Webpack五个核心概念
1.入口(Entry)
入口(Entry
)指示webpack
以哪个文件为入口起点开始打包,分析构建内部依赖图;
2.出口(Output)
输出(Output
)指示Webpack
打包后的资源bundles
输出到哪里去,以及如何命名;
3.Loader
Loader
让Webpack
能够去处理那些非JavaScript
文件(Webpack
自身只理解JavaScript
)
4.插件(Plugins)
插件(Plugins
)可以用于执行范围更广的任务,插件的作用范围包括,从打包优化和压缩,一直到重新定义环境中的变量等;
5.模式(Mode)
通过设置 mode
参数为 development
或 production
之中的一个,来启用相应模式下 webpack
内置的优化;
module.exports = {
mode: 'production'
};
development
模式:代码本地调试和运行的环境;production
模式:代码优化上线运行的环境;
四.源码仓库
webpack
系列中使用到的演示实例源码已上传至该仓库:
示例:
创建下图所示的文件目录:
其中src
表示源文件目录,存储着webpack
的入口起点文件,比如index.js
;build
用于webpack
打包处理之后输出的目录,其余文件可通过执行:
可通过下列指令创建一个演示实例:
npm init//生成package.json
webpack_test //文件夹名字
一路回车取默认值生成:
随后执行以下命令全局安装webpack
,其中的-g
参数表示全局安装,即使已经安装过了,也没关系。该指令会覆盖原来的安装并进行版本更新。
五.npm i 和 npm install 的区别
i
为install
的缩写;
实际使用的区别点主要如下(windows下):
-
用
npm i
安装的模块无法用npm uninstall
删除,用npm uninstall i
才卸载掉 -
npm i
会帮助检测与当前node
版本最匹配的npm
包版本号,并匹配出来相互依赖的npm
包应该提升的版本号 -
部分
npm
包在当前node
版本下无法使用,必须使用建议版本 -
安装报错时
intall
肯定会出现npm-debug.log
文件,npm i
不一定
npm i webpack webpack-cli -g
然后在本地安装,参数-D
为--save-dev
的缩写,表示它会将webpack
添加到package.json
中的开发依赖中:
npm i webpack webpack-cli -D
webpack
中下载的所有东西都属于开发依赖,不属于生产依赖,所以都使用-D
。
index.js
文件内容:
/*
index.js:webpack入口起点文件
1.运行指令:
开发环境指令:webpack ./src/index.js -o ./build/built.js --mode=development
翻译:webpack会以./src/index.js为入口环境开始打包,打包后输出 ./build/built.js;整体打包环境是开发环境;代码不会被压缩,可以看清楚逻辑;
生产环境指令:webpack ./src/index.js -o ./build/built.js --mode=production
翻译:webpack会以./src/index.js为入口环境开始打包,打包后输出 ./build/built.js;整体打包环境是生产环境;代码会被压缩,无法看清代码逻辑;
*/
function add(x, y){
return x + y
}
console.log(add(1, 2));
先进入02-webpack
初体验目录,然后打包文件
开发环境指令:
输入下列开发环境指令打包文件:
webpack ./src/index.js -o ./build/built.js --mode=development
输出结果:
- 其中的
Hash
值代表打包的结果,每次打包都会生成一个唯一的哈希值,唯一地ID
; - 其余的有版本,打包时间,资源
Asset
,打包后的文件built.js
的大小等;
此时build
目录下会多出一个built.js
文件
生产环境指令
输入下列生产环境指令打包文件:
webpack ./src/index.js -o ./build/built.js --mode=production
运行结果:
查看生成的build
目录下的built.js
文件,发现代码进行了压缩:
这个代码是可执行的:
每次src
文件夹中的入口文件index.js
引入了新依赖之后,都要重新进行打包,才能看到最新的结果;
也就是说只要指定了入口文件index.js
,就可以在index.js
中通过import
引入很多依赖文件,这样
webpack
在打包入口文件index.js
的时候就会根据其中引入关系,一起打包index.js
的依赖文件;
那么引入其他文件呢?
比如在index.js
中引入css
文件的时候:
会出现打包错误,打包出来的built.js
中的该部分会丢出一个错误:
得出结论:
webpack
能处理js/json
资源,不能处理css/img
等资源;- 生产环境和开发环境将
ES6
模块化编译成浏览器能识别的模块化; - 生产环境(
production
)比开发环境(development
)多了一个压缩js
代码;
六.打包样式资源
虽然webpack
不能直接打包css
文件,但是可以借助于各种各样的Loader
将webpack
不能识别的文件转换成它能识别的格式;
需要在根目录的package.json.js
文件中进行配置:
整体配置为:
const { resolve } = require('path')
module.exports= {
entry: './src/index.js',
//输出:这是一个对象,里面有两个东西。filename表示输出的文件名,path表示输出的路径,写路径的时候通常会写一个绝对路径,避免出错。绝对路径会引入一个nodejs的path模块
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules:[
{
//详细loader配置
//匹配哪些文件
test: /\.css$/,
//处理这些文件
use: [
'style-loader',
'css-loader'
]
}
]
},
plugins: [
//详细plugins配置
],
mode: 'development',
//mode: 'production'
}
首先, webpack.json.js
为webpack
的配置文件。作用为:指示webpack
干哪些活(当运行webpack
指令时,要加载哪些配置);
所有构建工具都是基于node.js
平台运行的,模块化默认采用commonjs
。可以看到commonjs
会导出一个对象,在该对象中写webpack
的配置:
1.入口起点
entry: './src/index.js'
表示打包的入口文件为src
目录下的index.js
文件;
2.输出
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
}
这是一个对象,其中:
-
filename
表示输出的文件名; -
path
表示输出的路径; -
写路径的时候为了避免出错,通常会写一个绝对路径。需要引入一个
nodejs
的模块path
模块:const { resolve } = require('path')
其中的
resolve
是用来拼接绝对路径的方法,格式为:path: resolve(__dirname, 'build')
传入两个参数:
__dirname
和当前的build
目录;其中__dirname
是nodejs
的变量,它代表当前文件(指webpack.config.js
)的目录的绝对路径:
- 该绝对路径就是
03-
打包样式资源,也就是说__dirname
的值就是03-打包样式资源
,拼接上build
,再加上第一个参数filename
指明的built.js
一起表示,打包后输出到build
目录下的built.js
文件中;
3.Loader配置
module: {
rules: {
//详细loader配置
}
}
4.插件(plugins
)
plugins: [
//详细plugins配置
],
5.模式
mode: 'development',
//mode: 'production'
开发模式development
和生产模式production
两种模式选一种,不能同时写;
6.打包样式资源
可以使用css-loader
来翻译css
文件:
module: {
rules: [
{
//详细loader配置
//使用正则表示式指定匹配哪些文件
test: /\.css$/,//该正则表达式表明,匹配以css结尾的文件,\为转义符
//通过test的正则表达式匹配了文件后,这里指定使用哪些loader进行处理
use: [
//需要使用两个loader
//作用:创建style标签,将js中的样式资源插入进去,添加到head中生效
'style-loader',
//将css文件转换成一个commonjs模块并加载到js中,里面内容是样式字符串
'css-loader'
]
}
]
}
注意:rules
属性是一个数组,里面的每一个元素都为对象,每个对象匹配并处理一类文件。并且对象中的use
属性也是一个数组,其中loader
的执行顺序为:从右到左,从下到上依次执行。为了不用每次都安装同样的依赖文件包,可以在根目录执行下列指令安装这些包,因为子目录中找不到包会依次往根目录找:
npm init //生成package.json
npm i webpack webpack-cli -D //下载webpack包,-D是--save-dev的缩写,表示是开发时依赖
npm i css-loader style-loader -D //下载两个loader,都是开发时依赖
进入03-打包样式资源
目录,执行webpack
进行打包:
打开打包生成的built.js
,可以看到index.css
被成功打包了:
在build
目录下新建index.html
引入生成的打包文件built.js
:
<script src="./built.js"></script>
随后使用浏览器打开该文件,发现样式发生了变化,源码多了style
标签
这便是style-loader
的作用。
注意:不同类型的文件要配置不同的loader
来处理;比如为了处理less
文件,需要webpack.config.js
中的rules
数组中再增添一个对象:
module: {
rules:[
//匹配并处理css文件
{
test: /\.css$/,
//处理这些文件
use: [
'style-loader',
'css-loader'
]
},
//匹配并处理less文件
{
test: /\.less$/,
use: [
//以style标签的形式在head标签中插入样式
'style-loader',
//将css文件装换为js形式
'css-loader',
//将less文件编译成css文件
'less-loader'
]
}
]
}
可以看到处理less
文件需要三个loader
,注意loader
执行的顺序为由下往上,由右往左:
less-loader
:将less
文件编译成css
文件;css-loader
:将css
文件内容整合到js
中;style-loader
:从js
中找到这些css
代码,将它传入style
标签,插入head
标签中;
所以处理less
文件需要使用三个loader
,注意:使用loader
之前要先进行下载:
npm i less less-loader -D //全局安装less和less-loader
注意:最好统一在根目录下载包,这样其他子目录向上查找时都能找到相应的包,避免重复下载;
安装完依赖包,并且正确配置package.config.js
之后,执行webpack
指令,就可以成功打包less
文件了:
七.打包html资源
1.添加基本配置
首先给webpack.config.js
添加基本的配置:
const {resolve} = require('path')
module.exports = {
entry : './src/index.js',
output : {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
//laoder的配置
]
},
plugins: [
//plugins的配置
],
//为了方便调试,使用不压缩代码的development模式
mode: 'development'
}
打包html
文件需要使用插件:
- 使用
loader
:下载 、使用(配置laoder
); - 使用
plugins
: 下载 、引入 、使用;
2.下载和引入插件
首先下载插件,同样采用全局下载,和开发时依赖:
npm i html-webpack-plugin -D
然后引入插件:
const HtmlWebpackPlugin = require('html-webpack-plugin')
由于它是构造函数,直接new
一个实例就可以使用了:
plugins: [
//plugins的配置
new HtmlWebpackPlugin()
],
3.打包文件
我们执行webpack
打包看看有什么效果:
注意要进入项目目录
04-打包html资源
再进行打包;
build
目录下多了一个html
文件:
打开该html
文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Webpack App</title>
<meta name="viewport" content="width=device-width, initial-scale=1"></head>
<body>
<script src="built.js"></script></body>
</html>
发现自动引入了built.js
文件,注意,src
目录下的源文件index.html
中是没有引入过built.js
文件的。
所以,插件html-webpack-plugin
的作用是,创建一个空的HTML
文件,自动引入打包输出的所有资源(包括js/css
);
如果想要打包出有结构的html
文件,则需要给该插件传入一个对象,里面有模板参数template
:
plugins: [
//plugins的配置
new HtmlWebpackPlugin({
//复制一个/src/index.html文件,并自动引入打包输出的所有资源
template: './src/index.html'
})
],
此时再次执行webpack
打包,打包出来的html
文件就拥有了src
目录下index.html
文件的所有结构,也就是原样复制了一份。
4.完整配置
webpack.config.js
的完整配置:
const {resolve} = require('path')
//引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry : './src/index.js',
output : {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
//laoder的配置
]
},
plugins: [
//plugins的配置
new HtmlWebpackPlugin({
//复制一个/src/index.html文件,并自动引入打包输出的所有资源
template: './src/index.html'
})
],
//为了方便调试,使用不压缩代码的development模式
mode: 'development'
}
5.总结
我们在入口文件index.js
中并没有引入html
文件。这个插件会根据template
属性指定的路径去寻找该文件,并且把该文件的内容复制进来并输出。只不过在输出之前会将打包生成的所有资源都引入到这个复制的html
文件中。如果是js
文件,就通过script
标签引入;如果是css
文件就通过link
标签引入;
需要注意的是,千万不要手动引入这些html
文件,因为插件帮我们自动引入了这些文件,我们再引入就重复了。
注意,与loader
的使用不同,使用插件时要多一步引入操作;
八.打包图片资源
项目目录如下:
在入口文件index.js
中引入样式文件index.less
;index.html
文件不用引入,该文件会由插件自动引入;在样式文件index.less
中又引入图片资源small.png
和big.png
;通过配置webpack.config.js
来打包这些文件:
1.初始配置
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path:resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader'
]
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}
)
],
mode: 'development'
}
此时没有添加处理图片的loader
,执行webpack
时会出现错误:
注意:webpack
不能识别资源的时候,第一时间就要想到loader
。添加以下loader
处理图片资源:
rules: [
//匹配并处理图片
{
test: /\.(jpg|png|gif)$/,
//只使用一个loader可以不用use数组
loader: 'url-loader',
options: {
limit: 8 * 1024
}
}
]
只使用一个loader
时可以不使用use
数组;使用多个loader
时需要使用use
数组。还可以通过option
对象进行参数的配置;
关于options
对象中的limit
属性:
- 规定图片大小小于
8kb
,就会被base64
处理。这样的好处是:减少服务器的请求数量,减轻服务器压力;缺点是:图片体积会增大,导致文件请求速度更慢; - 所以就需要找到一个平衡点,我们规定
8~12KB
以下的图片进行base64
处理。所以上面limit
中配置的8
就表示小于等于8KB
的图片都进行base64
处理。开发中可根据实际需要适当调整这个阈值;
2.打包图片资源
注意,需要下载两个loader
:url-loader
和file-loader
,因为前者依赖于后者;
回到根目录下,下载这两个包,依旧是开发时依赖:
npm i url-loader file-loader -D
安装完成后执行webpack
进行打包:
可以看到,成功地对两张图片进行了打包。
查看打包文件输出的目录build
,发现少了一张图片:
打开built.js
,可以发现,小于8KB
的small.png
被装换为了base64
编码,所以会少了一张图片:
我们再打开打包出来的index.html
文件,可以看到,small.png
也正常显示了;
打开调试工具,选中small/png
,可以看到该图片被转换成了base64
编码:
3.打包html文件中的图片
上面是通过样式文件index.less
引入的图片,那么通过src
目录下的index.html
文件引入图片能被正常打包吗:
<img src="./big.png" alt="">
执行webpack
再次打包,发现并没有报错:
但是,查看输出文件夹下的index.html
文件,发现图片的引入路径并没有发生变化:
这显然是不对的,打包过后,输出文件夹build
中显然不会存在big.png
。因此得出结论:url-loader
不能处理html
中的图片。
我们可以添加一个html-loader
来处理:
rules: [
{
test: /\.html$/,
loader: 'html-loader'
}
]
不能对该loader
顾名思义,它是专门处理html
中的img
图片的,负责引入img
从而能被url-loader
进行处理;
再次提醒:凡是使用到loader
都是要先下载;
npm i html-loader -D
随后打包出来的index.html
中的图片路径就是正确的了:
较低版本的webpack
可能会出现解析问题:打包出来的图片路径为[object module]
。这是由于url-loader
采用的是es6
模块化解析,而html-loader
引入图片时采用的是commonjs
而发生了冲突;
解决方法为:通过设置esModule: false
,来关闭url-loader
的es6
模块化,使用commonjs
解析:
rules: [
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
esModule: false
}
}
]
注意到,打包过后的图片是一串哈希值,比较长,可以通过name
属性重命名打包后的图片:
rules: [
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]'
}
}
]
-
[hash:10]
表示:取图片哈希值的前十位; -
[ext]
表示:取文件的原扩展名;
再次打包,可以看见另外生成了哈希值只有十位的图片:
还有一个细节:在样式index.less
中我们引入了三张图片,两张是相同的;webpack
不会打包出三张图片,它会进行识别,不打包重复的文件,这也是webpack
的优点之一;
4.完整配置
至此webpack
的完整配置为:
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path:resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.less$/,
//要使用多个loader时使用use数组
use: [
'style-loader',
'css-loader',
'less-loader'
]
},
//匹配并处理图片
{
test: /\.(jpg|png|gif)$/,
//需要下载url-loader 和 file-loader
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]'
}
},
{
test: /\.html$/,
//该loader专门处理html中的img图片
loader: 'html-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}
)
],
mode: 'development'
}
九.打包其他资源
1.安装file-loader
所谓其他资源,指的是自定义的资源,可能是字体文件,脚本文件等。我们希望这些文件不需要进行压缩或其他处理,直接原封不动地打包到输出文件夹中。
比如打包iconfont
文件,需要采用专门的laoder
进行处理:
module: {
rules: [
{
exclude: /\.(css|js|html)$/,
loader: 'file-loader',
}
]
}
打包其他资源(除了html/css/js
以外的资源),可以使用exclude
属性排除其他资源。使用file-loader
进行打包。
使用前需要在根目录安装这个loader
:
npm i file-loader -D
2.打包
随后对src
文件下的iconfont
文件和其他文件进行打包:
打包出来的文件目录为:
运行其中的index.html
可以正常看到iconfont
,说明打包成功:
同样也可以添加option
属性,重命名打包过后的文件名:
重新打包成功生成重命名后的文件:
3.完整配置
此时webpack
的完整配置为:
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules:[
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
//打包其他资源(除了html/css/js以外的资源),可以使用exclude属性排除其他资源
{
exclude: /\.(css|js|html)$/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development'
}
十.devServer
当我们每次给src
目录下新增文件和内容时,都需要重新打包。这显然很麻烦,webpack
也考虑到这一点,因此提供了devServer
的功能。
1.安装devServer
开发服务器 devServer
:用于自动化(自动编译,自动打开浏览器,自动刷新浏览器);
特点:只会在内存中编译打包,不会有任何输出;
启动devServer
指令为:webpack-dev-server
;
首先,需要在根目录下载该包,同样采用开发时依赖:
npm i webpack-dev-server -D
2.配置devServer
在webpack.config.js
中配置devServer
,它与五个基本配置是同一等级的:
devServer: {
//代表运行时的目录(打包后目录),也是使用字符串拼接绝对目录
contentBase: resolve(__dirname, 'build'),
//该参数表示启动gzip压缩
compress: true,
//端口号
port: 8081,
//自动打开浏览器
open: true
}
3.开启devServer
开启时添加npx
,找到该指令:
npx webpack-dev-server
成功运行后,可通过http://localhost:8081
端口查看运行结果:
能够成功显示:
此时只要改变src
目录下的文件,都会进行自动编译。这样不用频繁输入webpack
重新打包就可以实时看到变化了。
一旦开启devServer
它就会一直运行,可以通过ctrl+ c
关闭它:
也可以通过改变参数port
和open
来设置端口和是否自动打开浏览器,注意:只要重新配置了webpack.config.js
就需要重启devServer
:
npx webpack-dev-server
我们将打包输出目录build
下的文件都删除,再次运行上述指令。打包过后,build
目录下并不会生成任何文件。这就验证了:devServer
只会在内存中编译打包,不会有任何输出的特点。
十一.开发环境的基本配置
通过前面知识的学习,我们学会了打包样式资源,html
资源,图片资源和其他资源。以及学会了通过devServer
开启热更新。现在我们便可以开始配置基本的开发环境了;
1.开发环境基本配置
webpack.config.js
的配置如下:
/**
* 开发环境配置
*/
const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports ={
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
//loader的配置
//处理less资源
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
//处理css资源
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
//处理图片资源
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
// esModule: false
}
},
//处理html中图片的引入
{
test: /\.html$/,
loader: 'html-loader'
},
//处理其他资源
{
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]'
}
}
]
},
plugins: [
//plugins的配置
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true
}
}
2.优化输出目录
首先我们整理一下src
目录下的文件,并修改webpack.config.js
中的路径,随后使用webpack
打包。打包出来的build
目录下的文件还是很乱:
如果想要打包出来的build
目录下的文件能与src
目录结构相同呢?
可以给webpack.config.js
中的output
中的filename
添加前缀,这样打包过后的文件会自动创建这个指定的目录:
在处理图片时,只需要添加outputPath
属性就能指定打包后的目录结构了:
重新打包,图片文件被打包进了imgs
目录:
同理,可以在处理其他资源的loader
的option
属性中添加outputPath
属性指定打包后的目录结构:
注意了,主要的打包输出目录是由五大配置之一的output
中的path
属性决定的,上面的这一属性为build
。所以,之后使用outputPath
指定的路径都要拼接在build
后面。最后形成完整的路径。
十二.单独打包CSS文件
从现在开始我们将部署生成坏境;
常见错误:
ERROR in Entry module not found: Error: Can't resolve 'D:\webpack5\09-提取css成单独文件\src\index.html' in 'D:\webpack5\09-提取css成单独文件'
上面的这种错误大部分都是由于相关文件路径错误导致的。
1.安装mini-css-extract-plugin
可通过以下指令安装mini-css-extract-plugin
插件,提取打包文件built.js
中的css
文件:
npm i mini-css-extract-plugin -D
- 第一步:为了使用该插件:首先引入该插件:
const miniCssExtractPlugin = require('mini-css-extract-plugin')
- 第二步:然后在
plugins
属性中通过创建实例的方式使用:
plugins: [
new miniCssExtractPlugin()
],
- 第三步:配合相关的
loader
:
rules: [
{
test: /\.css$/,
use: [
// 'style-loader':创建style标签,将样式放入
//使用以下loader取代style-loader,作用:提取js中的css成单独文件
miniCssExtractPlugin.loader,
//将css文件整合到js文件中
'css-loader',
]
}
]
2.单独打包css文件
完成配置后,再次执行webpack
打包文件,没有报错,查看生成的打包输出文件目录build
,发现其中多了一个main.css
文件:
这个文件就是提取出来的css
文件;
也可以在第二步中传入参数进行一些配置:比如对输出路径和文件进行命名:
plugins: [
new miniCssExtractPlugin({
//对输出的css文件进行重命名
filename: 'css/built.css'
})
],
随后,删除原有打包出来的build
目录,再次执行webpack
,重新生成打包目录build
:
可以看到,成功地设置了css
文件存放的目录和文件名。
3.优点
这种做法的好处是:
-
提取后的
css
文件是通过link
标签引入的,这样就避免了闪屏现象: -
并且
css
文件与js
文件分离开了,所以js
文件体积缩小了,解析速度也会更好一些。
十三.CSS兼容性处理
css3
新增了许多的样式属性,但是并不是每一个浏览器都完全支持了这些样式属性,因此我们需要对某些样式属性进行兼容性处理,通过webpack
可以很容易地实现这一点;
需要使用postcss-loader
和postcss-preset-env
插件;
这个插件能帮助postcss
识别某些环境,而加载指定的配置,从而使兼容性做到精确到某一个浏览器版本;
1.安装依赖
首先,全局下载这个两包,依旧是开发时依赖:
npm i postcss-loader postcss-preset-env -D
2.基本配置
配置有两种写法:
-
第一种:使用
loader
默认配置。直接写:'postcss-loader'
这种方法不能修改配置,如果想要修改配置,采用第二种写法;
-
第二种:修改配置。写成对象的形式,在
options
属性中修改配置:{ loader: 'postcss-loader', options: { //固定写法 ident: 'postcss', plugins: () => [ //postcss的插件 require('postcss-preset-env')() ] } }
该插件的作用为:帮助postcss
找到package.json
中browserslist
里面的配置,通过配置加载指定的css
兼容性样式。
此时需要在根目录的package.json
中添加相关配置:
在其中添加下列配置:
"browserslist": {
//这是开发环境
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
//生产环境,
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
}
browserslist
对象中可以写两个参数:
development
代表开发环境的配置,值为数组;production
代表生产环境的配置;
关于
browserslist
的配置github
上又很详尽的介绍。
"last 1 chrome version"
代表兼容chrome
最近的一个版本;其他浏览器也是这个格式。如果是开发模式,我们不需要配置太多浏览器,只需要配置调试用到的浏览器就够了;
但是,生产环境就要多写一点;他们表示:
//表示兼容大于99.8%的浏览器
">0.2%",
//不要已经死的浏览器,比如IE10
"not dead",
//还有所有的op_mini浏览器
"not op_mini all"
这样就做到了绝大多数浏览器的兼容了。
打包时,默认看生产环境,与webpack.config.js
中的mode
是没关系的;如果想要使用开发环境,需要设置node
环境变量:
process.env.NODE_ENV = 'development'
打包前,webpack
的配置是这样的:
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const miniCssExtractPlugin = require('mini-css-extract-plugin')
//设置nodejs环境变量
process.env.NODE_ENV = 'development'
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [
miniCssExtractPlugin.loader,
'css-loader',
/**
* css兼容性处理:postcss --> postcss-loader postcss-preset-env
*/
{
loader: 'postcss-loader',
options: {
//固定写法
ident: 'postcss',
plugins: () => [
//postcss的插件
require('postcss-preset-env')()
]
}
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new miniCssExtractPlugin({
filename: 'css/built.css'
})
],
mode: 'development'
}
打包前,我们在css
文件中添加两个有兼容性问题的新样式:
使用webpack
打包后,打包后的样式发生了变化:
说明兼容性处理成功了。
这样,我们就能专心使用各种样式,而不需要考虑兼容性问题了,webpack
会自动帮我们做好这方面的工作。
十四.压缩CSS
这节介绍使用插件来完成css
的压缩,可以发现有的时候使用loader
,有的时候使用插件;主要的区别在于:loader
处理的东西比较少于专一,插件处理的东西比较大;
1.下载插件
使用的插件为:optimize-css-assets-webpack-plugin
,首先全局下载:
npm i optimize-css-assets-webpack-plugin -D
使用插件的时候,要先在webpack.config.js
中引入:
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
2.配置插件
然后在plugins
中使用:
plugins: [
//压缩css
new OptimizeCssAssetsWebpackPlugin()
]
只要引用就可以了,不需要额外配置,按照默认的配置已经可以将css
文件压缩了。
然后使用webpack
打包,输出的css
文件被压缩成了一行:
在样式较多的情况下,压缩是很有必要的。能够请求速度更快,加载样式更快,这样就能更快地渲染,用户体验也就更好。
十五.js语法检查
1.安装依赖
语法检查,不仅可以检查语法错误,还可以设置代码风格,让整个团队的代码风格都保持一致;专门做语法检查的如eslint-loader
它依赖于eslint
库,所以首先要在全局下载这两个包:
npm i eslint-loader eslint -D
2.配置eslint
注意:语法检查只会检查自己写的代码,第三方库不会也不用检查;所以需要使用exclude
来排除第三方库:
rules: [
{
test: /\.js$/,
loader: 'eslint-loader',
exclude: /node_modules/,
options: {}
}
]
此外还需要我们在package.json
中的eslintConfig
中配置检查规则。关于规则,推荐使用airbnb
。在github
上的这个项目会详细告诉你,应该如何配置这些规则:
npm
上有两个将airbnb
规则应用到eslint
中的包:
- 不包含
React
相关内容的插件也分为两种:eslint-config-airbnb-base
:包含ES6
及以上的内容;开发中一般都会使用ES6
语法,所以使用上面这个插件;eslint-config-airbnb-base/legacy
:包含ES5
和ES5
以下;
eslint-config-airbnb
:包含了React
的内容;
所以,我们选择使用eslint-config-airbnb-base
,注意,该插件依赖于eslint
和eslint-plugin-import
两个包;上面已经下载过eslint
了,这里只需要下载其他两个包:
npm i eslint-config-airbnb-base eslint-plugin-import -D
package.json
中的具体配置为:
"eslintConfig": {
//通过extends字段继承airbnb-base就可以了
"extends": "airbnb-base"
}
随后在入口文件index.js
中故意写一写不规范的代码:不加空格,分号等:
然后执行webpack
指令,进行打包,发现出现了很多错误:
虽然可以根据错误提示,在airbnb
文档中查找修改意见。但是手动修改仍然显得麻烦,可以在webpack.config.js
的eslint-loader
中的options
里添加fix
属性配置,让它自动修复:
rules: [
{
test: /\.js$/,
loader: 'eslint-loader',
exclude: /node_modules/,
options: {
//自动修复eslint的错误
fix: true
}
}
]
再次打包,通过自动修复就不会再显示错误信息了:
可以看到,虽然没有显示错误,但是有一个警告:不建议使用console
;在开发中可以使用console
进行调试,但是真正的代码中不应该含有它;此时可以通过添加下列注释,让eslint
忽略下一行代码:
//下一行eslint所有规则都失效(即下一个行不进行eslint检查)
//eslint-disable-next-line
console.log('123')
一个坑:安装包的时候,最好按照依赖关系来安装;即首先安装下层的包,再安装上层的包,否则可能会出现意想不到的错误;可以通过查看
package.json
中的配置来观察相应的包是否成功安装了;
十六.js兼容性处理
我们将入口文件index.js
内容改为箭头函数:
直接执行webpack
打包,打包出来的文件中,该部分并没有做兼容性处理:
这样的话,不支持ES6
的浏览器就会出错;这时候就需要使用babel-loader
来进行js
的兼容性处理了;
1.基本配置
webpack.config.js
中的配置为:
rules: [
/**
* js兼容性处理:babel-loader @babel/preset-env @babel/core
*/
{
test: /\.js/,
//排除不需要js兼容性处理的文件
exclude: /node_modules/,
loader: 'babel-loader',
options: {
//预设:指示babel做怎样的兼容性处理,一般使用@babel/preset-env就可以了
presets: ['@babel/preset-env']
}
}
]
注意:凡是涉及到兼容性处理都要使用exclude
将不需要进行兼容性处理的文件排除;否则会出错,如下图所示,不排除的话,依赖文件node_module
中的js
都报错了:
2.使用babel-loader
方法一:使用babel-loader
进行基本的兼容性处理;
首先需要在全局下载babel-loader
、@babel/preset-env
和@babel/core
:
npm i babel-loader @babel/preset-env @babel/core -D
随后再次执行webpack
打包,这次打包出来的js
文件中,ES6
的语法全都转换为了ES5
的语法了,也就是做了兼容性处理:
babel-loader
存在的问题
只能转换一些基本语法,如不能转换promise
;比如在入口文件index.js
中添加Promise
对象:
直接打包是不会被兼容性处理的。
解决方案:通过@babel/polyfill
对全部js
进行兼容性处理;
3.使用@babel.polyfill
方法二:使用@babel.polyfill
,对全部js
进行兼容性处理;
首先还是全局安装@babel/polyfill
:
npm i @babel/polyfill -D
它不是laoder
或插件,只需要在入口文件index.js
中通过import
引入就可以使用了:
import '@babel/polyfill';
此时再次执行webpack
打包,会发现打包出来的js
文件变得非常大:
引入@babel/polyfill
前:built.js
只有不到4KB
:
引入后,变成了441KB
:
这是因为只要使用了@babel/polyfill
,那么它就会将js
文件涉及到的所有兼容性问题都解决掉。
此时入口文件中的Promise
就被兼容性处理了:
@babel/polyfill
存在的问题
我只要解决部分兼容性问题,但是将所有兼容性代码全部引入,体积太大了;
解决方法:需要做兼容性处理就做:按需加载(只加载指定的兼容性库),通过core-js
实现;
4.使用core-js
方法三:使用core-js
,实现按需加载。
首先全局下载core-js
:
npm i core-js -D
随后在webpack.config.js
中进行相应的配置:
rules: [
/**
* js兼容性处理:babel-loader
*/
{
test: /\.js/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
//预设:指示babel做怎样的兼容性处理,一般使用@babel/preset-env就可以了
presets: [
[
'@babel/preset-env',
{
//按需加载
useBuiltIns: 'usage',
//指定corejs版本
corejs: {
version: 3
},
//指定兼容性做到那个版本浏览器
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
]
}
}
]
需要注意的是,使用第三种方法就不能使用第二种方法了,所以要在入口文件index.js
中不再引入@babel/polyfill
:
随后再次执行webpack
进行打包,会发现,打包出来的js
文件相比于使用第二种方法时的441KB
,缩减到了104KB
:
结合第一种和第三种方法就能实现对所有的js
代码进行兼容性处理;
5.完整配置
最后整个webpack.config.js
的配置如下:
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
/**
* js兼容性处理:babel-loader
*/
{
test: /\.js/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
//预设:指示babel做怎样的兼容性处理,一般使用@babel/preset-env就可以了
presets: [
[
'@babel/preset-env',
{
//按需加载
useBuiltIns: 'usage',
//指定corejs版本
corejs: {
version: 3
},
//指定兼容性做到那个版本浏览器
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
]
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development'
}
十七.压缩js和html
1.压缩js代码
只需要就webpack.config.js
中的模式mode
改为生产模式,就会自动压缩js
代码:
mode: 'production'
内部实现是通过插件UglifyJsPlugin
;
执行webpack
进行打包,打包出来的js
文件被压缩成了一行:
不需要做html
的兼容性处理,因为标签认识就认识,不认识就不认识,不能转换;只需要对html
进行压缩处理即可;
2.压缩html代码
只需要在配置文件webpack.config.js
中给HtmlWebpackPlugin
插件添加minify
属性即可:
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
//压缩html代码
minify: {
//移除空格
collapseWhitespace: true,
//移除注释
removeComments: true
}
})
],
执行webpack
进行打包,打包出来的html
文件被去除了所有空格并移除了注释:
十八.生产环境基本配置
学习了前面的基本配置,现在可以汇总起来配置基本的生产环境了。
1.配置的复用
如相同的配置可以抽离出来封装一个复用的loader
,比如css
和less
的兼容性处理,唯一的不同点是多了个less-loader
将less
转换为css
,所以其余部分可以复用;
未复用前,存在大量的重复代码:
rules: [
//处理css文件
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
//css兼容性处理
{
//还需要在webpack.json中定义browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
//指定插件
plugins: () => [
require('postcss-preset-env')()
]
}
}
]
},
//处理less文件
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
//css兼容性处理
{
//还需要在webpack.json中定义browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
//指定插件
plugins: () => [
require('postcss-preset-env')()
]
}
},
//由于use数组执行顺序为从下往上(注意执行顺序),经过less-loader转换为css后再进行兼容性处理
'less-loader'
]
}
]
复用后:
//复用loader
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
//css兼容性处理
{
//还需要在webpack.json中定义browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
//指定插件
plugins: () => [
require('postcss-preset-env')()
]
}
}
]
//...
rules: [
//处理css文件
{
test: /\.css$/,
//通过扩展运算符使用封装的loader
use: [...commonCssLoader]
},
//处理less文件
{
test: /\.less$/,
//由于use数组执行顺序为从下往上(注意执行顺序),经过less-loader转换为css后再进行兼容性处理
use: [...commonCssLoader,'less-loader']
}
]
//...
所以,当遇到重复代码的时候一定要考虑将重复代码抽离封装,达到复用的效果;
2.生产环境基本配置
完整的webpack.config.js
配置如下:
//0.引入path模块解决路径问题
const { resolve } = require('path');
//1.引入插件提取和兼容性处理css文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
//2.引入压缩css插件
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
//3.引入处理html图片引入的插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
//复用loader
const commonCssLoader = [
//这一行作用为将css文件抽离出来
MiniCssExtractPlugin.loader,
'css-loader',
//css兼容性处理
{
//还需要在webpack.json中定义browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
//指定插件
plugins: () => [
require('postcss-preset-env')()
]
}
}
]
//package.json中的browserslist默认使用开发环境,若使用生产环境需要定义nodejs环境变量
process.env.NODE_ENV = 'production';
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
//1.处理css文件
{
test: /\.css$/,
//通过扩展运算符使用封装的loader
use: [...commonCssLoader]
},
//2.处理less文件
{
test: /\.less$/,
//由于use数组执行顺序为从下往上(注意执行顺序),经过less-loader转换为css后再进行兼容性处理
use: [...commonCssLoader,'less-loader']
},
/**
* 正常来说:一个文件只能被一个loader处理
* 当一个文件要被多个loader处理时,那么一定要指定loader的执行顺序。
* 比如先执行eslint-loader,再执行babel-loader。这是因为一旦语法出错进行兼容性处理就没意义了。
* 如何添加顺序:enforce: 'pre'
*/
//3.进行语法检查
{
//在package.json中配置eslintConfig指定检查规则 --> airbnb
test: /\.js$/,
//配出不需要语法检查的文件
exclude: /node_modules/,
//优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
//自动修复错误
fix: true
}
},
//4.js兼容性处理
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
//添加预设,告诉babel以哪种方式进行兼容性处理
presets: [
//由于要使用方法一和三,所以使用数组保存
[
//简单处理
'@babel/preset-env',
//按需加载
{
useBuiltIns: 'usage',
//指定corejs版本
corejs: {version: 3},
//指定浏览器版本
targets: {
chrome: '60',
firefox: '50'
}
}
]
]
}
},
//5.处理图片
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
//通过base64编码优化
limit: 8 * 1024,
//重命名打包后的图片
name: '[hash:10].[ext]',
//指定输出路径
outputPath: 'imgs'
}
},
//6.处理html中的图片
{
test: /\.html$/,
loader: 'html-loader',
},
//8.处理其他文件
{
//排除其他文件
//正则中不加$表示只要匹配到这些词就行,是不是后缀都可以
exclude: /\.(js|css|less|html|jpg|png|gif)/,
//原封不动地输出文件
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
},
plugins: [
//兼容性处理css并单独抽离css文件
new MiniCssExtractPlugin({
//设置输出路径
filename: 'css/built.css',
}),
//压缩css
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
//指定html模板
template: './src/index.html',
//7.压缩html文件
minify: {
//移除空格
collapseWhitespace: true,
//移除注释
removeComments: true
}
})
],
//改为production模式自动压缩js文件
mode: 'production'
}