摇树(tree shaking)
我次要是想说摇树失败的起因(tree shaking 失败的起因),先讲下摇树自身成果
什么是摇树?
举个例子
首先 webpack.config.js 配置
const webpack = require("webpack");
/**
* @type {webpack.Configuration}
*/
module.exports = {mode: "production"};
在固定 a.js 用 esm 导出,b.js 用 commonjs 导出不变动
// a.js
export function f1() {console.log("11111");
}
export function f2() {console.log("22222");
}
// b.js
exports.f3 = function () {console.log("33333");
};
exports.f4 = function () {console.log("44444");
};
例子 1:import a.js 和 require b.js
// index.js
import {f1} from "./a";
import {f3} from "./b";
console.log(f1);
console.log(f3);
打包后果:a.j 和 b.js 都摇树了,只输入了 f1 和 f3。所以导入用 import,导出 esm 和 commonjs 都能够
例子 2:import a.js 和 import b.js
// index.js
import {f1} from "./a";
const {f3} = require("./b");
console.log(f1);
console.log(f3);
打包后果:a.js 摇,b.js 没摇,输入了 f1、f3、f4。所以导入用 require 不胜利
论断:
摇树只能 import,导出用 esm 和 commonjs 都能够
因为摇树产生在 编译阶段
,只反对 esm 的 import,不反对 commonjs 的 require,因为 esm 是编译时,commonjs 是运行时
摇树失败的起因
三方面可能导致失败:
1、代码没用 import 引入
2、webpack 配置没开启摇树
3、副作用(sideEffects)
4、babel 配置 preset-env 没写 module:false 参数
代码没用 import 引入
这一点下面曾经阐明,必须用 import 导入,导出用 esm 或者 commonjs 都行
webpack 配置没开启摇树
开启摇树两步:
1、usedExports 设置 true,标记无用代码,esm 导出的没应用到的导出函数标记为unused harmony export f2
,commonjs 导出的没应用的导出函数赋值为__webpack_unused_export__
2、terser-webpack-plugin 插件做代码压缩去除无用代码,依据一步两种标记,压缩代码会去除
const webpack = require("webpack");
/**
* @type {webpack.Configuration}
*/
module.exports = {
mode: "none",
optimization:{usedExports:true}
};
mode: production
模式下,默认开启摇树,不必做任何配置,由源码看出none
和development
不会开启摇树,须要手动加这两步,留神要设置minimize:true
, 或者放到 plugins 中
看 webpack 源码默认配置,参考 webpack 视频解说:进入学习
副作用(sideEffects)
先来解释下什么是副作用:批改以后作用域之外的行为都叫副作用
,比方在函数外部,批改 dom,批改全局对象等等
这条次要是针对引入三方包,三方包 package.json 的 sideEffects 字段默认 true 示意有副作用
,能够设置为 false 示意没有副作用,设置为数组列出有副作用的文件
在webpack.config.js 设置 sideEffects:true 示意查看三方包的 sideEffects 字段
,webpack 在用 userExports 标记无用代码时,如果判断不出库中代码是否有副作用,就不会标记,则压缩的时候也没法革除,如果判断有副作用,则更不会标记革除
mode: production
模式下,默认开启摇树,不必做任何配置,usedExports: true
const webpack = require("webpack");
const TerserPlugin = require("terser-webpack-plugin");
/**
* @type {webpack.Configuration}
*/
module.exports = {
mode: "none",
optimization:{
sideEffects:true,
usedExports:true
},
plugins:[new TerserPlugin() ]
};
babel 配置 preset-env 没写 module:false 参数
在文章 我把握的 Babel 配置 中具体解说了 module: false 参数,简略说不设置 false 时,只针对 babel 相干的 runtime 包
的引入会应用 require,设置了 false 引入会应用 import,就能让 webpack 去摇树,回到第一点上
module.exports = {presets: [ [ "@babel/preset-env", { modules: false}, ],
]
};
拆包(splitChunks)
splitChunks 是 webpack 配置下 optimization 下的配置,即优化。看单词了解意思就是拆分多个 chunk。
什么是 chunk
webpack 的实质是把多个 js 模块合并到一个 js 中,即一个入口失去一个输入 js 文件(bundle.js)。
然而导致的问题是,如果这个 bundle.js 文件很大,那么浏览器申请的时候,导致申请工夫很长,首屏长时间白屏。
所以优化伎俩就是把 bundle.js 文件拆分成多个小的 js 文件,同时申请,首屏当然就更快渲染显示。
所以入口文件,chunk 文件,输入文件三者的关系从 原来的一个入口文件对应一个 chunk 最初输入一个 bundle 文件
扭转为 一个入口文件对应多个 chunk 最初输入多个 bundle 文件
三种形式取得 chunk
- 1、入口文件能够生成 chunk,入口文件即 webpack 配置的
entry
选项; - 2、异步申请 import 函数调用 或者 require.ensure 能够生成 chunk;
如:import 函数即咱们在写 vue-router 时写的异步申请路由形式,这里webpackChunkName
能够魔法定义 chunk 名,也可不写
import(/* webpackChunkName: "AboutPage" */'./view/about.vue')
- 3、webpack 配置 splitChunks 手动拆分生成 chunk,最初独立输入到 js 文件
splitChunks 配置
简略配置,把 react 相干包都独自提到一个文件
{
optimization: {
splitChunks: {
chunks: "all", // initial、async 和 all
cacheGroups: {
react: {
name: "react",
test: /[\\/]react(\w)*[\\/]/i,
priority: 10
},
lodash: {
name: "lodash",
test: /[\\/]lodash(\w)*[\\/]/i,
priority: 20,
minChunks:3
},
},
},
},
}
先来看下 webpack 默认的 splitChunks 参数
看图 production
和非 production
模式下有参数不一样,上面这些参数示意主动拆包的条件:
- chunks
重要:
拆包的范畴,默认 async,只针对异步申请的,即下面第二条的 import 函数调用的 chunk 外面;initial 示意只针对初始化入口 entry 的;all 示意最大蕴含 async + entry
- cacheGroups
重要:
自定义拆包规定,name 是 chunk 名,test 正则包名,priority 优先级(因为同一个包可能合乎多个拆包规定,会解决给优先级高的);看图可知,默认会有两个包规定,defaultVendors
规定示意 node_modules
会拆到一个 chunk 包,default
规定示意只有被两个即以上 chunk 援用就要拆到一个 chunk 包
- minChunks
拆分前必须共享模块的最小 chunks 数,能够不必批改
- maxAsyncRequests
浏览器发送异步申请时,最大不超过 30 个申请,即下面第二条的 import 函数调用,能够不必批改
- maxInitialRequests
浏览器申请入口 entry 时,最大不超过 30 个,能够不必批改
热更新
咱们次要是阐明热更新的 module.hot.accept()
先来理解一下热更新怎么配置的?
热更新配置
装包
npm i -D webpack-dev-server html-webpack-plugin
webpack.config.js
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin');
/**
* @type {webpack.Configuration}
*/
module.exports = {
mode: "development",
devServer: {
port: 3000,
open: true,
hot: true,
},
plugins: [new HtmlWebpackPlugin(), ]
};
package.json
"scripts": {"serve": "webpack serve",},
论断
到此热更新配置实现,失常写代码,然而发现问题了,此时 更新页面是整个刷新页面的,并不是部分刷新
,怎么回事呢,原来须要在每个文件中最初加上module.hot.accept()
才会触发部分更新,accept 能够承受两个参数,依赖和回调
exports.f3 = function () {console.log("33333");
};
exports.f4 = function () {console.log("44444");
};
if (module.hot) {module.hot.accept();
}
随即产生了另一个疑难,这太麻烦了吧,每个文件文件都须要去加module.hot.accept()
,然而咱们在理论写下我的项目的时候怎么没有写这句呢?
起因是不管 css、vue、react 的 loader 都帮咱们主动加了这句。
css 有 style-loader,react 有 react-hot-loader,vue 有 vue-loader。
对于 jsx 文件,有vue-jsx-hot-loader
{
test:/\.jsx?$/,
use:['babel-loader','vue-jsx-hot-loader']
}
按需加载
一段时间以来,我始终把 tree shaking 和按需加载一概而论,其实应该离开了解,这里我次要是想说第三方包的按需加载,比方应用 element-ui、lodash、vant
tree shaking 的前提是应用 import 导入,然而按需加载并不需要
还有一个点须要留神:如果是咱们封装的库,如组件库,导出格局依据文件类型不同,如是 js 文件能够为 commonjs + es5、esm + es5;如是 vue 或 react 文件,esm/commonjs + es6/es5 任意都行,因为咱们用 babel-loader 时会排除 node_modules 目录不编译,vue-loader 等会去编译 vue 文件
应用 babel 插件
npm install babel-plugin-component -D
babel.config.js
module.exports = {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage",
corejs: 2,
modules: false,
},
],
],
plugins: [["@babel/plugin-transform-runtime"],
[
"babel-plugin-import",
{
libraryName: "vant",
libraryDirectory: "es",
style: true,
},
"vant",
],
[
"babel-plugin-import",
{
libraryName: "antd",
style: true, // or 'css'
},
],
[
"babel-plugin-import",
{
"libraryName": "lodash",
"libraryDirectory": "","camel2DashComponentName": false, // default: true
},
"lodash",
],
[
"babel-plugin-component",
{
libraryName: "element-ui",
styleLibraryName: "theme-chalk",
},
"element-ui",
],
],
};
结束!