这些都是和webpack去优化我们的项目有关的。
一切为了优化
要解决的问题
1.减少加载代码大小
2.提取公共资源,减少加载次数
代码分割
这是webpack标榜自己的一个特点,但是使用这个是有场景之分的。
多页面应用
- 提取公共依赖
把几个页面之中都用到的依赖打包为一个单独的文件。
浏览器有自动把加载过是js、css、图片等静态资源进行缓存。
比如A页面中用到了C模块,B页面中也用到了C模块,这样吧C模块单独的提取出来,虽然多了一次请求,但是加载A页面的时候就顺便把C加载了,再进入B页面就不用重新请求了
单页面应用
- 减少文件体积,拆分应用
把要异步加载的内容改成异步加载
在单页面应用中,就不存在说把公共的模块提取出来,因为我就这一个页面,那么我把他全部打包在一个页面中是OK的,可能说C模块和D模块同时用到了F模块,那么在webpack打包的时候是不会重复打包的。
单页面的问题是,他把所有东西都打包在一个入口,所以说单页面应用的打包体积是很大的,说以首屏加载会慢,所以要把能异步加载的就异步加载。
为了业务代码的纯净
- 有时候不希望业务代码里混入第三方代码,或webpack配置代码
把第三方的代码和webpack配置代码拆分为单独的文件。
可能我们写vue的时候,vue这个框架是第三方依赖,在比如我们引入的一些包比如axios这些都属于第三方依赖,他跟我们业务代码没什么关系,那么这个时候我们希望我们的业务代码保持纯净,不希望业务代码中混入第三方代码或者webpack的配置代码,这种情况下,我们就需要把业务代码和第三方代码以及webpack配置代码拆分为单独的文件,比如vue的打包就是这个样子的:
vue打包后生成3个文件:(为了代码的纯净性做的区分)
app.js(自己的业务代码)
vendor.js(第三方库、vue框架本身的代码)
manifest.js(webpack的配置代码)
所以一般这么打包
- 多页面应用
主业务代码+公共依赖+第三方包+webpack运行代码
- 单页面应用
主业务代码+异步模块+第三方包+webpack运行代码
如何进行代码分割
版本差异
- webpack3==》CommonsChunkPlugin
- webpack4==》SplitChunkPlugin
我们使用的vue脚手架2或者3都是用的CommonsChunkPlugin
一个插件:clean-webpack-plugin:作用是自动帮我们清除上一次打包的代码
npm install --save-dev clean-webpack-plugin
在webpack.config.js中:
const {CleanWebpackPlugin}=require("clean-webpack-plugin")
然后在注册webpack plugins的地方
new CleanWebpackPlugin()
webpack3的代码分割:
//因为这是webpack自带的,所以不用额外安装
然后在注册webpack plugins的地方
// 假设我们现在根据单页面的需要
// 要分成app.js manifest.js vender.js
// 所以要new 3次
new webpack.optimize.CommonsChunkPlugin({
name:"vender",
minChunks:'infinity'
}),
new webpack.optimize.CommonsChunkPlugin({
name:"app.js",
minChunks:2
}),
new webpack.optimize.CommonsChunkPlugin({
name:"manifest",
minChunks:'infinity'
})
//因为这是webpack自带的,所以不用额外安装
webpack4的代码分割:
项目webpackdemo的目录结构:
webbackdemo
| - src
| - app.js
| - app2.js
| - modulea.js
| - moduleb.js
| - mode1.js
| - package.json
| - webpack.config.js
app.js
import ma from "./modulea.js";
import mb from "./moduleb.js";
import lod from "loadsh"
console.log(22);
app2.js
import ma from "./modulea.js";
import jq from "jquery";
import lod from "loadsh";
mode1.js
module.exports=function(){
console.log("mode1");
}
modulea.js
import m1 from "./mode1.js";
module.exports=function(){
console.log("a");
}
moduleb.js
import m1 from "./mode1.js";
module.exports=function(){
console.log("b");
}
以多入口为例:
如果什么都不配置,他会打包出多个结果文件(比如app.bundle.js和app2.bundle.js),它是没有做任何的公共代码区分,也没有吧公共的代码独立的拿出来。
在webpack.config.js中,在webpack4版本中,无论是压缩还是代码分割,都是写在optimization配置项中
module.exports = {
mode:"development",
optimization:{
splitChunks:{
// name设为true是说
// 根据模块名字来命名打包的结果
name:true,
// 所有的结果都进行打包
chunks:"all"
}
},
entry: {
app: "./src/app.js",
app2:"./src/app2.js"
},……
这样在多入口的情况下,进行代码分割之后,将第三方代码提取出来一个单独的vendor.js
打包后的dist文件夹
| - dist
| - app.bundle.js
| - app2.bundle.js
| - vendors~app~app2.bundle.js
| - vendors~app2.bundle.js
| - vendors~app~app2.bundle.js(内面主要是loadsh)
| - vendors~app2.bundle.js(主要是jQuery)
发现他把modulea和moduleb都引用的loadsh提取出了一个单独的文件,把jQuery提取出一个单独的文件,但是app和app2公共的modulea没有提取。webpack把公共的代码提取出来是好的,但是也产生了一个负面效应,就是多了一个http请求,那我们就要做一个均衡,如果公共的代码只有1kb,再把他单独提出来没有必要为此多一次请求。
所以可以设置minsize
optimization:{
splitChunks:{
name:true,
chunks:"all",
// 默认是1000就是10kb
// 生产模式下是30000就是30kb
minSize:0
// 因为我们的modulea很小
// 所以设为0
}
},
然后打包文件就多了一个app~app2.bundle.js,内容就是mode1.js的内容和modulea.js的内容
optimization:{
splitChunks:{
name:true,
chunks:"all",
minSize:0
},
// 把运行代码,在vue打包出的manifest拿出来
runtimeChunk:true
},
加了runtime之后就多了两个runtime~app.bundle.js和runtime~app2.bundle.js文件,内面就是webpack运行时的配置文件。
有时有这样的需求,需要把modulea和mode1分开打包成两个单独的文件,可以设置cacheGroup:
splitChunks:{
name:true,
chunks:"all",
minSize:0,
cacheGroups:{
mode1:{
// 什么样的文件名需要单独打包
test:/mode1/
}
}
},
这样在原来的基础上就多了mode1~app~app2.bundle.js文件,内面是mode1的内容。
webpack4比webpack3更灵活,webpack4多了minsize和cacheGroup这样的功能。
单页面的分割需要
webpack在打包的时候,无论是单页面还是多页面,如果使用异步加载,都会把异步加载的东西单独打一个包。
现在修改app.js
// import ma from "./modulea.js";
import mb from "./moduleb.js";
import lod from "loadsh"
// 异步加载有两种方式
// 方法一:
import("./modulea").then(function(res){
// res就是拿到的modulea模块
// 因为本身import("./modulea")
// 返回的是promise,所以可以then
})
// 方法二:
// 第一个参数是一个数组
// 意思是如果在modulea中需要使用moduleb
require.ensure(["./moduleb.js"],function(){
require("./modulea.js");
})
console.log(22);
使用方法一或者是方法二然后我们打包,发现多了一个0.bundle.js内容就是异步加载的modulea。所以这就是异步组件的原理。
可以通过这样的方式改变异步加载的模块名
import(/*webpackChunksName:'ma'*/"./modulea").then(function(res){})
代码体积控制
(无非就是压缩和tree-shaking)
压缩
- webpack3
optimizeUglifyPlugin()
就是在注册webpack plugins的地方加上
//也是webpack自带的,不用额外安装
new webpack.optimize.UglifyPlugin();
- webpack4
optimization.minimize
optimization:{
//是否压缩
minimize:true,
splitChunks:{
name:true,
chunks:"all",
}
其实只要让mode为production就可以了
module.exports = {
mode:"production",
optimization:……
所以说webpack4的目的就是干掉配置文件,很多生产和开发模式要用到的东西先做一个预制,所以很多情况下用webpack4打包,我们只要设置mode、entry和output就可以了。
tree-shaking
假设modulea.js
export const a=function (){
console.log("i am a");
}
export const b=function (){
console.log("i am b");
}
在app.js
import {a} from "./modulea.js"
a();
在mode:"development"的模式下打包,会发现在打包代码中b方法也被打包进来。但是本可以不打包进来的。
tree-shaking的作用就是,在文件中export多个内容时,他就是监听模块流,然后在引入的时候只引入部分,那使用tree-shaking的时候就不会把没有引入的内容打包进去。这个功能对export暴露多个内容时效果好,如果像jQuery是一个自执行函数,那他就没办法tree-shaking,因为他的原理是监听export
将mode设为production就开启了tree-shaking。
但是tree-shaking会受到babel的影响,因为babel会把export编译掉,而tree-shaking是根据export做事情的,所以会失败。
解决方法:我们可以改.babelrc文件
{
"presets":[
[
"env",{
//不去编译模块化的东西
"modules":false
}
]
]
}