背景

由于,我们在开发过程中,会引入很多大大小小的模块,比如一个计算方法通用的大包,封装了很多很多的方法,但是我们只用到了其中一两个方法,往往希望,打包只打包使用到的方法,未使用到的方法,不被打包进最后的代码中,以保持体积的最小化。

方案

webpack为这种情况,提供了tree shaking

写法

  1. 清理一下文件并新增一个math.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   webpack_learn
|- node_modules
|- package.json
|- webpack.config.js
|- yarn.lock
- |- staticFrom
- |- test.txt
|- src
|- let.js
|- get.js
|- index.js
+ |- math.js
|- dist
|- bundle.js
- |- main.js
|- index.html
- |- test.txt
  1. 修改math.js的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    //math.js
+ export function square(x) {//平方
+ return x * x;
+ }
+
+ export function cube(x) {//立方
+ return x * x * x;
+ }
+
+ export function add(x,y) {//求和
+ return x + y;
+ }
+
+ export function cut(x,y) {//计算差
+ return x - y;
+ }

修改index.js

1
2
3
4
5
6
7
    let name = require('./let.js');
let sayName = require('./get.js');
+ let math = require('./math.js');

sayName('大家好,我的名字是'+name)
+ console.log(math.add(2,3))
+ console.log(math.add(9,18))

然后执行打包指令

1
npm run build #也可以yarn build

打包后的代码如下

1
2
3
4
5
!function(n){var t={};function e(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return n[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}e.m=n,e.c=t,e.d=function(n,t,r){e.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:r})},e.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},e.t=function(n,t){if(1&t&&(n=e(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var r=Object.create(null);if(e.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var o in n)e.d(r,o,function(t){return n[t]}.bind(null,o));return r},e.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return e.d(t,"a",t),t},e.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},e.p="",e(e.s=0)}([function(n,t,e){var r=e(1),o=e(2),u=e(3);o("大家好,我的名字是"+r),console.log(u.add(2,3))},function(n,t){n.exports="小明"},function(n,t){n.exports=function(n){console.log(n)}},function(n,t,e){"use strict";function r(n){return n*n}function o(n){return n*n*n}function u(n,t){return n+t}function c(n,t){return n-t}e.r(t),
e.d(t,"square",(function(){return r})),
e.d(t,"cube",(function(){return o})),
e.d(t,"add",(function(){return u})),
e.d(t,"cut",(function(){return c}))}]);

可以看出,我们只使用了math.js中的add方法,但是打包后的内容里math.js中导出的squarecubeaddcut方法都被打包了,这显然不是我们想要的结果。

我们修改package.json的内容

1
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
    {
"dependencies": {
"babel-loader": "^8.0.6",
"copy-webpack-plugin": "^5.1.1",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1"
},
"name": "webpack_learn",
"version": "1.0.0",
"main": "index.js",
"devDependencies": {
"@babel/core": "^7.7.7",
"@babel/preset-env": "^7.7.7",
"babel-core": "^6.26.3",
"babel-preset-env": "^1.7.0"
},
"scripts": {
"build": "webpack",
"watch": "webpack --watch",
"serve": "webpack-dev-server --open",
"dev": "webpack --mode development",
"product": "webpack --mode production",
"testParams": "webpack --mode production --env.production product --param1 1 --param2 2 --explane 这是一个说明",
"showColorAndProgress": "webpack --progress --colors",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
+ "sideEffects": false,
"license": "ISC",
"description": ""
}

然后执行打包指令

1
npm run build #也可以yarn build

然后我们发现,打包指令中,依然包含math.js导出的四个方法

修改index.js文件

1
2
3
4
5
6
7
8
9
10
    let name = require('./let.js');
let sayName = require('./get.js');
- let math = require('./math.js');
+ import {add} from './math.js'
sayName('大家好,我的名字是'+name)
- console.log(math.add(2,3))
- console.log(math.add(9,18))

+ console.log(add(2,3))
+ console.log(add(9,18))

修改webpack.config.js文件增加mode:"production"

再次打包

1
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
!function(e) {
var t = {};
function n(r) {
if (t[r])
return t[r].exports;
var o = t[r] = {
i: r,
l: !1,
exports: {}
};
return e[r].call(o.exports, o, o.exports, n),
o.l = !0,
o.exports
}
n.m = e,
n.c = t,
n.d = function(e, t, r) {
n.o(e, t) || Object.defineProperty(e, t, {
enumerable: !0,
get: r
})
}
,
n.r = function(e) {
"undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {
value: "Module"
}),
Object.defineProperty(e, "__esModule", {
value: !0
})
}
,
n.t = function(e, t) {
if (1 & t && (e = n(e)),
8 & t)
return e;
if (4 & t && "object" == typeof e && e && e.__esModule)
return e;
var r = Object.create(null);
if (n.r(r),
Object.defineProperty(r, "default", {
enumerable: !0,
value: e
}),
2 & t && "string" != typeof e)
for (var o in e)
n.d(r, o, function(t) {
return e[t]
}
.bind(null, o));
return r
}
,
n.n = function(e) {
var t = e && e.__esModule ? function() {
return e.default
}
: function() {
return e
}
;
return n.d(t, "a", t),
t
}
,
n.o = function(e, t) {
return Object.prototype.hasOwnProperty.call(e, t)
}
,
n.p = "",
n(n.s = 2)
}([function(e, t) {
e.exports = "小明"
}
, function(e, t) {
e.exports = function(e) {
console.log(e)
}
}
, function(e, t, n) {
"use strict";
function r(e, t) {
return e + t
}
n.r(t);
var o = n(0);
n(1)("大家好,我的名字是" + o),
console.log(r(2, 3)),
console.log(r(9, 18))
}
]);

最后的结果,可以看出,add方法被打包成了

1
2
3
4
function r(e, t) {
return e + t
}
n.r(t);

而其他的方法并没有被打包进来,实现了代码的裁剪,根据表现来看,如果你希望使用webpacktree-shaking的话,需要如下使用

  1. js文件中,每一个方法,都需要使用exports导出,使用方法的地方,使用impoet {xxx} from 'xx/xx'来按需导入,使用require是无法完成按需导入的
  2. package.json中,增加"sideEffects": false声明文档安全性是否拥有副作用,
  3. config.webpack.js中,增加mode:production或者使用webpackUglifyJSPlugin插件