摇树(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",
],
],
};
结束!
发表回复