精华内容
下载资源
问答
  • Tree-Shaking原理

    千次阅读 2020-02-10 22:42:25
    Tree-Shaking性能优化实践 - 原理篇 一. 什么是Tree-shaking 先来看一下Tree-shaking原始的本意 上图形象的解释了Tree-shaking 的本意,本文所说的前端中的tree-shaking可以理解为通过工具"摇"我们的JS...

     

    Tree-Shaking性能优化实践 - 原理篇

     

    一. 什么是Tree-shaking

    先来看一下Tree-shaking原始的本意

     

    上图形象的解释了Tree-shaking 的本意,本文所说的前端中的tree-shaking可以理解为通过工具"摇"我们的JS文件,将其中用不到的代码"摇"掉,是一个性能优化的范畴。具体来说,在 webpack 项目中,有一个入口文件,相当于一棵树的主干,入口文件有很多依赖的模块,相当于树枝。实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 tree-shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。

     

    Tree-shaking 较早由 Rich_Harris 的 rollup 实现,后来,webpack2 也增加了tree-shaking 的功能。其实在更早,google closure compiler 也做过类似的事情。三个工具的效果和使用各不相同,使用方法可以通过官网文档去了解,三者的效果对比,后文会详细介绍。

     

    二. tree-shaking的原理

    Tree-shaking的本质是消除无用的js代码。无用代码消除在广泛存在于传统的编程语言编译器中,编译器可以判断出某些代码根本不影响输出,然后消除这些代码,这个称之为DCE(dead code elimination)。

    Tree-shaking 是 DCE 的一种新的实现,Javascript同传统的编程语言不同的是,javascript绝大多数情况需要通过网络进行加载,然后执行,加载的文件大小越小,整体执行时间更短,所以去除无用代码以减少文件体积,对javascript来说更有意义。

    Tree-shaking 和传统的 DCE的方法又不太一样,传统的DCE 消灭不可能执行的代码,而Tree-shaking 更关注宇消除没有用到的代码。下面详细介绍一下DCE和Tree-shaking。

     

    (1)先来看一下DCE消除大法

     

    Dead Code 一般具有以下几个特征

    •代码不会被执行,不可到达

    •代码执行的结果不会被用到

    •代码只会影响死变量(只写不读)

     

    下面红框标示的代码就属于死码,满足以上特征

    图4

    传统编译型的语言中,都是由编译器将Dead Code从AST(抽象语法树)中删除,那javascript中是由谁做DCE呢?

    首先肯定不是浏览器做DCE,因为当我们的代码送到浏览器,那还谈什么消除无法执行的代码来优化呢,所以肯定是送到浏览器之前的步骤进行优化。

    其实也不是上面提到的三个工具,rollup,webpack,cc做的,而是著名的代码压缩优化工具uglify,uglify完成了javascript的DCE,下面通过一个实验来验证一下。

     

    以下所有的示例代码都能在我们的github中找到,欢迎戳❤

    github.com/lin-xi/tree…

     

    分别用rollup和webpack将图4中的代码进行打包

    图5

    中间是rollup打包的结果,右边是webpack打包的结果

    可以发现,rollup将无用的代码foo函数和unused函数消除了,但是仍然保留了不会执行到的代码,而webpack完整的保留了所有的无用代码和不会执行到的代码。

     

    分别用rollup + uglify和 webpack + uglify 将图4中的代码进行打包

    图6

    中间是配置文件,右侧是结果

    可以看到右侧最终打包结果中都去除了无法执行到的代码,结果符合我们的预期。

     

    (2) 再来看一下Tree-shaking消除大法

    前面提到了tree-shaking更关注于无用模块的消除,消除那些引用了但并没有被使用的模块。

    先思考一个问题,为什么tree-shaking是最近几年流行起来了?而前端模块化概念已经有很多年历史了,其实tree-shaking的消除原理是依赖于ES6的模块特性。

    ES6 module 特点:

    • 只能作为模块顶层的语句出现
    • import 的模块名只能是字符串常量
    • import binding 是 immutable的

    ES6模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,这就是tree-shaking的基础。

    所谓静态分析就是不执行代码,从字面量上对代码进行分析,ES6之前的模块化,比如我们可以动态require一个模块,只有执行后才知道引用的什么模块,这个就不能通过静态分析去做优化。

    这是 ES6 modules 在设计时的一个重要考量,也是为什么没有直接采用 CommonJS,正是基于这个基础上,才使得 tree-shaking 成为可能,这也是为什么 rollup 和 webpack 2 都要用 ES6 module syntax 才能 tree-shaking。

     

    我们还是通过例子来详细了解一下

    面向过程编程函数和面向对象编程是javascript最常用的编程模式和代码组织方式,从这两个方面来实验:

    • 函数消除实验
    • 类消除实验

    先看下函数消除实验

    utils中get方法没有被使用到,我们期望的是get方法最终被消除。

    注意,uglify目前不会跨文件去做DCE,所以上面这种情况,uglify是不能优化的。

    先看看rollup的打包结果

    完全符合预期,最终结果中没有get方法

    再看看webpack的结果

    也符合预期,最终结果中没有get方法

    可以看到rollup打包的结果比webpack更优化

    函数消除实验中,rollup和webpack都通过,符合预期

     

    再来看下类消除实验

    增加了对menu.js的引用,但其实代码中并没有用到menu的任何方法和变量,所以我们的期望是,最终代码中menu.js里的内容被消除

    main.js

    menu.js

    rollup打包结果

    包中竟然包含了menu.js的全部代码

    webpack打包结果

    包中竟然也包含了menu.js的全部代码

    类消除实验中,rollup,webpack 全军覆没,都没有达到预期

    what happend?

    这跟我们想象的完全不一样啊?为什么呢?无用的类不能消除,这还能叫做tree-shaking吗?我当时一度怀疑自己的demo有问题,后来各种网上搜索,才明白demo没有错。

    下面摘取了rollup核心贡献者的的一些回答:

    图7

    • rollup只处理函数和顶层的import/export变量,不能把没用到的类的方法消除掉
    • javascript动态语言的特性使得静态分析比较困难
    • 图7下部分的代码就是副作用的一个例子,如果静态分析的时候删除里run或者jump,程序运行时就可能报错,那就本末倒置了,我们的目的是优化,肯定不能影响执行

     

    再举个例子说明下为什么不能消除menu.js,比如下面这个场景

    function Menu() {
    }
    
    Menu.prototype.show = function() {
    }
    
    Array.prototype.unique = function() {
        // 将 array 中的重复元素去除
    }
    
    export default Menu;
    复制代码

    如果删除里menu.js,那对Array的扩展也会被删除,就会影响功能。那也许你会问,难道rollup,webpack不能区分是定义Menu的proptotype 还是定义Array的proptotype吗?当然如果代码写成上面这种形式是可以区分的,如果我写成这样呢?

    function Menu() {
    }
    
    Menu.prototype.show = function() {
    }
    
    var a = 'Arr' + 'ay'
    var b
    if(a == 'Array') {
        b = Array
    } else {
        b = Menu
    }
    
    b.prototype.unique = function() {
        // 将 array 中的重复元素去除
    }
    
    export default Menu;
    复制代码

    这种代码,静态分析是分析不了的,就算能静态分析代码,想要正确完全的分析也比较困难。

    更多关于副作用的讨论,可以看这个

    图标

    Tree shaking class methods · Issue #349 · rollup/rollupgithub.com

     

    tree-shaking对函数效果较好

    函数的副作用相对较少,顶层函数相对来说更容易分析,加上babel默认都是"use strict"严格模式,减少顶层函数的动态访问的方式,也更容易分析

     

    我们开始说的三个工具,rollup和webpack表现不理想,那closure compiler又如何呢?

    将示例中的代码用cc打包后得到的结果如下:

    天啊,这不就是我们要的结果吗?完美消除所有无用代码的结果,输出的结果非常性感

    closure compiler, tree-shaking的结果完美!

    可是不能高兴得太早,能得到这么完美结果是需要条件的,那就是cc的侵入式约束规范。必须在代码里添加这样的代码,看红线框标示的

    google定义一整套注解规范Annotating JavaScript for the Closure Compiler,想更多了解的,可以去看下官网。

    侵入式这个就让人很不爽,google Closure Compiler是java写的,和我们基于node的各种构建库不可能兼容(不过目前好像已经有nodejs版 Closure Compiler),Closure Compiler使用起来也比较麻烦,所以虽然效果很赞,但比较难以应用到项目中,迁移成本较大。

     

    说了这么多,总结一下:

    三大工具的tree-shaking对于无用代码,无用模块的消除,都是有限的,有条件的。closure compiler是最好的,但与我们日常的基于node的开发流很难兼容。

    tree-shaking对web意义重大,是一个极致优化的理想世界,是前端进化的又一个终极理想。

    理想是美好的,但目前还处在发展阶段,还比较困难,有各个方面的,甚至有目前看来无法解

    决的问题,但还是应该相信新技术能带来更好的前端世界。

    优化是一种态度,不因小而不为,不因艰而不攻。

     

    知识有限,如果错误,请不惜指正,谢谢

     

    下一篇将继续介绍 Tree-Shaking性能优化实践 - 实践

     

    展开全文
  • 写在前面今天这道题目是在和小红书的一位面试官聊的时候:我:如果要你选择一道题目来考察面试者,你最有可能选择哪一道?面试官:那应该就是介绍一下tree shaking及其工作原理?我:为什么...

    写在前面

    今天这道题目是在和小红书的一位面试官聊的时候:

    我:如果要你选择一道题目来考察面试者,你最有可能选择哪一道?

    面试官:那应该就是介绍一下tree shaking及其工作原理?

    我:为什么?

    面试官:是因为最近面了好多同学,大家都说熟悉webpack,在项目中如何去使用、如何去优化,也都或多或少会提到tree shaking,但是每当我深入去问其工作机制或者原理时,却少有人能回答上来。(小声 bb:并不是我想内卷,确实是工程师的基本素养啊,哈哈 ????)

    面试官:那你来回答一下这个问题?

    我:我也用过tree shaking,只是知道它的别名叫树摇,最早是由Rollup实现,是一种采用删除不需要的额外代码的方式优化代码体积的技术。但是关于它的原理,我还真的不知道,额,,,,

    我们平时更多时候是停留在应用层面,这种只是能满足基础的业务诉求,对于后期的技术深挖以及个人的职业发展都是受限的。还是那句老话:知其然,更要知其所以然~

    话不多说,下面我就带大家一起来深入探究这个问题。

    什么是Tree shaking

    Tree shaking 是一种通过清除多余代码方式来优化项目打包体积的技术,专业术语叫 Dead code elimination

    这个概念,我相信大多数同学都是了解的。什么,你不懂?

    不懂没关系,我可以教你啊(不过那是另外的价钱,哈哈 ????)

    走远了,兄弟,让我们言归正传:tree shaking如何工作的呢?

    tree shaking如何工作的呢?

    虽然 tree shaking 的概念在 1990 就提出了,但直到 ES6ES6-style 模块出现后才真正被利用起来。

    ES6以前,我们可以使用CommonJS引入模块:require(),这种引入是动态的,也意味着我们可以基于条件来导入需要的代码:

    let dynamicModule;
    // 动态导入
    if (condition) {
      myDynamicModule = require("foo");
    } else {
      myDynamicModule = require("bar");
    }
    

    但是CommonJS规范无法确定在实际运行前需要或者不需要某些模块,所以CommonJS不适合tree-shaking机制。在 ES6 中,引入了完全静态的导入语法:import。这也意味着下面的导入是不可行的:

    // 不可行,ES6 的import是完全静态的
    if (condition) {
      myDynamicModule = require("foo");
    } else {
      myDynamicModule = require("bar");
    }
    

    我们只能通过导入所有的包后再进行条件获取。如下:

    import foo from "foo";
    import bar from "bar";
    
    if (condition) {
      // foo.xxxx
    } else {
      // bar.xxx
    }
    

    ES6import语法可以完美使用tree shaking,因为可以在代码不运行的情况下就能分析出不需要的代码。

    看完上面的分析,你可能还是有点懵,这里我简单做下总结:因为tree shaking只能在静态modules下工作。ECMAScript 6 模块加载是静态的,因此整个依赖树可以被静态地推导出解析语法树。所以在 ES6 中使用 tree shaking 是非常容易的。

    tree shaking的原理是什么?

    看完上面的分析,相信这里你可以很容易的得出题目的答案了:

    • ES6 Module引入进行静态分析,故而编译的时候正确判断到底加载了那些模块

    • 静态分析程序流,判断那些模块和变量未被使用或者引用,进而删除对应代码

    common.js 和 es6 中模块引入的区别?

    但到这里,本篇文章还没结束。从这道题目我们可以很容易的引申出来另外一道“明星”面试题:common.js 和 es6 中模块引入的区别?

    这道题目来自冴羽大佬的阿里前端攻城狮们写了一份前端面试题答案,请查收[1]

    这里就直接贴下他给出的答案了:

    CommonJS 是一种模块规范,最初被应用于 Nodejs,成为 Nodejs 的模块规范。运行在浏览器端的 JavaScript 由于也缺少类似的规范,在 ES6 出来之前,前端也实现了一套相同的模块规范 (例如: AMD),用来对前端模块进行管理。自 ES6 起,引入了一套新的 ES6 Module 规范,在语言标准的层面上实现了模块功能,而且实现得相当简单,有望成为浏览器和服务器通用的模块解决方案。但目前浏览器对 ES6 Module 兼容还不太好,我们平时在 Webpack 中使用的 exportimport,会经过 Babel 转换为 CommonJS 规范。在使用上的差别主要有:

    1、CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

    2、CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

    3、CommonJs 是单个值导出,ES6 Module可以导出多个

    4、CommonJs 是动态语法可以写在判断里,ES6 Module 静态语法只能写在顶层

    5、CommonJsthis 是当前模块,ES6 Modulethisundefined

    冴羽大佬的文章质量都非常高,也欢迎大家多去支持冴羽大佬,相信看完一定会对你有所收获。

    总结一下

    这是大厂面试问题解析的第二篇了,和之前准备写这一系列的初衷一样:我力求通过一些面试题去发掘自己未曾了解或者未曾深入了解的一个领域。

    面试题更多时候是一个引子,更多是想通过面试题去思考题目背后带来的对某一模块的深入学习和探讨。

    当然,每篇文章也不会只是草草给出答案,我都会尽量深入浅出的给出自己对于这道题目的理解,也会在这个基础上做一些拓展。

    参考资料

    [1]

    阿里前端攻城狮们写了一份前端面试题答案,请查收: https://juejin.cn/post/6844904097556987917

    展开全文
  • Webpack4: Tree-shaking 深度解析

    千次阅读 2019-02-15 04:36:25
    什么是Tree-shaking 所谓Tree-shaking就是‘摇’的意思,作用是把项目中没必要的模块全部抖掉,用于在不同的模块之间消除无用的代码,...至于为什么不完备,可以看一下百度外卖的Tree-shaking原理 Tree-shading原理...

    什么是Tree-shaking

    所谓Tree-shaking就是‘摇’的意思,作用是把项目中没必要的模块全部抖掉,用于在不同的模块之间消除无用的代码,可列为性能优化的范畴。

    Tree-shaking早期由rollup实现,后来webpack2也实现了Tree-shaking的功能,但是至今还不是很完备。至于为什么不完备,可以看一下百度外卖的Tree-shaking原理

    Tree-shading原理

    Tree-shaking的本质用于消除项目一些不必要的代码。早在编译原理中就有提到DCE(dead code eliminnation),作用是消除不可能执行的代码,它的工作是使用编辑器判断出某些代码是不可能执行的,然后清除。

    Tree-shaking同样的也是消除项目中不必要的代码,但是和DCE又有略不相同。可以说是DCE的一种实现,它的主要工作是应用于模块间,在打包过程中抽出有用的部分,用于完成DCE。

    Tree-shaking是依赖ES6模块静态分析的,ES6 module的特点如下:

    1. 只能作为模块顶层的语句出现
    2. import 的模块名只能是字符串常量
    3. import binding 是 immutable的

    依赖关系确定,与运行时无关,静态分析。正式因为ES6 module的这些特点,才让Tree-shaking更加流行。

    主要特点还是依赖于ES6的静态分析,在编译时确定模块。如果是require,在运行时确定模块,那么将无法去分析模块是否可用,只有在编译时分析,才不会影响运行时的状态。

    Webpack4的Tree-shaking

    webpack从第2版本就开始支持Tree-shaking的功能,但是至今也并不能实现的那么完美。凡是具有副作用的模块,webpack的Tree-shaking就歇菜了。

    副作用

    副作用在我们项目中,也同样是频繁的出现。知道函数式编程的朋友都会知道这个名词。所谓模块(这里模块可称为一个函数)具有副作用,就是说这个模块是不纯的。这里可以引入纯函数的概念。

    对于相同的输入就有相同的输出,不依赖外部环境,也不改变外部环境。

    符合上述就可以称为纯函数,不符合就是不纯的,是具有副作用的,是可能对外界造成影响的。

    webpack自身的Tree-shaking不能分析副作用的模块。以lodash-es这个模块来举个例子

    //test.js
    import _ from "lodash-es";
    
    const func1 = function(value){
        return _.isArray(value);
    }
    const func2 = function(value){
        return value=null;
    }
    
    export {
        func1,
        func2,
    }
    //index.js
    import {func2} from './test.js'
    func2()
    复制代码

    上述代码在test.js中引入lodash-es,在func1中使用了loadsh,并且这里不符合纯函数的概念,它是具有副作用的。func2是一个纯函数。

    在index.js中只引入了func2,并且使用了func2,可见整个代码的执行是和func1是没有任何关系的。我们通过生产环境打包一下试试看(Tree-shaking只在生产环境生效)

    main.js 91.7KB,可见这个结果是符合我们的预期的,因为func1函数的副作用,webpack自身的Tree-shaking并没有检测到这里有没必要的模块。解决办法还是用的,webpack的插件系统是很强大的。

    webpack-deep-scope-plugin

    webpack-deep-scope-plugin是一位中国同胞(学生)在Google夏令营,在导师Tobias带领下写的一个webpack插件。(此时慢慢的羡慕)

    这个插件主要用于填充webpack自身Tree-shaking的不足,通过作用域分析来消除无用的代码。

    插件的原理

    这个插件是基于作用域分析的,那么都有什么样的作用域?

    // module scope start
    
    // Block
    
    { // <- scope start
    } // <- scope end
    
    // Class
    
    class Foo { // <- scope start
    
    } // <- scope end
    
    // If else
    
    if (true) { // <- scope start
       
    } /* <- scope end */ else { // <- scope start
      
    } // <- scope end
    
    // For
    
    for (;;) { // <- scope start
    } // <- scope end
    
    // Catch
    
    try {
    
    } catch (e) { // <- scope start
    
    } // <- scope end
    
    // Function
    
    function() { // <- scope start
    } // <- scope end
    
    // Scope
    
    switch() { // <- scope start
    } // <- scope end
    
    // module scope end
    复制代码

    对于ES6模块来说,上面作用域只有function和class是可以被导出的,其他的作用域可以称之为function和class的子作用域并不能被导出实际上归属于父作用域的。

    插件通过分析代码的作用域,进而得到作用域与作用域之间的关系。

    分析作用域

    分析代码的作用域的基础是建立做AST(Abstract Syntax Tree)抽象语法树上面的。这个可以通过escope来完成。

    拿到解析完的AST抽象语法树,利用图的深度优先遍历找到哪些作用域是可以被使用到的,哪些作用域是不可以被使用到的。从而分析作用域之间的关系和导出变量之间的关系。进而执行模块消除。

    插件的不足

    JavaScript中还是有一些代码是不会消去的。

    根作用域的引用
    import { isNull } from 'lodash-es';
    
    export function scope(...args) {
      return isNull(...args);
    }
    
    复制代码

    在根作用域引用到的作用域不会被消除。

    给变量重新赋值
    import _ from "lodash-es";
    
    var func1
    func1 = function(value){
        return _.isArray(value);
    }
    const func2 = function(value){
        return value=null;
    }
    
    export {
        func1,
        func2,
    }
    复制代码

    上述代码中先定义了func1,然后又给func1赋值,这样缺少了数据流分析,同样插件也是不可以的。

    纯函数调用

    引用作者的例子

    import _curry1 from './internal/_curry1';
    import curryN from './curryN';
    import max from './max';
    import pluck from './pluck';
    
    var allPass = /*#__PURE__*/_curry1(function allPass(preds) {
      return curryN(reduce(max, 0, pluck('length', preds)), function () {
        var idx = 0;
        var len = preds.length;
        while (idx < len) {
          if (!preds[idx].apply(this, arguments)) {
            return false;
          }
          idx += 1;
        }
        return true;
      });
    });
    export default allPass;
    复制代码

    当一个匿名函数被包在一个函数调用中(IIFE也是如此),那么插件是无法分析的。但是如果加上/*#__PURE__*/注释的话,这个插件会把这个函数调用当作一个独立的域,tree-shaking是可以生效的。

    探讨的一些问题

    我们都知道在这个ES6泛滥的时代,ES6的代码在项目中出现已经很广泛。(先不考虑线上环境打包成ES5)。上面提到插件的利用作用域来分析。能导出的作用域只有class和funciton。function的情况在上面已经说过,现在来探讨一下class的情况。

    no plugin

    当不使用插件的时候,我们来看一下会不会Tree-shaking,预期是会被Tree-shaking。书写下面这样一段简单的代码。

    class Test{
        init(value){
            console.log('test init');
        }
    }
    export {
        Test,
    }
    复制代码

    我们发现在没有使用插件的情况下,被Tree-shaking了。预期相同。

    no plugin + 副作用

    当我们在不适用插件的情况下,并且引入副作用,观察一下会不会打包,预期是不会打包。书写下面代码。

    class Test{
        init(value){
            console.log('test init');
            return _.isArray(value);
        }
    }
    export {
        Test,
    }
    复制代码

    观察打包结果,main.js 91.7KB,并没有被Tree-shaking,符合预期的结果。

    plugin + 副作用

    当我们使用插件并且代码中存在副作用的情况下,观察打包情况。由于上面的插件原理的铺垫,我们预期这次是可以Tree-shaking的。利用上例代码来测试。

    我们观察可以看出main.js 6.78KB,Tree-shaking生效。

    plugin + 副作用 + babel

    由于用户浏览器对ES6支持度不够的原因,线上的代码不能全是ES6的,有时候我们要把ES6的代码打包成ES5的,放到线上环境来执行。利用上例代码来测试。

    ??? 什么鬼,我没有用到它,为什么这么大??? 一串懵逼

    成也babel,败也babel

    懵逼懵逼,babel成就了线上生产环境,但失去了Tree-shaking优化。我们来看看怎么回事。

    没有副作用的情况

    当去除调副作用的时候我们来打包一下。

    没有找到test init Tree-shaking生效。为什么呢?我们使用babel线上工具编译一下源代码。

    "use strict";
    
    function _instanceof(left, right) { 
        if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
             return right[Symbol.hasInstance](left); 
            } else {
                 return left instanceof right; 
                } 
            }
    
    function _classCallCheck(instance, Constructor) { 
        if (!_instanceof(instance, Constructor)) { 
            throw new TypeError("Cannot call a class as a function"); 
        } 
    }
    
    function _defineProperties(target, props) { 
        for (var i = 0; i < props.length; i++) { 
            var descriptor = props[i]; 
            descriptor.enumerable = descriptor.enumerable || false; 
            descriptor.configurable = true; 
            if ("value" in descriptor) 
                descriptor.writable = true; 
            Object.defineProperty(target, descriptor.key, descriptor); 
        } 
    }
    
    function _createClass(Constructor, protoProps, staticProps) { 
        if (protoProps) 
            _defineProperties(Constructor.prototype, protoProps); 
        if (staticProps) _defineProperties(Constructor, staticProps); 
        return Constructor; }
    
    var Test =
        /*#__PURE__*/
        function () {
            function Test() {
                _classCallCheck(this, Test);
            }
    
            _createClass(Test, [{
                key: "init",
                value: function init(value) {
                    console.log("test init")
                }
            }]);
    
            return Test;
        }();
    复制代码

    上面可以看到最新的babel和webpack有了契合,在Test立即执行函数的地方使用了 /*#__PURE__*/(忘记可以往上看),让下面的IIFE变成可分析的,成功了使用了Tree-shaking。

    有副作用的情况下

    上面探讨情况的时候就得知有副作用的情况下,不可以被打包的。ES6编译代码如下。

    "use strict";
    
    function _instanceof(left, right) { 
        if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
             return right[Symbol.hasInstance](left); 
            } else {
                 return left instanceof right; 
                } 
            }
    
    function _classCallCheck(instance, Constructor) { 
        if (!_instanceof(instance, Constructor)) { 
            throw new TypeError("Cannot call a class as a function"); 
        } 
    }
    
    function _defineProperties(target, props) { 
        for (var i = 0; i < props.length; i++) { 
            var descriptor = props[i]; 
            descriptor.enumerable = descriptor.enumerable || false; 
            descriptor.configurable = true; 
            if ("value" in descriptor) 
                descriptor.writable = true; 
            Object.defineProperty(target, descriptor.key, descriptor); 
        } 
    }
    
    function _createClass(Constructor, protoProps, staticProps) { 
        if (protoProps) 
            _defineProperties(Constructor.prototype, protoProps); 
        if (staticProps) _defineProperties(Constructor, staticProps); 
        return Constructor; }
    
    var Test =
        /*#__PURE__*/
        function () {
            function Test() {
                _classCallCheck(this, Test);
            }
    
            _createClass(Test, [{
                key: "init",
                value: function init(value) {
                    console.log("test init")
                    return _.isArray(value);
                }
            }]);
    
            return Test;
        }();
    复制代码

    这里虽然bable新版契合了webpack,但是还是有一些问题。自己也没有找出是哪里除了问题,作者说JavaScript代码还是有一些是不可以清除的,也许就出现到这里。提供一个作者的插件Demo

    babel的解决方案

    无论是ES6,还是ES5,Tree-shaking不能生效的原因总的归根结底还是因为代码副作用的问题。可想而知代码的书写规范是多么重要。这里我所想出的解决方案有两种。

    1.代码的书写规范,尽量避免副作用。

    书写代码过程中尽量使用纯函数的方式来写代码,保持书写规范,不让代码有副作用。例如把class类引用的副作用改成纯的。

    class Test{
        init(value,_){  //参数引入lodash模块
            console.log('test init');
            return _.isArray(value);
        }
    }
    export{
        Test,
    }
    复制代码

    可以看出,没有副作用的ES6代码编译成ES5,Tree-shaking也是生效的。

    2.灵活使用ES6代码

    两套代码。当浏览器支持的时候,就使用ES6的代码,ES5的代码。此方案可参考浏览器支持ES6的最优解决方案

    总结

    项目中难免会一些用不到的模块占位置影响我们的项目,Tree-shaking的出现也为开发者在性能优化方面提供了非常大的帮助,灵活使用Tree-shaking才能让Tree-shaking发挥作用,处理好项目中代码的副作用可以使项目更加的完美。

    引用文章

    webpack 如何通过作用域分析消除无用代码

    Tree-Shaking性能优化实践 - 原理篇

    原文发布于Webpack4:Tree-shaking深度解析

    展开全文
  • webpack构建之tree-shaking原理是什么

    千次阅读 2020-08-20 17:53:03
    我们在开发一个项目的时候,总会遇到这样的问题,就是比如我们写了一个utils工具类,我们在某一个组件内要用到utils这个类里的其中一个或者某几个方法,但是当我们引入utils的时候,实际是...tree-shaking原理 利用es6

    我们在开发一个项目的时候,总会遇到这样的问题,就是比如我们写了一个utils工具类,我们在某一个组件内要用到utils这个类里的其中一个或者某几个方法,但是当我们引入utils的时候,实际是将utils里的方法全都引入了,这样就会导致将没有必要的东西也引入,包提就会越来越大。那么我们如何解决这个问题呢?是的,tree-shaking.看名字就知道是将哪些没有用的东西都shaking掉。Tree-shaking是一种通过清楚多余代码的方式来优化项目打包体积的技术。

    tree-shaking原理

    利用es6模块的特点:

    • 只能作为模块顶层的语句出现
    • import的模块名只能是字符串常量,不能动态引入
    • import binding是immutable,引入的模块不能再做修改

    其实tree-shaking的概念很早就提出了,但是直到es6的ES6-style模块出现后才被真正的利用起来,这是因为tree-shaking只能在静态modules下工作,Es6模块的加载是静态的。因此整个依赖树可以被静态的推导出解析语法树,所以在es6模块中使用tree-shaking是非常容易的。而且也支持statement(声明级别)。

    之前,我们可以使用commonjs引入模块。require(),这种引入是动态的,也就是意味着我们可以给予条件来导入需要的代码:

    let mynamicModule
    if (condition) {
        mynamicModule = require('aaa')
    } else {
        mynamicModule = require('bbb')
    }

    commonjs的动态特性模块意味着tree-shaking不适用,因为它是不可能确定哪些模块实际运行之前是需要或者是不需要的。在ES6中,进入来完全静态的导入语法:import,这也就是意味着下面的导入是不可行的:

    if (condition) {
        mynamicModule = require('aaa')
    } else {
        mynamicModule = require('bbb')
    }

    只能是通过导入所有的包后再进行有条件的获取:

    import aaa from './aaa'
    import bbb from './bbb'
    if (condition) {
    
    } else {
    
    }

    es6的import语法完美可以使用tree-shaking,因为可以在代码不运行的情况下就能分析出不需要的代码。

    接下来看看如何使用tree-shaking?

    其实从webpack2开始就已经开始支持tree-shaking的特性了,webpack2正式内置支持2015模块,和未引用模块的检测能力。新的webpack4正式版扩展了这个检测能力。通过package.json的sideEffects属性标记。向complier提供提示,表面项目中的哪些文件是pure(纯es2015模块)由此,可以安全的删除文件中未使用的部分。如果使用的webpack4只需要将webpack4设置为production,即可开启tree-shaking.如果使用的是webpack2可能你会发现tree-shaking并不起作用,因为bable会将代码编译成Commonjs模块。而tree-shaking不支持commonjs,所以需要配置不转意:

    options: {
        presets: [
            ['es2015', {modules: false}]
        ]
    }

    sideEffect的一些说明:

    sideEffect是指哪些当import的时候会执行的一些动作。但是不一定会有任何export,比如ployfill,polyfill不对外暴露方法给主程序使用。tree-shaking不能自动识别哪些代码属于side effect.因此手动指定这些代码显的非常重要。如果不指定可能会出现一些意想不到的问题。

    在webpack中是通过package.json的sideEffect属性来实现。

    {
        "name": "tree-shaking",
        "sideEffect": false
    }

    如果所有的代码都不包括副作用,我们就可以简单的将该属性标记为false来告诉webpack,它可以安全的删除用不到的export导出。

    如果代码确实有一些副作用:那么可以提供一个数组:

    {
        "name": "tree-shaking",
        "sideEffect": ["./src/common/polyfill.js"]
    }

    总的来说:

    tree-shaking不支持动态导入,只支持纯静态导入;

    webpack中可以在项目package.json文件中,添加一个sideEffect属性,用于手动指定副作用的脚本。

    展开全文
  • tree shaking

    2021-03-03 14:02:43
    tree-shaking的消除原理是依赖于ES6的模块特性,因为ES6模块的依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,然后进行消除,这也是 ES6 modules 在设计时的一个重要考量,也是为什么没有直接采用 ...
  • webpack之Tree Shaking

    2021-01-03 19:20:42
    目录Tree ShakingTree Shaking定义Tree Shaking原理Dead Code 特征不同模式测试1. development 模式2. production 模式sideEffects副作用使 Tree Shaking 失效副作用导致的问题sideEffects 作用sideEffects 注意事项...
  • Tree shaking学习

    2020-09-02 10:59:53
    一、Tree shaking是什么 Tree shaking 是一个通常用于描述...二、Tree shaking原理 2.1 原理 Tree shaking的本质是移除掉无用的js代码。无用代码移除存在于各种compiler中,compiler会根据代码的export和import的关
  • tree shaking简单分析

    2017-07-10 00:55:49
    tree shaking原理 什么是tree shaking tree shaking首先是由rollup的作者提出的,它是DCE(dead code elimination)的一个实现,通过tree shaking的分析,可以使你代码里没有使用的代码全部删...
  • 一. 什么是Tree-shaking 先来看一下Tree-shaking原始的本意 上图形象的解释了Tree-shaking 的本意,本文所说的前端中的tree-shaking可以理解为通过工具"摇"...
  • 写在前面今天这道题目是在和小红书的一位面试官聊的时候:我:如果要你选择一道题目来考察面试者,你最有可能选择哪一道?面试官:那应该就是介绍一下tree shaking及其工作原理?我:为什...
  • webpack 中的 tree shaking

    2020-10-31 08:44:44
    webpack 中 tree shaking 的用途和原理是什么? 参考链接: https://webpack.docschina.org/guides/tree-shaking/ https://juejin.im/post/6844903544756109319 tree shaking 的基本概念 对无用代码进行剔除,以...
  • 概念:1 个模块可能有多个⽅法,只要其中的某个⽅法使⽤到了,则整个⽂件都会被打到 bundle ⾥⾯去,tree shaking 就是只把⽤到的⽅法打⼊ bundle ,没⽤到的⽅法会在 uglify 阶段被擦除掉。 使⽤: webpack 默认...
  • tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code) 如果将应用程序比作一棵树。 绿色表示实际用到的源码和 library,是树上活...tree shaking原理可以参考Tree-Shaking性...
  • 一、tree shaking (摇树优化) tree shaking概念: 1个模块可能有多个方法,只要其中某个方法使用了,整个文件会被打到bundle中 tree shaking 只将用到的...tree shaking原理:依赖于ES6 moudel特性 只能作为模块
  • 上一篇文章 Tree-Shaking性能优化实践 - 原理篇 介绍了 tree-shaking原理,本文主要介绍 tree-shaking 的实践三. tree-shaking实践webpack2 发布,宣布支持tree-shaking,webpack 3发布,支持作用域提升,生成的...
  • 目录Tree-Shaking原理副作用成也Babel,败也Babel不够屌的UglifyJS那到底该怎么办?如果是使用webpack打包JavaScript库使用rollup打包JavaScript库使用webpack打包工程化项目总结 本文将探讨tree-shaking在当下的...
  • Webpack 中的 TreeShaking 是什么?

    千次阅读 2021-01-13 06:51:27
    tree shaking就是通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的静态结构特性,例如import和export。 原理 ESM import 只能作为模块顶层的语句出现 import 的模块名...
  • 这次学习webpack4不仅仅要会配置,记住核心API,最好还要理解一下webpack更深层次的知识,比如打包原理等等,所以可能会省略一些比较基础的内容,但是希望我可以通过此次学习掌握webpack,更好地应对以后的工作。...
  • Tree-ShakingTree-Shaking,它代表的大意就是删除没用到的代码。这样的功能对于构建大型应用时是非常好的,因为日常...Tree-Shaking原理,通过静态分析,找出未被引用、未被执行、无法到达的代码进行消除,也就是...
  • webpack之摇树优化(tree shaking

    千次阅读 2019-06-25 20:54:14
    tree shaking主要作用是打包项目时会将不用到的方法不打包入项目中,这样得以优化项目体积。 主要原理: 利用ES6模块的特点: 只能作为模块顶层的语句出现 import 的模块名只能是字符串常量 import binding 是 ...
  • webpack的核心开发者Sean在介绍webpack插件系统原理时,隆重介绍了一个中国学生于Google夏令营,在导师Tobias带领下写的一个webpack插件,webpack-deep-scope-analysis-plugin,这个插件能够大大提高webpack tree-...

空空如也

空空如也

1 2 3
收藏数 55
精华内容 22
关键字:

shaking原理tree