1 Webpack4
1.1 五大核心概念
-
【入口(entry)】:指示webpack应该使用哪个模块来作为构建其内部依赖图的开始。
-
string:
"./src/index.js"
单入口,打包形成一个chunk,输出一个bundle文件,输出的文件名默认叫
main.js
。 -
array:
["./src/index.js", "./src/index.html"]
(dll就是此写法)多入口,打包形成一个chunk,输出一个bundle文件,一般用于HTML文件的HMR功能。
-
object:
{index: "./src/index.js", add: "./src.add.js"}
多入口,有多少个入口文件就形成几个chunk,输出多少个bundle文件,文件名称是对象中的key
-
特殊用法:
{ index: ["./src/index.js", "./src/count.js"], add: "./src/add.js" }
-
-
【输出(output)】:在哪里输出文件,以及如何命名这些文件。
output:{ filename: "js/[name].js", path: resolve(__dirname, "build"), // 输出文件目录(将来所有资源输出的公共目录) publicPath: '/', // 所有资源引入的公共路径前缀,一般用于生产环境下,所有引入的资源都加入此前缀 chunkFilename: "js/[name]_chunk.js", // 打包其他资源时使用此命名,若不配置此项,则使用filename命名 library: "[name]", // 一般用于dll打包所向外暴露的变量名 libraryTarget: "window/global/commonjs" // 向外暴露的变量名挂载到哪个全局变量 }
-
【loader】:处理那些非js文件(webpack自身只能解析js和json)。
1)webpack本身只能处理js、json模块,若要加载其他类型的文件或者模块,就需要使用对应的loader。它本身是一个函数,接收原文件作为参数,返回转换的结果。
2)loader一般以xxx-loader的方式命名,xxx代表了这个loader要做的转换功能,比如css-loader。 -
【插件(plugin)】:执行范围更广的任务,从打包到优化都可以实现。
1)插件可以完成一些loader完成不了的功能。 -
【模式(mode)】:有生产模式production和开发模式development。
1)开发依赖:帮助程序员加工代码的库,都是开发依赖。
2)生产依赖:帮助程序员实现功能效果的库,都是生产依赖。
1.2 其他配置
1. devServer
-
安装webpack-dev-server
webpack@4、webpack-cli@3、webpack-dev-server@3 这三个版本相容。 局部安装:npm install webpack-dev-server@3 -D 全局安装:npm install webpack-dev-server@3 -g
-
配置:与五大核心概念平级
// webpack.config.js module.exports = { ... module: {}, // 开发服务器 devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器~) // 特点:只会在内存中编译打包,不会有任何输出 // 启动devServer的指令为:npx webpack-dev-server devServer:{ contentBase: resolve(__dirname, "build"), // 运行代码的目录 watchContentBase: true, // 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload watchOptions: { ignored: /node_modules/, // 忽略文件 } compress: true, // 启动gzip压缩 port: 5000, // 端口号 host: "localhost", // 域名 open: true, // 自动打开浏览器 hot: true, // 开启HMR功能 clientLogLevel: "none", // 不要显示启动服务器日志信息 quiet: true, // 除了一些基本启动信息外,其他内容不要显示 overlay: false, // 若出错了,不要全屏提示 // 服务器代理,目的是解决开发环境跨域问题 proxy: { // 一旦devServer(5000)服务器接收到 /api/xxx 的请求,就会把请求转发到另外一个服务器(3000) "/api": { target: "http://localhost:3000", // 发送请求时,请求路径重写,将 /api/xxx --> /xxx(去掉/api) pathRewrite: { "^/api": "" } } } } ... }
-
修改package.json中的script指令:
"dev": "webpack-dev-server"
。 -
修改后运行指令:
npm run dev
。 -
生产环境准备
-
新建config文件夹,重命名webpack.config.js为webpack.dev.js,放入config文件夹。
-
复制webpack.dev.js,重命名为webpack.prod.js,删除其中的devServer配置,因为这是开发环境特有的,生产环境不需要。
-
修改pack.json中的script指令:
"start": "webpack-dev-server --config ./config/webpack.dev.js", "build": "webpack --config ./config/webpack.prod.js"
-
修改output中的path为:
path: resolve(__dirname, '../build')
-
2. resolve
// webpack.config.js
mode: "development",
// 解析模块的规则
resolve: {
// 配置解析模块路径别名。优点:简写路径;缺点:没有路径提示
alias: {
$css: resolve(__dirname, "src/css")
},
// 配置省略文件路径的后缀名
extensions: [".js", ".json", ".jsx", ".css"],
// 告诉webpack解析模块是去找哪个目录(假如现在webpack.config.js处于第三级目录)
modules: [resolve(__dirname, "../../node_modules"), "node_modules"]
}
1.3 说明
- webpack.config.js:用于存储webpack的配置信息。
1. webpack打包的基本流程
- 连接:webpack从入口JS开始,递归查找出所有相关的模块,并连接起来形成一个网(图)的结构。
- 编译:将js模块中的模块化语法编译为浏览器可以直接运行的模块语法,当然其他类型的资源也会处理。
- 合并:将图中所有编译过的模块合并成一个或少量的几个文件,将浏览器真正运行的是打包后的文件。
2. loader和plugin的区别
- loader:用于加载特定类型的资源文件,webpack本身只能打包js和json。
- plugin:用来扩展webpack其他方面的功能,一般loader处理不了的资源、完成不了的操作交给插件处理。
3. live-reload与HMR
-
不同点:
live-reload(自动刷新):刷新整体页面,从而查看到最新代码效果,页面状态全部都是最新的。
HMR(热膜替换):没有刷新整个页面,只是加载了修改模块的打包文件并运行,从而更新页面的局部界面,整个界面的其他部分的状态还在。
-
相同点:代码修改后都会自动重新编译打包。
1.3 开启项目
1. 初始化项目
- 使用
npm init
或者yarn init
生成一个package.json文件
2. 安装webpack
npm install webpack@4 webpack-cli@3 -g
全局安装作为指令使用。npm install webpack@4 webpack-cli@3 -D
本地安装,作为本地依赖使用。
3. 运行
- 执行
webpack ./src/js/app.js -o ./build/js/app.js --mode=development/production
命令即可完成打包。
4. 结论
- webpack能够编译打包js和json文件。
- 能将es6的模块化语法转换成浏览器能识别的语法,而且也能压缩代码。
5. 缺点
- 默认不能编译打包css、img等文件。
- 默认不能将js的es6基本语法转换为es5以下语法。
6. 改善
- 使用webpack配置文件解决,自定义功能,需要安装响应的loader和plugin。
7. webpack配置文件
-
规范:遵循commonJS规范,所有构建工具都是基于nodejs平台运行的。src文件则遵循ES6modeule规范。
-
目的:在项目根定义配置文件,通过自定义配置文件简化上述运行指定的操作。
-
文件名称:webpack.config.js。
-
文件内容:五大核心概念
// webpack.config.js const {resolve} = require('path'); module.exports = { mode:'development', entry:'./src/js/app.js', output:{ // __dirname: 代表当前文件的绝对路径,build: 代表文件夹 path: resolve(__dirname, 'build'), // 输出的文件名 filename: 'built.js' }, module:{ rules:[ // 在此处配置一个一个loader ] }, plugin:[ // 此处专门用于配置插件,插件必须经过实例化这一环节 ] }
-
运行指令:
webpack
,运行了该指令后会自动找webpack.config.js文件。
2 基础配置
2.1 打包css文件
- 安装:
npm i style-loader@2 css-loader@5 -D
// webpack.config.js
module.exports = {
...
module: {
rules: [
{
// 匹配哪些文件
test: /\.css$/,
// 使用哪些loader处理,use数组中loader的执行顺序:从左到右,从下到上 依次执行
use: [
// 在head中创建style标签并将css-loader编译后的文件进行引入
'style-loader',
// 将css文件变成commonjs模块加载到js中,里面的内容是样式字符串
'css-loader'
]
}
]
}
...
}
2.2 打包less文件
- 安装:
npm i less@4 less-loader@7 -D
// webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
// 将less翻译成css,less-loader依赖于less
'less-loader'
]
}
]
}
...
}
2.3 打包html文件
- 创建html文件:不要在该文件引入任何css和js文件。
- 打包html文件需要使用插件
html-webpack-plugin
。 - 安装:
npm i html-webpack-plugin@4 -D
// webpack.config.js
// 插件使用:1.下载 2.引入 3.实例化(new)
const HtmlWebPackage = require('html-webpack-plugin');
module.exports = {
...
plugins: [
// 功能:默认创建一个空的html文件,自动引入打包输出的资源(js/css)
// 需要有结构的html的话,则需要进行配置
new HtmlWebPackage({
// 赋值该文件的html文件中的结构
template: './src/index.html'
})
]
...
}
2.4 打包图片资源
- 在css/less文件中通过背景图的方式引入图片。
- 安装:
npm i file-loader@6 url-loader@4 -D
// webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.(jpg|png|gif)$/,
// 下列写法与此写法作用一致use:[{loader:'url-loader',options:{...}}]
// url-loader: 默认处理不了html中的img图片,url-loader依赖于file-loader
// url-loader相对于file-loader的优势是可以对图片进行动态转换base64编码(控制limit属性值可以控制阈值)
loader: 'url-loader',
options: {
// 图片大小小于 8kb,就会被base64处理;若大于 8kb 就用file-loader处理(相当于处理其他资源一样)
// 优点:减少请求数量(减轻服务器压力); 缺点:图片体积会更大(文件请求速度变慢)
limit: 8 * 1024,
// 打包后的文件命名取前10位hash值
name: '[hash:10].[ext]',
// 若打包后html中的图片的src='[object Module]'
// 解决办法:url-loader中加入一个配置 -> esModule: false即可
esModule: true
}
},
]
},
...
}
- 打包html文件中的img图片
- 安装:
npm i html-loader@1 -D
module: {
rules: [
{
test: /\.html$/,
// 处理html文件中的img图片资源,主要负责引入img,从而能被url-loader进行处理
use: ['html-loader']
// 若打包后html中的图片的src='[object Module]',因为html-loader引入的图片是commonjs语法
// 解决办法:url-loader中加入一个配置 -> esModule: false即可
}
]
},
2.5 打包其他资源
- 其他资源(字体、音视频等)webpack不能解析,需要借助loader编译解析。
module: {
rules: [
{
// 要排除的文件
exclude: /\.(html|js|css|less|jpg|png|gif)$/,
// 用于处理其他资源,也可以处理图片资源,核心操作:提取资源到指定位置,且可以修改文件名等操作
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
// 输出路径
outputPath: '/assets/font'
}
}
]
},
2.6 打包css为单独文件
-
安装:
npm i mini-css-extract-plugin@1 -D
-
引入:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
-
配置:
// webpack.config.js const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { module: { rules: [ { test: /\.css$/i, use: [MiniCssExtractPlugin.loader, "css-loader"], }, ], }, plugins: [new MiniCssExtractPlugin()], };
-
由于提取了独立的文件,要从外部引入,所以可能会有路径的问题。
解决方案:在output配置中,添加publicPath: '/'
,publicPath根据实际情况自行调整,若上线运行值为:/imgs,若本地右键运行值为:/build/imgs
2.7 css兼容性处理
-
安装:
npm i postcss@8 postcss-loader@4 postcss-preset-env@7 -D
-
postcss-preset-env
:帮助postcss识别某些环境,从而加载指定的配置,精确到某个浏览器的版本。运作时,帮助postcss找到package.json中的browserslist里面的配置,并通过配置加载指定的css兼容性样式。 -
使用顺序:
["css-loader", "postcss-loader", "less-loader"]
// webpack.config.js // 设置node环境变量,使得postcss加载browserslist中的"development"兼容样式 process.env.NODE_ENV = 'development' module.exports = { module: { rules: ['css-loader', { loader: 'postcss-loader', options: { postcssOptions:{ ident: 'postcss', plugins: [ // postcss的插件 require('postcss-preset-env') ] } } } ] } }
-
在package.json配置,在其中追加browserslist配置,通过配置加载指定的css兼容性样式(vue、react中的配置)
// package.json "browserslist": { // 开发环境 "development": [ "last 1 chrome version", // 最新版的chrome浏览器版本 "last 1 firefox version", // 最新版的firefox浏览器版本 "last 1 safari version" ], // 生产环境:默认是生产环境 "production": [ ">0.2%", // 兼容市面上99.8%的浏览器 "not dead", // "死去"的浏览器不做兼容,例如IE8 "not op_mini all", // 不做opera浏览器mini版兼容 ] }
-
browserslist是一个单独的库,被广泛用在各种设计浏览器或者移动端的兼容支持工具中。关于browerlist更详细的配置,参考
https://github.com/browserslist/browerslist
。
2.8 压缩css
-
安装:
npm i optimize-css-assets-webpack-plugin@6 -D
。// webpack.config.js module.exports = { plugins: [ new OptimizeCssAssetsWebpaclPlugin(), ] }
2.9 js语法检查
-
概述:对js基本语法错误或者隐患,进行提前检查。
-
安装:
npm i eslint@7 eslint-loader@4 -D
。 -
安装检查规则库:
npm i eslint-config-airbnb-base@15 eslint-plugin-import@2
。ellint-config-airbnb-base
定制了一套标准的、常用的js语法检查规则,推荐使用。eslint-plugin-import
用于将js语法检查规则进行导入,配合eslint-loader使用。// webpack.config.js module:{ rules:[ { test: /\.js$/, exclude: /node_modules/, // 优先执行,原因是eslint和babel都是对js文件进行处理,所以两者之间要指定执行的顺序 enforce: 'pre', // 对js进行语法检查 loader: 'eslint-loader', // eslint-loader依赖于eslint options: { fix: true // 若有问题自动修复,重要! } } ] } // package.json "eslintConfig": { "extends": "airbnb-base", // 直接使用airbnb-base提供的语法规则 "env": { "browser": true // 支持浏览器端全局变量 } }
-
若出现:
warning Unexpected console statement no-console
警告,意思是不应该在项目中写console.log();若想忽略,就在要忽略检查代码的上方输入一行注释:eslint-disable-next-line
即可。
2.10 js兼容性处理
- 概述:将浏览器不能识别的新语法转换成原来的旧语法,做浏览器兼容性处理。
- 安装:
npm i babel-loader@8 @babel/core@7 -D
兼容性处理方式
1. 基本js兼容性处理
-
安装:
npm i @babel/preset-env@7 -D
// webpack.config.js module: { rules: [ test: /\.js$/, exclude: /node_modules/, use: [{ // 将es6语法转换为es5语法(即做浏览器的兼容性处理) loader: "babel-loader", options: { // 预设:只是babel做怎么样的兼容性处理,@babel/preset-env处理最基本的兼容性问题 presets: ["@babel/preset-env"] } }] ] }
-
问题:使用
@babel/preset-env
只能处理简单的语法,promise等无法处理。
2. 全部js兼容性处理
-
安装:
npm i @babel/polyfill -D
,此包要安装在生产依赖中,而非开发依赖。// index.js 入口文件 import "@babel/polyfill"
-
问题:虽然可以完成高级es6语法的转换,但缺点是所有都转换,无法按需转换,生成的js体积大。
3. 按需处理js兼容性
-
安装:
npm i core-js@3 -D
。 -
注意点:正常来讲,一个1文件只能被一个loader处理。当一个文件要被多个loader处理,那么一定要指定loader的执行顺序,故
core-js兼容性处理
与eslint语法检查
的执行顺序不能颠倒,先执行语法检查后执行兼容性处理。因为core-js的作用原理是,要在入口文件中导入core-js
中相应的js文件;而eslint
中排除了node_modules
文件夹,当eslint
检查到core-js
导入的文件时,就会发生错误。// webpack.config.js module: { rules: [ test: /\.js$/, exclude: /node_modules/, use: [{ loader: "babel-loader", options: { presets: [ [ "@babel/preset-env", { // 按需加载 useBuiltIns: "usage", // 指定core-js版本 corejs: { version: 3 }, // 指定兼容性做到哪个版本的浏览器 targets: { chrome: "60", firefox: "60", ie: "9", safari: "10", edge: "17" } } ] ] } }] ] }
2.11 压缩html、js
-
直接修改webpack.prod.js中的mode为production即可。
-
若设置了模式为production,必须在new HtmlWebpackPlugin时添加配置minify: false。
new HtmlWebpackPlugin({ minify: { collapseWhitespace: true, // 去除空格 removeComments: true // 去除注释 } })
3 优化代码调试
3.1 source-map
-
source-map:一种提供源代码到构建后代码映射技术,若构建后代码出错了,通过映射可以追踪源代码错误。它会生成一个
xxx.map
文件,里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后代码出错了,会通过xxx.map
文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。// webpack.config.js module: { ... }, plugins: [], devtool: '[inline-|eval-|hidden-][nosources-][cheap-[module-]]source-map'
-
组合:
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
。source-map
:生成外部.map文件,提示错误代码准确信息和源代码的错误位置。inline-source-map
:在js文件中生成内联source-map,格式为base64,提示错误代码准确信息和源代码的错误位置。eval-source-map
:在每个js文件中生成对应source-map,都在eval,提示错误代码准确信息和源代码的错误位置。hidden-source-map
:生成外部文件,提示错误代码原因,但没有错误位置,不能追踪源代码错误,只能提示到构建后代码的错误位置。nosource-source-map
:生成外部文件,提示错误代码原因,但是没有任何源代码信息。(用于生产环境)cheap-source-map
:生成外部文件,提示错误代码原因和源代码的错误位置,只能精确到行。(默认精确到列)cheap-module-source-map
:生成外部文件,提示错误代码原因和源代码的错误位置。除外,还会将loader的进行加入。 -
内联和外部:外部生成了.map文件,内联没有;内联构建速度更快。
-
开发环境:速度快,调试更友好(一般选择内联)
速度快(eval > inline > cheap > …)
eval-cheap-source-map > eval-source-map
调试更友好
source-map > cheap-module-source-map > cheap-source-map
综上:开发环境选择eval-source-map > eval-cheap-module-source-map
-
生产环境:源代码是否要隐藏?调试是否要友好?(内联会让代码体积变大,故在生产环境下不用内联)
隐藏:
nosource-source-map 全部隐藏
,hidden-source-map 只会隐藏源代码,会提示构建后代码错误信息
调试:source-map > cheap-module-source-map
4 优化打包构建速度
4.1 HMR
-
HMR:hot module replacement 热膜块替换,在devServer中添加
hot: true
即可。 -
作用:一个模块发生变化,只会重新打包这一模块(而不是打包所有的模块),极大提升了构建速度。
-
样式文件:可以使用HMR功能,因为style-loader内部实现了热模块替换功能,在开发环境下,必须使用
style-loader
而不能使用mini-css-extract-webpack-plugin
。 -
html文件:默认不能使用HMR功能(webpack5中默认能使用),修改的同时html文件的内容也不会发生变化(解决这个问题需要在entry入口,将html文件引入)。值得注意的是,html只有一个文件,没有必要进行HMR。
-
js文件:默认不能使用HMR功能,若要实现则需要修改js代码,添加支持HMR功能的代码。
// index.js 入口文件 // 兼容性处理 if(module.hot){ // 一旦 module.hot 为true,说明开启了HMR功能,就可以让HMR功能代码生效 // module.hot.accep会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建 module.hot.accept('./print.js'); }
注意:HMR功能只能处理非入口js文件。
上面这样写会很麻烦,所以实际开发我们会使用其他 loader 来解决,比如vue-loader
,react-hot-loader
。
4.2 oneOf
- 默认情况下,每个文件都需要webpack定义的loader检验一遍,这就会使打包构建效率变得很慢。
// webpack.config.js
module: {
roules: [
{
loader: 'eslint-loader'
},
{
// oneOf:作用是当匹配到了符合自己的loader时就不再往下匹配了,
// 要求每个loader匹配的都不能重复,若重复,则提取出来,否则只有最先匹配的loader生效
oneOf: [
{
loader: ''
},
{
loader:''
}
]
}
]
}
4.3 cache缓存
- 定义:对 Eslint 检查 和 Babel 编译结果进行缓存。
- 作用:每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了。
eslint缓存
-
若是用plugin进行语法检查,写法如下:
// webpack.confg.js plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "../src"), exclude: "node_modules", // 默认值 cache: true, // 开启缓存 // 缓存目录 cacheLocation: path.resolve(__dirname, "../node_modules/.cache/.eslintcache"), }), ]
babel缓存
-
babel缓存,目的是让第二次打包构建的速度更快,优化打包速度。
// webpack.config.js { loader: 'babel-loader', options: { presets: [], // 开启babel缓存,第二次构建是,会读取之前的缓存 cacheDirectory: true, // 关闭缓存文件的压缩,压缩会消耗时间,缓存在内存中,对上线无影响 cacheCompression: false } }
4.4 多进程打包
-
多进程打包需要借助
thread-loader
来完成,进程启动大概需要600ms,进程通信也有开销,只有工作消耗时间比较长,才需要多进程打包,若滥用的话只会更慢。// webpack.config.js module:{ rules:[ { // 一般情况下,主要是打包js文件,因为一个项目中js居多 test:/\.js$/, use:[ { // 多进程loader需要放置在功能loader的后面 loader:'thread-loader', options:{ workers: 2 // 开启进程为2个 } }, { loader: 'babel-loader', } ] } ] }
4.5 externals
-
externals:打包的时候拒绝把第三方库打包进来,此时第三方库就需要在index.html中手动通过cdn的方式引进来。
// webpack.config.js mode: "production", externals:{ // 拒绝jQuery被打包进来 jquery: "jQuery" // jquery是库名:jQuery是npm下的包名 }
5 优化代码运行的性能
5.1 dll(动态链接库)
-
dll作用:默认情况下,第三方库在code split下是将项目所用的库都打包成一个js文件,那么这个js文件就非常大,利用dll技术可以将用到的第三方库打包成不同的js文件(即分别进行单独打包)以达到减少每个文件体积的目的。
-
externals和dll的区别:externals拒绝打包第三方库,使用时则通过cdn的方式引入(第三方库不需要部署在服务器);dll是提前将第三方库打包好,使用时直接引入打包好的即可(第三方库需要部署在服务器)。
-
配置,在webpack.config.js的同级目录下新建webpack.dll.js。单独打包第三方库,需运行
webpack --config webpack.dll.js
// webpack.dll.js const webpack = require("webpack") // webpack自带的插件,无需下载 module.exports = { entry:{ jquery: ["jquery"], // 第一个jquery是最终打包生成的[name],数组里面的jquery是要打包的库 vue: ["vue"] }, output:{ filename: "[name].js", path: resolve(__dirname, "dll"), library: "[name]_[hash]" // 打包的库里面向外暴露出去的内容叫什么名字 }, plugins:[ // 打包生成一个 manifest.json,提供和jquery映射,目的是webpack打包的时候标记此库无需打包 new webpack.DllPlugin({ name: "[name]_[hash]", // 映射库向外暴露的内容的名称 // 每个第三方库对应一个映射文件 path: resolve(__dirname, "dll/[name].manifest.json") // 输出文件路径 }) ], mode: "production" }
-
webpack.config.js配置
// webpack.config.js const webpack = require('webpack') plugins:[ // 告诉webpack哪些库不需要打包,同时使用时的名称也得变 new webpack.DllReferencePlugin({ manifest: resolve(__dirname, "dll/jquery.manifest.json"), // 进行webpack.dll.js打包时生成的映射文件 manifest: resolve(__dirname, "dll/vue.manifest.json") }), // 每次打包时在html文件中引入dll文件夹里面的第三方资源 new AddAssetHtmlWebpackPlugin({ filepath: resolve(__dirname, "dll/jquery.js"), filepath: resolve(__dirname, 'dll/vue.js'), }) ]
5.2 network cache缓存
- 文件资源缓存,目的是让代码运行缓存更好使用。
浏览器向服务器请求数据时,除了第一次向服务器索取数据,后面就强制走浏览器的缓存了(数据缓存未过期)。若此时服务器重新部署了新的资源,浏览器会请求不到新的文件资源。为了解决这个问题,可以对服务器的文件资源进行hsah值命名,作为文件资源的版本号。
-
配置
// webpack.config.js --> mode: "production" module.exports = { entry: "./src/js/index.[contenthash:10].js", plugins: [ new MiniCssExtractWebpackPlugin({ filename: "css/built.[contenthash:10].css" }) ] }
- hash:每次webpack构建时都会生成一个唯一的hash值。只要是属于同一次构建打包的,每个文件的hash值都一样,这时就会产生一个问题,如果重新打包就会导致所有缓存失效,无论改动多少个文件。
- chunkhash:根据chunk生成的hash值。若打包来源于同一个chunk(即来自同一个入口),那么hash值就一样,因为css是在js中被引入的,所以同属于一个chunk,也并不能做到js和css文件产生独立hash值。
- contenthash:根据文件内容生成hash值。不同文件hash值一定是不一样的。(一般使用此等hash值)
5.3 tree shaking
-
概述:有时候,我们一个模块向外暴露了n个函数、对象或者其他一些数据,但是我们只是用到了其中的几个,那在最终打包的时候,我们只希望把我们所用的打包进去,这时候就要用到tree-shaking,即去除无用代码,以达到减少代码体积的目的。
-
配置:同时满足两个条件webpack会自动开启tree-shaking
1)使用ES6模块化;2)开启production环境。
-
由于webpack版本的原因,打包的时候可能会出现去除入口文件引入的css、less、@babel/polyfill,为了防止出现此等原因,需要在package.json中配置
"sideEffects":["*.css", "*.less"]
,这样子就不会把入口文件的css、less文件意外去除掉。
5.4 code split
code split:代码分割,将一个js文件分割成多个js文件。默认情况下,在单入口文件下,项目中用到的js代码都会被打包成为一个bundle,这就会使得该bundle体积大。
-
方法一:从入口文件入手,采用多入口文件时,就不需要在index.js(原单文件入口时的入口文件)引入了。
// webpack.config.js entry: './src/js/index.js' --> 单入口,对应单页面应用 entry: { // 多入口:有多少个入口文件就输出多少个bundle index: "./src/js/index.js", test: "./src/js/test.js" }, output: { path: resolve(__dirname, "build"), filename: "js/[name].[contenthash:10].js", --> 这样就可以避免每个js文件名字重复 }
缺点:有多少个js文件就需要在配置文件里面添加,重复修改,不够灵活。
-
方法二:配置optimization。(可以与多入口文件用)
-
若多个js文件中都引入了一个模块,打包时会将该模块都打包到各自引用的js文件(即每个js文件都会有该模块一样的代码),为了防止这种情况,可以将该模块单独打包到一个js文件,其他js文件要使用就引入即可,极大减少代码体积,提高代码复用率,此时就需要配置optimization。
-
若是多入口文件,则自动分析多入口chunk中有没有公共的文件,若有会打包成单独一个bundle。
-
若是单入口文件,则将项目用到的第三库中的代码单独打包成一个js文件最终输出。
// webpack.config.js optimization: { splitChunks: { chunks: "all" } }, mode: "production"
-
-
方法三:import动态导入语法,能将某个文件单独打包。在单入口文件index.js中,通过js代码,通过按需导入方式,让某个js文件被单独打包成一个文件。再配合optimization。
解决动态导入import语法报错问题 --> 实际使用
eslint-plugin-import
的规则解决的,故需要下载eslint-plugin-import
插件。// index.js 单入口文件 /*webpackChunkName:'test'*/:为这个文件打包输出的时候命名,还需要配置output:{chunkFilename: [name].chunk.js} import(/* webpackChunkName: 'test' */'.test') .then((mul, count)=>{ // 文件加载成功 }) .catch(()=>{ // 文件加载失败 })
-
方案:单文件入口 + optimization + 入口文件import
5.5 懒加载和预加载
-
懒加载:当文件使用时才加载。放在监听事件的回调函数中(异步事件)。
-
预加载(prefecth):会在使用之前提前加载js文件。(兼容性很差,慎用)
// index.js 入口文件 document.eventListen = function(){ // 该写法与code split中的按需导入一样 import(/*webpackChunName: 'test', webpackPrefetch: true*/'./test').then(()=>{ // 文件加载成功 }) }
-
预加载和正常加载区别:正常加载可以认为是并行加载(同一时间加载多个文件);预加载是其他资源正常加载后,浏览器空闲了,再偷偷加载资源。
5.6 PWA
-
PWA:渐进式网络开发应用程序(离线可访问技术)
-
PWA需要借助workbox插件来完成,
workbox-webpack-plugin
。// webpack.config.js plugins:[ new WorkboxWebpackPlugin.GenerateSW({ // 作用:帮助serviceWorker快速启动,删除旧的serviceWorker。 // 打包后生成 serviceworker 配置文件 clientsClaim: true, skipWaiting: true }) ]
-
配置注册serviceWorker,注册完成后需要运行在服务器
// index.js 入口文件 // 处理兼容性问题 if("serviceWorker" in navigator){ // 绑定监听事件,等待全局load加载完毕 window.addEventListener("load", ()=>{ // 全局load加载完毕后,开始注册serviceWorker。注册时传入的参数文件是workbox-webpack-plugin打包的chunk navigator.serviceWorker.register("/service-worker.js"). then(()=>{ // 注册成功 }) .catch(()=>{ // 注册失败 }) }) }
6 Webpack5
代码优化
从 4 个角度对 webpack 和代码进行了优化:
-
提升开发体验
- 使用
Source Map
让开发或上线时代码报错能有更加准确的错误提示。
- 使用
-
提升 webpack 提升打包构建速度
-
使用
HotModuleReplacement
让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。 -
使用
OneOf
让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。 -
使用
Include/Exclude
排除或只检测某些文件,处理的文件更少,速度更快。 -
使用
Cache
对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。 -
使用
Thead
多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)
-
-
减少代码体积
-
使用
Tree Shaking
剔除了没有使用的多余代码,让代码体积更小。 -
使用
@babel/plugin-transform-runtime
插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。 -
使用
Image Minimizer
对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)
-
-
优化代码运行性能
-
使用
Code Split
对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。 -
使用
Preload / Prefetch
对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。 -
使用
Network Cache
能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。 -
使用
Core-js
对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。 -
使用
PWA
能让代码离线也能访问,从而提升用户体验。
-
7 基础配置
7.1 处理图片资源
Webpack4 :处理图片资源通过 file-loader
和 url-loader
进行处理。
Webpack5: 已经将两个 Loader 功能内置到 Webpack 里了,只需要简单配置即可处理图片资源。
// webpack.config.js
module:{
rules:[
{
test: /\.(png|jpe?g|gif|webp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
}
},
generator: {
// [query]: 添加之前的query参数
filename: "static/imgs/[hash:8][ext][query]",
},
}
]
}
7.2 自动清空上次的资源
// webpack.config.js
output: {
clean: true // 自动将上次打包目录清空
}
7.3 处理其他资源
// webpack.config.js
module: {
rules: [
{
test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
type: "asset/resource",
generator: {
filename: "static/media/[hash:10][ext][query]"
}
}
]
}
7.4 js语法检查
Eslint:可组装的 JavaScript 和 JSX 检查工具。这句话意思就是:它是用来检测 js 和 jsx 语法的工具,可以配置各项功能。使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查。
1. 配置文件
-
配置文件有很多种写法:
新建文件,位于项目根目录,
.eslintrc
,.eslintrc.js
,.eslintrc.json
,以上文件区别在于配置格式不同,通常使用.eslintrc.js
。
package.json
中eslintConfig
:不需要创建文件,在原有文件基础上写Eslint会自动查找和读取它们。
// .eslintrc.js
module.exports = {
// 解析选项
parserOptions: {
ecmaVersion: 6, // ES语法版本
sourceType: "module", // ES模块化
ecmaFeatures: { // ES其他特性
jsx: true // 如果是React项目,就需要开启jsx语法
}
},
// 具体检查规则
rules: {
// "off"或0 -> 关闭规则;
// "warn"或1 -> 开启规则,使用警告级别的错误,warn不会导致程序退出;
// "error"或2 -> 开启规则,使用错误级别的错误,error被触发程序会退出。
},
// 继承其他规则
extends: [
// 开发中一点点写rules规则太费劲,所以有更好的办法,继承现有的规则
// 现有以下有名的规则
// Eslint官方规则:eslint:recommended
// Vue Cli官方规则:plugin:vue/essential
// React Cli官方规则:react-app
]
}
2. Webpack中使用
- 下载
npm i eslint-webpack-plugin eslint -D
。
// webpack.config.js
const EslintWebpackPlugin = require("eslint-webpack-plugin");
module.exports = {
plugins:[
new EslintWebpackPlugin({
// 指定检查文件的根目录
context: path.resolve(__dirname, "src");
})
]
}
// .eslintrc.js
module.exports = {
// 继承Eslint规则
extends: ["eslint:recommended"],
env: {
node: ture, // 启用node中全局变量
browser: true, // 启用浏览器中全局变量
},
parserOptions: {
ecmaVersion: 6,
sourceType: "module"
},
rules: {
"no-var": 2, // 不能使用var定义变量
},
plugins: ["import"], // 解决动态导入语法报错
}
3. VSCode Eslint 插件
在VSCode中下载Eslint插件即可,不用编译就能看到错误,可以提前解决问题。但此时所有文件都会被Eslint插件所检查,dist目录下的打包文件就会报错,但只需检查src下面的文件,无需检查dist目录下的文件。此时要在项目根目录新建.eslintignore
文件。
// .eslintignore
# 忽略dist目录下的所有文件
dist
7.5 js兼容性处理
Babel:主要用于将ES6语法编写的代码转换为向后兼容的js语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
1. 配置文件
-
配置文件有很多种写法:
新建文件,位于项目根目录,
babel.config.js
,babel.config.json
,.babelrc
,.babelrc.js
,.babelrc.json
。
package.json
中babel
:不需要创建文件,在原有的基础上配置。Babel会查找和自动读取他们,所以以上配置文件只需存在一个即可。
// babel.config.js module.exports = { // 预设 presets: [], }
presets预设:简单理解就是一组Babel插件,扩展Babel功能。
@babel/preset-env
:一个智能预设,允许使用最新的JavaScript。@babel/preset-react
:一个用来编译React jsx语法的预设。@babel/preset-typescript
:一个用来编译TypeScript语法的预设。
2. 在Webpack中使用
-
下载
npm i babel-loader @babel/core @babel/preset-env -D
。// babel.config.js module.exports = { presets: [ "@babel/preset-env" ] }
// webpack.config.js module.exports = { module: { rules: [ { test:/.\js$/, exclude: /node_modules/, loader: "babel-loader" } ] } }
7.6 css压缩
-
下载
npm i css-minimizer-webpack-plugin -D
。// webpack.config.js const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin"); plugins:[ new CssMinimizerWebpackPlugin(); // 压缩css ] // 也可以将css压缩写到optimization.minimizer里面 optimization:{ minimizer:[new CssMinimizerWebpackPlugin()] }
8 优化打包构建速度
8.1 多进程打包
-
定义:多进程打包就是开启电脑的多个进程同时干一件事,速度更快。
-
作用:当项目越来越庞大时,打包速度越来越慢,甚至于需要一个下午才能打包出来代码。这个速度是比较慢的。我们想要继续提升打包速度,其实就是要提升 js 的打包速度,因为其他文件都比较少。而对 js 文件处理主要就是 eslint 、babel(babel多进程打包跟webpack4一样)、Terser(js压缩工具) 三个工具,所以我们要提升它们的运行速度。我们可以开启多进程同时处理 js 文件,这样速度就比之前的单进程打包更快了。
-
下载包
npm i -D thread-loader terser-webpack-plugin
。// webpack.config.js // nodejs核心模块,直接使用 const os = require("os"); // cpu核数 const threads = os.cpus().length; // 压缩js的插件,在该插件内进行压缩js代码的多进程配置 const TerserWebpackPlugin = require("terser-webpack-plugin"); plugins: [ new ESLintWebpackPlugin({ threads, // 为语法检查开启多进程 }) ], optimization: { minimizer: [ // 当mode: "production"时,会默认开启js代码压缩,但是需要进行其他配置时,就需要重新写了 new TerserWebpackPlugin({ parallel: threads // 开启js代码压缩多进程 }) ] }
9 优化代码运行性能
9.1 Babel
-
@babel/plugin-transform-runtime
: 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入@babel/plugin-transform-runtime
并且使所有辅助代码从这里引用。 -
Babel 为编译的每个文件都插入了辅助代码,使代码体积过大!Babel 对一些公共方法使用了非常小的辅助代码,比如
_extend
。默认情况下会被添加到每一个需要它的文件中,将这些辅助代码作为一个独立模块,来避免重复引入,从而减少代码体积。 -
下载
npm i @babel/plugin-transform-runtime -D
// webpack.config.js { loader: "babel-loader", options: { plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积 } }
9.2 压缩图片
-
开发如果项目中引用了较多图片,那么图片体积会比较大,将来请求速度比较慢。我们可以对图片进行压缩,减少图片体积。
注意:如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。
-
下载包:
npm i -D image-minimizer-webpack-plugin imagemin
。无损压缩:
npm i -D imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo
。有损压缩:
npm i -D imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo
。// webpack.config.js const ImageMinimizerWebpackPlugin = require("image-minimizer-webpack-plugin"); optimization: { minimizer: { // 压缩图片 new ImageMinimizerPlugin({ minimizer: { implementation: ImageMinimizerPlugin.imageminGenerate, options: { plugins: [ ["gifsicle", { interlaced: true }], ["jpegtran", { progressive: true }], ["optipng", { optimizationLevel: 5 }], [ "svgo", { plugins: [ "preset-default", "prefixIds", { name: "sortAttrs", params: { xmlnsOrder: "alphabetical", }, }, ], }, ], ], }, }, }), } }
-
此时运行会出错,需要安装两个文件到 node_modules 中才能解决
jpegtran.exe:需要复制到
node_modules\jpegtran-bin\vendor
下面,官网http://jpegclub.org/jpegtran/
。optipng.exe:需要复制到
node_modules\optipng-bin\vendor
下面,官网https://optipng.sourceforge.net/
。