前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Import 方式对 Tree-shaking 的影响

Import 方式对 Tree-shaking 的影响

原创
作者头像
猫哥学前班
修改2019-10-09 17:41:14
3.6K0
修改2019-10-09 17:41:14
举报
文章被收录于专栏:猫哥学前班猫哥学前班

最近从小被子那里学了不少 Tree-shaking 的知识,Tree-shaking 译作“摇树优化”,是 DCE(Dead Code Elimination)优化的一种实现。

Webpack 在编译阶段,通过分析模块依赖,对代码中未使用到的对象和 function 进行标注,再通过压缩工具“摇”掉这些多余的代码。

DIY

首先我们使用以下命令,来搭建一套实验环境,加深对 Tree-shaking 的理解。

代码语言:txt
复制
# 初始化项目目录与 package.json
mkdir demo && cd demo
npm init -y

# 安装 webpack,当前版本 4.35.0
npm i -D webpack webpack-cli

# 创建源码目录和文件
mkdir src
touch src/index.js src/util.js

其中 util.js 文件的内容如下:

代码语言:txt
复制
function funcA() {
  return "funcA";
}
function funcB() {
  return "funcB";
}
export { funcA, funcB };

index.js 文件的内容如下:

代码语言:txt
复制
import { funcA } from "./util";

const name = funcA();
console.log(name);

然后我们在项目根目录中,执行 npx webpack 来编译项目代码,得到编译后的代码 dist/main.js 。我们对它进行格式化,能看到以下内容:

代码语言:txt
复制
!(function(e) {
  // ... 省略
})([
  function(e, t, r) {
    "use strict";
    r.r(t);
    console.log("funcA");
  }
]);

从结果上来看,因为util.js 中的funcB 没有被 index.js import,所以最终编译的代码main.js 中没有 funcB 相关的代码。funcB 被 tree-shaking 掉了。

import * as

接下来,我们改变 index.js 中 import 语句的写法:

代码语言:txt
复制
import * as util from "./util";

const name = util.funcA();
console.log(name);

发现最终结果同上:funcB 被 tree-shaking 掉了。

由此可见,import * as 的效果和 import {} 解构的效果是一样的:都是先取到 import 的对象,再基于对象上的属性是否被使用来进行标注。

export default {}

接下来,我们修改下 util.js 的内容,在 export 后面加上一个 default 关键字试试:

代码语言:txt
复制
function funcA() {
  return "funcA";
}
function funcB() {
  return "funcB";
}
export default { funcA, funcB };

由于加上了 default,所以 index.js 文件中对 util 的调用需要加多一级 default 属性的引用:

代码语言:txt
复制
import * as util from "./util";

const name = util.default.funcA();
console.log(name);

同时,又因为我们的 util.js 中只有一条默认的 export default,所以可以直接写成以下同时去掉 * as 和 default 的写法:

代码语言:txt
复制
import util from "./util";

const name = util.funcA();
console.log(name);

我们格式化下最终编译的 dist/main.js 发现,虽然 funcB 没有被调用,但是依然被打包进了源码:

代码语言:txt
复制
!(function(e) {
  // ... 省略
})([
  function(e, n, t) {
    "use strict";
    t.r(n);
    const r = {
      funcA: function() {
        return "funcA";
      },
      funcB: function() {
        return "funcB";
      }
    }.funcA();
    console.log(r);
  }
]);

由此可见,export default 对象被 import 后,挂在 default 上的属性和方法,即使没有被调用,也无法被 tree-shaking。

所以我们在组织模块文件时,应当尽可能避免 export default {A, B, C} 的写法。

Lodash 不同 import 方式的包大小比较

讲完上面的 demo,我们以实际的 lodash 库为例,看看不同的 import 方式对打包后的文件大小的影响。

首先,我们继续在上面的 demo 项目中安装 lodash 模块(当前版本 4.17.15)

代码语言:txt
复制
npm i -S lodash

index.js 修改为如下内容:

代码语言:txt
复制
import { cloneDeep } from 'lodash'

const name = cloneDeep({id:'猫哥学前班'})
console.log(name)

执行 npx webpack 后,可以看到 dist/main.js 文件的大小为 70.9KB

如果我们将第一行函数引入方式修改为按需引用:

代码语言:txt
复制
import cloneDeep from 'lodash/cloneDeep'

const name = cloneDeep({id:'猫哥学前班'})
console.log(name)

编译后文件为 17.8KB,大小减少了 75%。

Webpack 官网提到,要开启模块的 Tree-shaking,需要满足以下四个条件

  1. 使用 ES6 的 import export 语句
  2. 确保 ES6 模块没有被 babel 等编译器转换成 ES5 CommonJS 的形式
  3. 项目 package.json 文件中,要有 "sideEffects" 属性的定义(false 表示所有文件无副作用,可启用 Tree Shaking)
  4. 使用 Webpack 的 production mode

Lodash 为了能支持 Tree Shaking,同时发布了 lodash-es 版本模块,我们将 index.js 修改一下,看看是否有效果:

代码语言:txt
复制
import { cloneDeep } from 'lodash-es'

const name = cloneDeep({id:'猫哥学前班'})
console.log(name)

可以看到,开启 Tree-shaking 后,编译大小减少至 14.6KB ,比上述按需引用的方式更小。

补充:Vue SFC 写法对 Tree-shaking 的影响

Tree-shaking 的使用场景,主要是为了将第三方 npm 模块中未用到的代码剔除出打包后的文件。如果是项目本身的 src 文件,Tree-shaking 的意义并不大。

因为项目本身的业务代码不应该存在能被 shake 掉的 dead code,如果有,可以通过代码覆盖率工具找到它们,并从源码中优化掉。

业务代码可以按照 Vendor 和 Router 的维度进行 Code Splitting,而模块中的代码则需要做好 sideEffects 的声明。

贴张前端技术群里的讨论,仅供参考。

Vue SFC & Tree-shaking
Vue SFC & Tree-shaking

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • DIY
  • import * as
  • export default {}
  • Lodash 不同 import 方式的包大小比较
  • 补充:Vue SFC 写法对 Tree-shaking 的影响
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档