Rich Harris的模块打包机Rollup提出了JavaScript世界的一个新特性:Tree-Shaking,为打包文件去掉不必要的导出内容。Rollup依赖ES6模块的静态结构(讲解了imports内容和exports内容在JavaScript执行时是不变的)检测来决定哪个导出是不必要的。

  webpack 2的Tree-Shaking还在beta阶段。这篇文章讲解了它是如何工作的。也可以先看个demo:tree-shaking-demo

1、webpack 2如何排除无用导出

  webpack的新beta版本webpack 2通过下面两步来排除无用的导出:

  • 首先,所有的ES6模块文件都合并成一个打包后的文件。在这个文件中,没有被import过的exports是不会被合并进来的。
  • 其次,打包后的文件被合并minified时移除了不用的代码。所以,哪些没有被导出或没有被使用的入口就不会出现在minified压缩后的包里了。没有第一步的操作,不用的代码就不会被移除掉(而是作为一个export被注册进来)。

  如果模块系统有静态结构,无用的导出将在打包的时候被检测出来。所以,webpack 2可以分析理解所有的ES6代码并且只在检测到是ES6模块时才使用tree-shaking。然而,只有import导入和export导出的模块才会被编译为ES5,如果你希望所有的打包文件都编译为ES5,你需要使用一个转译器来处理剩下来的文件。这篇文章中,我们将使用babel 6。

2、ES6 代码

  样例代码含有两个ES6 模块

1
helpers.js
1
main.js

1
2
3
4
5
6
7
// helpers.js
export function foo() {
    return 'foo';
}
export function bar() {
    return 'bar';
}
1
2
3
4
5
// main.js
import {foo} from './helpers';

let elem = document.getElementById('output');
elem.innerHTML = `Output: ${foo()}`;

  注意下导出的

1
heplers
1
bar
模块是没有在任何地方用到的。

3、不使用tree-shaking输出

  正常使用Babel 6来转换的方式是这样的:

1
2
3
{
  presets: ['es2015'],
}

  然而这种方式使用的

1
transform-es2015-modules-commonjs
插件意味着Babel会将ES 6模块通过commonJs模块转换输出,然后webpack 2就不能进行tree-shaking分析了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function(module, exports) {
    
    'use strict';

    Object.defineProperty(exports, "__esModule", {
        value: true
    });
    exports.foo = foo;
    exports.bar = bar;
    function foo() {
        return 'foo';
    }
    function bar() {
        return 'bar';
    }
}

  你会看到

1
bar
成了导出的一部分,这样就成为无用代码存在于打包后文件中,而且不能被辨认出来。

4、 使用tree-shaking输出

  我们想要的是用Babel编译ES6,但不是使用

1
transform-es2015-modules-commonjs
。这时,唯一个可行的方法是在我们的配置文件中列出所有要处理的文件,当然除去那些我们不需要处理的,所以我们的做法是这样的:(demo)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
    plugins: [
        'transform-es2015-template-literals',
        'transform-es2015-literals',
        'transform-es2015-function-name',
        'transform-es2015-arrow-functions',
        'transform-es2015-block-scoped-functions',
        'transform-es2015-classes',
        'transform-es2015-object-super',
        'transform-es2015-shorthand-properties',
        'transform-es2015-computed-properties',
        'transform-es2015-for-of',
        'transform-es2015-sticky-regex',
        'transform-es2015-unicode-regex',
        'check-es2015-constants',
        'transform-es2015-spread',
        'transform-es2015-parameters',
        'transform-es2015-destructuring',
        'transform-es2015-block-scoping',
        'transform-es2015-typeof-symbol',
        ['transform-regenerator', { async: false, asyncGenerators: false }],
    ],
}

  如果我们构建这个项目,

1
helper
模块就是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
function(module, exports, __webpack_require__) {
    
    /* harmony export */ exports["foo"] = foo;
    /* unused harmony export bar */;

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

  现在只导出

1
foo
了,但是
1
bar
仍然在那里。通过minified之后就可以了:

1
2
3
4
5
6
7
function (t, n, r) {
    function e() {
        return "foo"
    }

    n.foo = e
}

  OK,再也没有多余的东西了。这里一个值的注意的地方是,webpack 2的tree-shaking只分析含有导入导出的ES6模块,而且使用其它的编译插件时一定需要注意下。

原文作者:Dr. Axel Rauschmayer

原译:ouven

原文地址: http://www.2ality.com/2015/12/webpack-tree-shaking.html