记一次webpack优化 --- 从babelrc和UglifyPlugin下手

Front End Webpack

优化前

先来看优化前打包速度 大的第三方库大概有vue+axios+vueRouter+vuex+elementUI(datepicker, message两个插件)+jquery 打包总体积为2648k, 一共14个chunk(使用了异步路由) image

在我本地打包一次需要31s

image

而在服务器打包时候则要70s以上, 这里就不贴图了.

优化后

优化后时间 打包总体积上升为2700k, 上升了50k

image

本地打包18s

image

服务器打包时间40s

image

提升很明显有没有

修改了哪些地方?

UglifePlugin

主要修改地方还是在UglifyPlugin配置中 由于我是用的并不是webpack自带的,而是独立的uglifyjs-webpack-plugin

其实官方使用的也是这个插件. 只不过官方使用的暂且不是最新版,而webpack4.0-beta已经使用此插件最新版本

用法很简单

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
  plugins: [new UglifyJsPlugin()]
}

如果使用默认配置,那么打包速度并不会有提升.

而且uglifyplugin在打包过程中其实也会进行一些压缩优化,比如内敛静态变量等等.

那么我们可以从这里面入手,去除一切不必要的压缩优化.可以提升压缩速度.

同时.我们需要开启parallel和cache选项,对压缩进行缓存和多线程执行

具体配置规则请参考官方文档UglifyOptions

我的最终配置如下

    new UglifyEsPlugin({
            parallel: true,
            cache: true,
            sourceMap: true,
            uglifyOptions: {
                ecma: 8,
                // 详细规则
                // https://github.com/mishoo/UglifyJS2/tree/harmony#minify-options
                compress: {
                    // 在UglifyJs删除没有用到的代码时不输出警告
                    warnings: false,
                    // 删除所有的 `console` 语句
                    drop_console: true,
                    // 将()=>{return x} 转成 ()=>x
                    // 关闭.eslint有做检查
                    arrows: false,
                    // 转换类似!!a ? b : c → a ? b : c
                    // 关闭.eslint做检查
                    booleans: false,
                    // 转换由计算得来的属性名 {["computed"]: 1} is converted to {computed: 1}.
                    // 关闭,eslint做检查
                    computed_props: false,
                    // 自动转换判断
                    // e.g. a = !b && !c && !d && !e → a=!(b||c||d||e) etc.
                    // 关闭,请自行做规范
                    comparisons: false,
                    // 去掉死代码
                    // 关闭.eslint做检查
                    dead_code: false,
                    // 关闭debugger
                    // eslint做检查
                    drop_debugger: false,
                    // 自动进行静态算术计算
                    // 开启
                    evaluate: true,
                    // 函数声明提升
                    // 默认就是关闭,不需要开启
                    hoist_funs: false,
                    // For example: var o={p:1, q:2}; f(o.p, o.q); is converted to f(1, 2);
                    // 不需要咯
                    hoist_props: false,
                    // 变量提升
                    // 不需要咯
                    hoist_vars: false,
                    //  optimizations for if/return and if/continue
                    // 不需要, eslint做检查
                    if_return: false,
                    /**
                     * 无法用言语表达,自行理解
                     * inline (default: true) -- inline calls to function with simple/return statement:
                        false -- same as 0
                        0 -- disabled inlining
                        1 -- inline simple functions
                        2 -- inline functions with arguments
                        3 -- inline functions with arguments and variables
                        true -- same as 3
                     */
                    inline: false,
                    // join consecutive var statements
                    // 就是将变量声明合并到一个var中
                    // 关闭, eslin做检查
                    join_vars: false,
                    // 自动去除无用的function参数
                    // 关闭. eslint做检查
                    keep_fargs: false,
                    //  Pass true to prevent Infinity from being compressed into 1/0
                    // 禁止将infinity转成1/0
                    keep_infinity: true,
                    // optimizations for do, while and for loops when we can statically determine the condition.
                    // 优化循环
                    // 此处关闭,应该由开发者自行优化
                    loops: false,
                    // negate "Immediately-Called Function Expressions" where the return value is discarded, to avoid the parens that the code generator would insert.
                    // 自行体会
                    negate_iife: true,
                    //  rewrite property access using the dot notation, for example foo["bar"] → foo.bar
                    // 关闭.eslint检查
                    properties: false,
                    // 将只用到一次的function,通过inline方式插入
                    // 关闭.开发者自行把控
                    reduce_funcs: false,
                    // 将静态变量直接lnline紧代码里
                    // 可以开启
                    reduce_vars: true,
                    // 使用逗号运算符连接连续的简单语句
                    // 自行把控
                    sequences: false,
                    /**
                     *  Pass false to disable potentially dropping functions marked as "pure". 
                     * A function call is marked as "pure" if a comment annotation \/*@__PURE__*\/ or \/*#__PURE__*\/ immediately precedes the call. 
                     * For example: \/*@__PURE__*\/foo();
                     * 就是关闭标注纯函数的注释了
                     */
                    side_effects: false,
                    // 去掉重复和无法到达的switch分支
                    // eslint做检查, 以及开发者把控
                    switches: false,
                    // Transforms typeof foo == "undefined" into foo === void 0
                    typeofs: false,
                }
            }

其实很多优化点都是可以通过eslint来检查,而不需要在压缩过程检查

再配合自身的开发习惯以及规范,可以去掉很多压缩检查, 压缩效率就能提升

但是带来的负面影响就是压缩体积会有上升/

因为对于第三方库来说,并不会安装项目配置的eslint来跑.自然就达不到要求.

再少了uglifyplugin的压缩优化,体积就会上升.

以我的例子来看,总体积上升了50k. 尚可以接受.

而打包时间足足提升了30s.

但可能也有人说上线打包不必在乎打包时间.

其实这些都看具体业务需求,以及自身的开发规范来配置.

重要的还是在打包速度和打包体积两者中找出一个最合适的平衡点

babelrc

其实babel并不会影响到打包速度.我也只是顺便提下

我的配置如下

{
    "plugins": [
        [
            "component",
            [{
                "libraryName": "element-ui",
                "styleLibraryName": "theme-default"
            }]]
    ],
    "comments": false,
    "env": {
        "development": {
            "plugins": ["transform-object-rest-spread", "syntax-dynamic-import"]
        },
        "production": {
            "presets": [["es2015", {"modules": false}], "stage-2"],
            "plugins": ["transform-runtime"]
        },
        "test": {
            "presets": ["env", "stage-2"],
            "plugins": ["transform-runtime", "istanbul"]
        }
    }
}

我把babel配置区分成了三个阶段,开发,生产和测试.

在开发过程不使用preset,直接跑原生代码.

在生产环境则使用es2015的preset

也许这样能提高开发环境的编译速度? 暂时不清楚,因为没感觉.一向很快.

这也看个人喜好了.

ps

eslint的作用真的很大很大.

但是在开发阶段使用eslint真的很烦很烦.

所以,我目前的做法就是在开发阶段关闭eslint检查.因为我的vscode有带插件提示

即便检查到有错误,也可以正常编译.

但是在commit的时候添加了一层pre-commit来对修改的文件执行eslint.

这样就确保上传到git的代码是经过eslint检查的

这样既能确保开发不被干扰,也能确保代码能按照规范.