从webpack3+转成Vue CLI 4+遇到的问题, Zlib解压缩速度变慢

问题背景

前端项目使用Vue框架,之前一直使用Vue CLI2 推荐的webpack打包方式, 使用原生webpack进行配置, webpack版本3+. 最近修改了打包方式, 升级到了Vue CLI 4+.

问题描述

前端服务接受服务器传来的数据, 进行解压, 然后进行其他处理. 这里面解压使用的是nodejs提供的zlib.unzip进行解压.
主要代码如下:

// data 为请求到的数据
var buffer = Buffer.from(data, 'base64');
zlib.unzip(buffer, (err, buffer) => {
  // do something
});

在进行效率测试的时候, 发现解压同样的数据时间要比之前多花20s左右. 这是很严重的问题, 需要马上解决.

问题分析

1.首先排除其他代码问题, 将这段代码单独抽取进行测试发现多花的时间就是zlib.unzip, 定位到出现问题代码的位置. 然后进行排查, 对比修改记录发现这段代码最近没有修改过, 初步认为是升级Vue CLI 造成的, 对比了升级前后的代码发现升级前的代码没有问题, 从而判断就是升级造成的
2.使用性能工具对执行过程进行分析, 注意到其中idle时间占了绝大部分时间(这里开启了advanced paint instrumentation,所以时间比实际慢),对比升级前的性能分析,发现idle多出来的时间正是整个过程前后增加的时间

升级后
从webpack3+转成Vue CLI 4+遇到的问题, Zlib解压缩速度变慢_第1张图片
升级前

从webpack3+转成Vue CLI 4+遇到的问题, Zlib解压缩速度变慢_第2张图片
3.定位到了问题之后, 却发现这个问题解决起来无从下手.浏览器的性能工具无法提供帮助, 没办法采用了最笨的方法进行分析. 执行结果不同肯定是有执行的代码不一样才导致的问题, 既然有好的代码也有不好的代码, 那么在运行过程中一行一行对比总会找出这个不一样的代码在哪里,然后找出来为什么代码会产生差异,问题不就解决了吗?沿着这个思路, 把升级前后的代码都运行起来,一行一行对比, 功夫不负苦心人, 终于找到了升级前后代码的差异.

如下代码, 当执行_write函数时, 需要调用process的nextTick函数. 此时, 升级前后的代码在这产生了分歧,

Zlib.prototype._write = function (async, flush, input, in_off, in_len, out, out_off, out_len) {
  // ...
  process.nextTick(function () {
    self._process();
    self._after();
  });
  // ...
};

// 使用Vue CLI 4 执行了 node-libs-browser/mock/process.js
exports.nextTick = function nextTick(fn) {
    var args = Array.prototype.slice.call(arguments);
    args.shift();
    setTimeout(function () {
        fn.apply(null, args);
    }, 0);
};

// 使用webpack 3 执行了 process/browser.js
process.nextTick = function (fun) {
    var args = new Array(arguments.length - 1);
    if (arguments.length > 1) {
        for (var i = 1; i < arguments.length; i++) {
            args[i - 1] = arguments[i];
        }
    }
    queue.push(new Item(fun, args));
    if (queue.length === 1 && !draining) {
        runTimeout(drainQueue);
    }
};

对比两个nextTick,可以发现升级后使用的nextTick 里面使用了setTimeOut(0), 而升级前的nextTick里面虽然也可能会使用setTimeOut但是却加了很多限制条件.

setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在"任务队列"的尾部添加一个事件,因此要等到同步任务和"任务队列"现有的事件都处理完,才会得到执行。

作者:谢小菜
链接:https://segmentfault.com/a/1190000013538587
来源:SegmentFault 思否
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

据此,性能分析中的大量idle时间很可能就是由setTimeOut造成了, 而之所以升级前的时间较短也很可能是由于升级前引用的函数里面对setTimeOut的条件做了限制,不是每次调用都会调用.

解决办法

问题原因找到之后,解决就显得容易了。不过在解决之前,需要知道, 我们使用的zlib库是 Node.js中的,浏览器并不能直接使用, 需要进行中间转换才能变成浏览器能使用的js文件,这里面参与转换的是 browserify-zlib。同样的,process也是 Node.js环境的模块。浏览器也不能直接使用,需要在打包过程中指定。这就是为什么我们在升级Vue CLI 后产生问题的原因,打包方式变了,一些默认配置造成的.

于是,我们使用vue inspect 审查了Vue CLI的webpack 配置, 发现了下面的配置

node: {
    setImmediate: false,
    process: 'mock',
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
 }

参考webpack官方文档的解释,这些选项可以配置是否 polyfill 或 mock 某些 Node.js全局变量和模块。这可以使最初为 Node.js 环境编写的代码,在其他环境(如浏览器)中运行。参考Vue CLI 源码

webpackConfig.node
      .merge({
        // prevent webpack from injecting useless setImmediate polyfill because Vue
        // source contains it (although only uses it if it's native).
        setImmediate: false,
        // process is injected via DefinePlugin, although some 3rd party
        // libraries may require a mock to work properly (#934)
        process: 'mock',
        // prevent webpack from injecting mocks to Node native modules
        // that does not make sense for the client
        dgram: 'empty',
        fs: 'empty',
        net: 'empty',
        tls: 'empty',
        child_process: 'empty'
      })
可以参考里面提到的Issues地址:https://github.com/vuejs/vue-cli/issues/934

这样问题的根源就找到了, 因为升级了Vue CLI, 这里面默认配置的process的方式是mock, 而原来是polyfill,导致了执行nextTick时执行了不同的函数,导致了问题。

所以最终的解决方法就是在vue.config.js里面加入一行代码即可:

config.node
      .set('process', true);

总结

  1. 对webpack还是不够熟悉,对其中一些配置不够了解,升级造成了问题
  2. 对Node.js不够了解,node环境和浏览器之间的差异,和node一些模块在浏览器中怎么使用

参考链接

webpack-node

Vue CLI

setTimeOut

你可能感兴趣的:(前端)