tree-shaking实战

tree-shaking是一个在前端领域比较熟知的东西了。在没有深入了解前,一直以为他在项目中发挥了很大的作用。但是在看了许多文章说tree-shaking并没有什么卵用后,想自己深入了解一下,所以搜了许多博文,自己也在项目中试验了一下。基本了解了大致的流程。所以这篇博文主要是记录一下学习的成果。

tree-shaking是干啥的:

// app.js

export function A(a, b) {
    return a + b
}

export function B(a, b) {
    return a + b
}

// index.js

import {A} from '/app.js'

A(1, 2)

当index.js引用了app.js的A函数,如果tree-shaking起了作用,B函数是不会被打包进最后的bundle的。

但是

世界上有很多但是,而且往往但是后面的内容更加重要。

relies on the static structure of ES2015 module syntax, i.e. import and export.

在webpack官网当中有这样一句话,翻译成人话就是tree-shaking依赖es6的模块引入或输出语法。如果你的模块引入方式是require等等等乱七八糟的东西。tree-shaking将不会起到任何作用。


babel, webpack打包, uglifyJs

这三项东西东西是在我们开发中几乎绕不过去东西。而tree-shaking的关键点就在第一步,babel

虽然我不太了解webpack内部的运行机制(看过运行顺序的相关文章,但一直是懵比状态),但是看过这么多的文章后,上面三项的基本运行顺序还是理解的:

就是babel-loader先去处理js文件,处理过后,webpack进行打包处理,最后uglifyjs进行代码压缩。而关键就是babel怎么去处理js文件

babel的配置文件中有一个preset配置项:


{
  "presets": [
    ["env", {
      "modules": false  //关键点
    }],
    "stage-2",
    "react"
  ]
}


其中presets里面的env的options中有一个 modules: false,这是指示babel如何去处理import和exports等关键子,默认处理成require形式。如果加上此option,那么babel就不会吧import形式,转变成require形式。为webpack进行tree-shaking创造了条件。

在看过这些篇博文后,我本人对于tree-shaking有了一个基本的认识,那就是

babel首先处理js文件,真正进行tree-shaking识别和记录的是webpack本身。删除多于代码是在uglify中执行的

注:webpack在认定某块代码无用后,会再处理过程中写下一段注释。uglifyjs会根据这点注释去进行删除代码。

直接复制这篇博文代码

注释的大体内容(博文很久了,还是在webpack2.0时代,具体内容可能已经变化,但原理应该是不变的。)


function(module, exports, __webpack_require__) {
    /* harmony export */ exports["foo"] = foo;
    /* unused harmony export bar */;

    function foo() {
        return 'foo';
    }
    function bar() {
        return 'bar';
    }
}

tree-shaking,实战代码:

背景:在学习tree-shaking的过程中,如何支持class的tree-shaking是我一直关注的,而且大部分的文章还只停留在理论方面。所以最近自己写了一个demo,支持class的tree-shaking

1.首先使用loader去处理,实验阶段代码,编译成标准es代码。这样webpack内部的编译器才能正确识别代码。


module: {
rules: [
  {
    test: /\.js$/,
    use: [
      {
        loader: 'babel-loader',
        options: {
          presets: ['babel-preset-stage-2', 'babel-preset-react']
        }
      },
      'eslint-loader'
    ],
    exclude: /node_modules/
  }
]
}

2.然后通过webpack打包,并对代码进行tree-shaking.在打包完最后的bundle之后,和输出文件之前,对最后的bundle进行兼容性处理。

plugins: [
    new UglifyJSPlugin(),  //  uglify要在babelPugin的前面
    new BabelPlugin({  //在这个插件内部进行最后bundle的兼容性处理
      test: /\.js$/,
      babelOptions: {
        presets: [env]
      }
    }),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    }),
    new HtmlWebpackPlugin({
      template: 'index.html'
    }),
  ]


最后总结步骤:

  1. 先编译实验性质代码为标准代码,会涉及到babel-preset-stage-x插件

  2. webpack打包代码并进行tree-shaking识别。

  3. uglifyjs进行代码压缩,并根据webpack标识删除多余代码

4.对最后的代码进行兼容性处理涉及到babel-preset-env插件。

第三方类库的tree-shaking

在研究了许多第三方类库后,基本得出了一个结论:tree-shaking本质上是不能对大部分的第三方类库进行tree-shaking的.上面的实战代码,对于自己写的代码还有点用,但是只要涉及到第三方类库,基本就是歇菜。

ramda的输出文件:
大部分的react ui组件,以及函数工具类库。基本都是这样来进行模块输出,和引用的。


export { default as F } from './F';
export { default as T } from './T';
export { default as __ } from './__';
export { default as add } from './add';
export { default as addIndex } from './addIndex';
export { default as adjust } from './adjust';
export { default as all } from './all';
export { default as allPass } from './allPass';
export { default as always } from './always';
export { default as and } from './and';
export { default as any } from './any';
export { default as anyPass } from './anyPass';
export { default as ap } from './ap';
export { default as aperture } from './aperture';
export { default as append } from './append';
export { default as apply } from './apply';
export { default as applySpec } from './applySpec';

...

这样的文件结构是无法进行tree-shaking的


// 只要是你在代码中引用了一个方法,那么你肯定将所有的代码都引入了进来
import {path} from 'ramda' 


唯一的解决方法就是直接到具体的文件夹去引用,而不是在根index.js里面去引用。


import path from 'ramda/src/path'


但是如果每一次引用都是这样去写,开发的效率就无法保证,所以基本上有点追求的技术团队,基本上会再类库的基础上,开发一个babel的插件以支持代码的tree-shaking。

像著名的antd,以及ramda等都开发了相应的插件。

babel-plugin-ramda:此插件会默认将你写的代码转化为tree-shaking的代码


from:

import {path} from 'ramda' 

to

import path from 'ramda/src/path'


而本人也在了解了以上东西后,为本公司的ui组件开发了一个插件:

babel-plugin-b-rc

在看过ramda的代码,以及他的构建过程和对于tree-shaking的支持后,学到了很多。有的时候学习webpack以及如何优化网页的时候,不知道如何下手。我的经验就是找到一个技术点,进行深入。比如我得研究点就是tree-shaking,在一路研究过后,babel,babel-preset, babel-plugin,webpack,webpack-plugin,都有了一定的了解。这几个功能点基本上花费了我将近一个多月的时间。从找材料,到动手完成自己的一个babel插件,收获颇丰。学习babel以及webpack的过程,其本质是学习如何优化网页的过程!!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348