webpack基础应用知识总结
# webpack应用总结二
# module
、chunk
、bundle
区别
- module --> 各个源代码文件都是模块,webpack中一切都是模块
- chunk --> 多模块合并成的,如 entry、import、splitChunk。类似于内存中还未产出的代码块
- bundle最终输出的文件,每个chunk都会产生一个bundle文件
entry是webpack的入口文件可以按键名生成chunk名。 import可以生成一个chunk,默认引入到html中 在optimization中定义splitChunks通过cacheGroups缓存分组并命名chunk,然后在htmlWebpackPlugin中引入或排除某个chunk文件。
# loader和plugins的区别
- 两者都是为了扩展webpack的功能。loader它只专注于转化文件(transform)这一个领域,完成压缩,打包,语言翻译; 而plugin不仅只局限在打包,资源的加载上,还可以打包优化和压缩,重新定义环境变量等
- loader运行在打包文件之前(loader为在模块加载时的预处理文件);plugins在整个编译周期都起作用
- 一个loader的职责是单一的,只需要完成一种转换。一个loader其实就是一个Node.js模块。当需要调用多个loader去转换一个文件时,每个loader会链式的顺序执行
- webpack运行的生命周期中会广播出许多事件,plugin会监听这些事件,在合适的时机通过webpack提供的API改变输出结果
- loader模块转换器,如less --> css、识别图片格式
- plugins扩展插件,如htmlWebpackPlugin、friendlyLoaderWebpackPlugin、bundleAnalyzerPlugin、CleanWebpackPlugin、MiniCssExtractPlugin、ParallelUglifyPlugin、
webpack.IgnorePlugin
、webpack.DefinePlugin
常用的laoder有哪些https://www.webpackjs.com/loaders/ (opens new window) 常用的plugins有哪些https://www.webpackjs.com/plugins/ (opens new window)
# 优化babel-loader
module: {
rules: [
{
test: /\.js$/,
// loader: ['babel-loader?cacheDirectory'], // 没有修改的es6代码不会重新编译
use: {
loader: 'babel-loader',
options: {
presets: [
["@babel/preset-env"],
{
useBuiltIns: 'entry',
corejs: 3,
targets: 'last 2 Chrome versions', // 指定兼容性
// use3rBuiltIns: 'false' 不对polyfill做操作。如果引入@babel/polyfill,则无视配置的浏览器兼容性,引入所有polyfill
// use3rBuiltIns: 'entry' 根据浏览器的兼容性,引入浏览器不兼容的polyfill。兼容的就不会引入了
// use3rBuiltIns: 'usage',根据配置的浏览器兼容性,以及代码中用到的api来进行polyfill的按需添加
// 'corejs':2的时候需要在入口文件中手动添加import '@babel/polyfill',会自动动根据browserlistrc替换成浏览器不兼容的所有polyfill
// 这里需要指定core-js版本,如果”corejs“:3,则import `@babel/polyfill` 需要改成 import 'core-js/stable'; import 'regenerator-runtime/runtime'
// core-js@2 分支中已经不会再添加新特性,新特性都会被添加到core-js@3,比如,Array.prototype.flat()
}
]
}
},
include: srcPath,
// exclude: /node_modules/
},
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
配置bable-loader时include 和exclude写一个,这样减少不必要代码
- cache-loader 根据版本号文件名生成cacheKey做为文件的文件名,看文件是否存在,存在则通过对比mtime(modify time),如果相同return缓存,如果不同走下面的loader,它是一个轻量的,不对比内容的。
- babel-loader 通过文件内、option、版本号生成cacheKey做为文件的文件名,读文件是否已经存在,存在则使用缓存,不存在用bable-core转义、zlib压缩、fs写入缓存,它根据文件内容来缓存。它不影响其它loader。
# ignorePlugin和noParse
忽略掉不需要的插件。 比如jquery引入时如果不需要解析可以在noParse中声明
module:{
noParse:/jquery/,//不去解析jquery中的依赖库
}
2
3
# happyPack
- js单线程,开启多进程打包
- 提高构建速度(特别是多核cpu)
const HappyPack = require('happypack')
modles:{
rules: [
// js
{
test: /\.js$/,
// 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
use: ['happypack/loader?id=babel'],
include: srcPath,
// exclude: /node_modules/
}]
}
// plugins中
// happyPack 开启多进程打包
new HappyPack({
// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: 'babel',
// 如何处理 .js 文件,用法和 Loader 配置中一样
loaders: ['babel-loader?cacheDirectory']
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ParallelUglifyPlugin
- webpack内置Uglify工具压缩js
- js单线程,开启多线程更快
- 和happyPack同理
// 引用
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
// plugins中配置
// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
new ParallelUglifyPlugin({
// 传递给 UglifyJS 的参数
// (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
uglifyJS: {
output: {
beautify: false, // 最紧凑的输出
comments: false, // 删除所有的注释
},
compress: {
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 关于开启多进程
如果项目较大时,打包慢,开启多进程能提高速度。但如果项目小,打包快的情况使用,开启多进程会降低速度(进程开销),要按需使用。
# 自动刷新
修改代码后变更的代码显示到浏览器,如果用了webpack-dev-server时会watch自动开启自动刷新
watch: true, // 开启监听,默认为 false
watchOptions: {
ignored: /node_modules/, // 忽略哪些
// 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
// 默认为 300ms
aggregateTimeout: 300,
// 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
// 默认每隔1000毫秒询问一次
poll: 1000
}
2
3
4
5
6
7
8
9
10
11
# 热更新
正常的刷新,整个页面全部刷新,速度比较慢,而且状态会丢失。热更新的话,网页不刷新,新代码生效,状态不丢失。
// 第一步引入
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
// 第二步
entry: {
// index: path.join(srcPath, 'index.js'),
index: [
'webpack-dev-server/client?http://localhost:8080/',
'webpack/hot/dev-server',
path.join(srcPath, 'index.js')
]
},
// 第三步 在devServer中设置 开启
hot:true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
以上开启热更新,修改css样式时能热更新,且不刷新页面,但是修改js还是会刷新页面
// // 开启热更新之后的代码逻辑
// if (module.hot) {
// module.hot.accept(['./math'], () => {
// const sumRes = sum(10, 30)
// console.log('sumRes in hot', sumRes)
// })
// }
2
3
4
5
6
7
上面指有指定的math模块代码修改时会回掉callback函数,显示结果。其它的都会刷新页面。
如果页面确实很复杂,状态很多的情况下可以开启热更新
# DllPlugin动态链接库
- 前端框架如Vue、React,体积大构建慢
- 比较稳定,不常升级版本
- 同一版本只需要构建一次不需要多次构建
- webpack已经内置DllPlugin支持
- (1)DllPlugin --> 打包出dll文件(先预打包)
- (2)DllReferencePlugin --> 使用dll文件(无论做多少改动与升级,只要版本不升级文件不变就一直用那个结果)
提示
无论配置不配置dllPlugin是对程序的源代码没有影响的。
(1)、首先配置webpack.dll.js ,通过dll插件打包出dll文件
点击展开配置示例
const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const { srcPath, distPath } = require('./paths')
module.exports = {
mode: 'development',
// JS 执行入口文件
entry: {
// 把 React 相关模块的放到一个单独的动态链接库
react: ['react', 'react-dom']
},
output: {
// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
// 也就是 entry 中配置的 react 和 polyfill
filename: '[name].dll.js',
// 输出的文件都放到 dist 目录下
path: distPath,
// 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
// 之所以在前面加上 _dll_ 是为了防止全局变量冲突
library: '_dll_[name]', // lib全局变量名
},
plugins: [
// 接入 DllPlugin
new DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一致
// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
// 例如 react.manifest.json 中就有 "name": "_dll_react"
name: '_dll_[name]',
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(distPath, '[name].manifest.json'),
}),
],
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
在package.json中配置script。设置"dll": "webpack --config build/webpack.dll.js"
执行npm run dll
此时dist中会生成react.dll.js和react.manifest.json
(2)、dll的用法:如下 首先在index.html引用react.dll.js文件(产出的js内容 )也可以动态标签方式引入文件。
在dev环境的plugin中AddAssetHtmlPlugin可以将文件动态插入到html中
new AddAssetHtmlPlugin({ filepath: path.join(distPath, 'react.dll.js'), }),
然后配置manifest的地址
// 第一,引入 DllReferencePlugin
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
module: {
rules: [
{
test: /\.js$/,
loader: ['babel-loader'],
include: srcPath,
exclude: /node_modules/ // 第二,不要再转换 node_modules 的代码
},
]
}
// 第三,在plugins中,告诉 Webpack 使用了哪些动态链接库
new DllReferencePlugin({
// 描述 react 动态链接库的文件内容
manifest: require(path.join(distPath, 'react.manifest.json')),
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# webpack 优化构建速度
可用于生产环境的有哪些?
- 优化babel-loader 缓存可以用开发环境也可用于生产环境
- IgnorePlugin 可以避免一些模块的引入
- noParse webpack精准过滤不需要解析的文件
- happyPack开启多进程打包会快一些
- parallelUglifyPlugin 压缩代码
不可用于生产环境的配置有哪些?
- 自动刷新
- 热更新
- dllPlugin
# webpack 性能优化
目标:
- 体积更小
- 合理分包,不重复加载
- 速度更快,内存使用更少
实践:
- 小图片base64编码 用url-loader设置limit
- bundle加hash值,如果没有变的文件hash不变,加载时会命中缓存。
- 懒加载,先把基本文件加载出来,对于大的文件可以异步加载
- 提取公共代码,在optimization中配置splitChunks分割代码块
- ignorePlugin 如:引moment库,里面有多语言忽略掉,自己引入需要的语言
- 使用cdn加速。设置output的publicPath,然后将文件上传到cdn服务器上去。如果是图片的话在配置url-loader时在options中配置publicPatch
- 使用Production,设置mode为production时会自动压缩代码。webpack4.x之后出现的。如果觉得慢可以用ParallelUglifyPlugin开启多线程压缩,根据自已情况如果已经很快了就不要添加了。mode=production的时候,vue、react会自动删除掉调试代码(如开发环境的warning)。production状态会自动开启tree-shaking(必须es6 module才能让tree-shaking生效,如果用commonjs就不行)
- 使用Scope Hosting
# es6 module和commonjs的区别
前端常用的是es6 module,nodejs常用的是commonjs
es6 module是静态引入,是编译时引入
commonjs是动态引入,执行时引入
只有es6 module静态引用才能做静态分析,实现tree-shaking
commonjs 模块输出的是一个值的拷贝,es6模块输出的是值的引用
commonjs 模块运行时加载,es6 模块是编译时输出接口
commonjs 模块加载是一个对象,只有在脚本运行完成才会生成.
es6模块不是对象,它的对外接口只是一种静态定久,在代码静态解析阶段就会生成。
// commonjs方式引入
let apiList = require('../config/api.js')
if(isDev){
// 可以执行时动态引入
apiList = require('../config/api_dev.js')
}
// es6 module方式引入
import apiList from '../config/api.js'
if(isDev) {
// 编译时报错,因为只能静态引入
import apiList from '../config/api.js'
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# tree shaking
一个模块可以有多个方法,只要其中某个方法使用到了,则整个文件会被打到bundle里面,tree shaking就是只把用到的方法打入bundle没用的在
uglify
阶段删除掉原理是利用es6模块的特点,只能作为模块顶层语句出现,import的模块名只能是字符常量
webpack默认支持,在production mode下默认开启
- ”sideEffects“:false所有的代码都没有副作用(都可以进行
tree-shaking
) - 可能把css和
@babel/polyfill
文件干掉,可以设置"sideEffectes:['css']
"
- ”sideEffects“:false所有的代码都没有副作用(都可以进行
# scope Hosting
把多个js的内容放到一个函数中去,这样作用域的数量更少一些,耗费的内存更少一些,也不用频繁的跨作用域去调用
scope hositing的原理是将所有的模块按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量防止命名冲突
这个功能在mode为production下默认是开启的。开发环境需 要用webpack.optimizeModuleConcatenationPlugin插件
- 代码体积更小
- 创建函数作用域更少
- 代码可读性更好
配置:
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenatoinPlugin')
module.exports = {
resolve:{
// 针对npm中的第三方模块优先采用jsnext:main中指向的es6 module模块化语法文件
mainFields: ['jsnext:main','browser', 'main']
},
plugins:[
// 开启scope hosting
new ModuleConcatenationPlugin(),
]
}
2
3
4
5
6
7
8
9
10
11
12
13
# 为什么要进行打包和构建
- 体积更小(Tree-shaking、压缩、合并),加载更快
- 编译高级语言或语法(TS、es6+,模块化、scss)
- 兼容性和错误提示(Pollyfill、postcss、eslint)
- 统一高效的开发环境
- 统一的构建流程和产出标准
- 集成公司构建规范(提测、上线等)