mrbone / mrbone.github.io

0 stars 0 forks source link

autodll-webpack-plugin #59

Closed mrbone closed 6 years ago

mrbone commented 6 years ago

紧接 #53

mrbone commented 6 years ago

webpack build vs parcel

[TOC] 一入前端深似海,大家对 webpack 稍微熟悉了,parcel-bundler 横空出世,根据官方文档的说法,构建速度比 webpack 快了10倍,真的是这样么? 我们用一个简单的 demo 来对比 webpackparcel 的构建时间。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app"></div>
  <script type="text/javascript" src="./src/index.jsx"></script>
</body>
</html>
//index.jsx
import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(<div>hello react</div>,document.querySelector('#app'));
#babelrc
{
  "presets": ["react"]
}

parcel build

> yarn parcel
yarn run v1.3.2
$ parcel
Server running at http://localhost:1234
✨  Built in 1.32s.

这只是首次 build 的时间,目前的 parcel 默认开启缓存,并且每次 build 都会在当前目录下生成 .cache 目录,之后的构建会从中获取缓存。如下是第二次构建的时间。

> yarn parcel
yarn run v1.3.2
$ parcel
Server running at http://localhost:1234
✨  Built in 341ms.

速度提高了大概4倍。

webpack build

要使用 webpack, 我们需要使用一个简单的 webpack 配置如下:

const path = require('path');

const distFolder = path.resolve(__dirname, 'dist');

module.exports = {
  entry: {
    app:'./src/index',
    vendor: ['react','react-dom']
  },
  output: {
    path: distFolder,
    filename: '[name].js'
  },
  module: {
    rules: [
      {
        test: /\.js(x)?$/,
        loader: 'babel-loader',
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.jsx']
  }
};

接着 webpack 的成绩是多少呢?

$ webpack --config webpack.config.js
[BABEL] Note: The code generator has deoptimised the styling of "/Users/workspace/github/learning/packages/parcel-try/node_modules/react-dom/cjs/react-dom.development.js" as it exceeds the max of "500KB".
Hash: c0695954a45246ccc632
Version: webpack 3.10.0
Time: 4054ms
    Asset    Size  Chunks                    Chunk Names
vendor.js  671 kB       0  [emitted]  [big]  vendor
   app.js  672 kB       1  [emitted]  [big]  app
  [26] ./src/index.jsx 167 bytes {1} [built]
  [27] multi react react-dom 40 bytes {0} [built]
    + 26 hidden modules
✨  Done in 4.49s.

4s 的成绩!可以说 webpack 倍秒成渣渣了。但是作为一个风行这么多年的构建工具怎么可能就这么简单的败了呢。我们稍微修改下 babel-loader 的配置。

{
    test: /\.js(x)?$/,
    loader: 'babel-loader',
+    include: [path.resolve(__dirname,'src')]
}

再 run 一次。

$ webpack --config webpack.config.js
Hash: 47e4eec4bfa3916607ea
Version: webpack 3.10.0
Time: 797ms
    Asset    Size  Chunks                    Chunk Names
vendor.js  726 kB       0  [emitted]  [big]  vendor
   app.js  727 kB       1  [emitted]  [big]  app
  [26] ./src/index.jsx 167 bytes {1} [built]
  [27] multi react react-dom 40 bytes {0} [built]
    + 26 hidden modules
✨  Done in 1.24s.

看来瓶颈在 babel-loader 去做了太多的转换上。这个成绩离开启了缓存的 parcel 已经不远了。
我们这时还能再去开启 webpack 的 DllPluginDllRefrencePlugin 进一步提高压缩时间。

DllPlugin

首先我们再新建一个 webpack.vendor.config.js

const path = require('path');
const webpack = require('webpack');
const DllPlugin = webpack.DllPlugin;

const distFolder = path.resolve(__dirname, 'dist');

module.exports = {
  entry: {
    vendor: ['react','react-dom']
  },
  output: {
    path: distFolder,
    filename: '[name].js'
  },
  plugins: [
    new DllPlugin({
      name:'vendor',
      path: 'dist/[name]-manifest.json'
    })
  ],
  resolve: {
    extensions: ['.js']
  }
};

修改 webpack.config.js 新增 DllReferencePlugin

+plugins: [
+    new DllReferencePlugin({
+      context: '.',
+      manifest: require('./dist/vendor-manifest.json')
+    })
+],

这个时候我们可以通过命令

yarn webpack --config webpack.vendor.js && yarn webpack --config webpack.config.js

来再一次构建,结果如下:

$ webpack --config webpack.vendor.js
Hash: 8812022b15303529e172
Version: webpack 3.10.0
Time: 567ms
    Asset    Size  Chunks                    Chunk Names
vendor.js  726 kB       0  [emitted]  [big]  vendor
  [14] dll vendor 12 bytes {0} [built]
    + 26 hidden modules
✨  Done in 1.23s.
yarn run v1.3.2
$ webpack --config webpack.config.js
Hash: bd3a39de22418f317ea0
Version: webpack 3.10.0
Time: 299ms
 Asset     Size  Chunks             Chunk Names
app.js  3.56 kB       0  [emitted]  app
   [1] ./src/index.jsx 167 bytes {0} [built]
   [2] delegated ./node_modules/react/index.js from dll-reference vendor 42 bytes {0} [built]
   [3] delegated ./node_modules/react-dom/index.js from dll-reference vendor 42 bytes {0} [built]
    + 1 hidden module
✨  Done in 0.96s.

第二次的构建之间只有 299ms,而两次时间之和也约等于未使用 dll 插件之前的时间,这插件的原理其实也就是将那些不常变动的 modules 单独做了一次编译并生成了 manifest.json 而且之后对于 vendor 的编译都可以省略,这样最终的构建时间几乎比 parcel 的时间还要快(parcel 有加 hash 和 html 注入的功能,如果 webpack 也加上应该速度差不多)。
这种方式增加了构建的复杂度,但是对于速度的提升来说还是值得一做的。

autodll-webpack-plugin

使用 DllPlugin 还需要修改我们的构建过程,有没有其他简单点的方法呢?答案是有的,我们可以使用一个 autodll-webpack-plugin 的插件做这种脏活累活。我们新建一个 webpack.autoDll.config.js

const path = require('path');
const webpack = require('webpack');
const AutoDllPlugin = require('autodll-webpack-plugin');

const distFolder = path.resolve(__dirname, 'dist');

module.exports =   {
  entry: {
    app: './src/index'
  },
  output: {
    path: distFolder,
    filename: '[name].js'
  },
  module: {
    rules: [
      {
        test: /\.js(x)?$/,
        loader: 'babel-loader',
        include: [path.resolve(__dirname, 'src')]
      }
    ]
  },
  plugins: [
    new AutoDllPlugin({
      entry:{
        vendor:[
          'react',
          'react-dom'
        ]
      }
    })
  ],
  resolve: {
    extensions: ['.js', '.jsx']
  }
};

接着再 run 一次:

> yarn webpack --config webpack.autoDll.config.js
yarn run v1.3.2
$ webpack --config webpack.autoDll.config.js
Hash: af8b8df5583a7b0804ad
Version: webpack 3.10.0
Time: 2470ms
    Asset     Size  Chunks                    Chunk Names
   app.js  3.58 kB       0  [emitted]         app
vendor.js   726 kB          [emitted]  [big]
   [1] ./src/index.jsx 167 bytes {0} [built]
   [2] delegated ./node_modules/react/index.js from dll-reference vendor_4fbf655873cb2c37456b 42 bytes {0} [built]
   [3] delegated ./node_modules/react-dom/index.js from dll-reference vendor_4fbf655873cb2c37456b 42 bytes {0} [built]
    + 1 hidden module
✨  Done in 3.33s.

首次有 2.5s,之后的 build 呢?

$ webpack --config webpack.autoDll.config.js
Hash: af8b8df5583a7b0804ad
Version: webpack 3.10.0
Time: 311ms
    Asset     Size  Chunks                    Chunk Names
   app.js  3.58 kB       0  [emitted]         app
vendor.js   726 kB          [emitted]  [big]
   [1] ./src/index.jsx 167 bytes {0} [built]
   [2] delegated ./node_modules/react/index.js from dll-reference vendor_4fbf655873cb2c37456b 42 bytes {0} [built]
   [3] delegated ./node_modules/react-dom/index.js from dll-reference vendor_4fbf655873cb2c37456b 42 bytes {0} [built]
    + 1 hidden module
✨  Done in 1.19s.

之后的 build 时间就和使用 DllPlugin 和 DllReferencePlugin 一起的效果差不多了。
简单查看源码发现 AutoDllPluginnode_nodules 下创建了 .cache 文件夹并且每次 rebuild 都会从缓存文件夹中读取对应 module 信息。这就解释了为什么每次我把 dist 清理干净它都能快速的重新 rebuild。
根据其 README 上的说法,每次修改 plugin 的配置或者 install/remove module 的时候都会 rebuild 对应的 Dll。

总结

parcel 主打快横空出世给大家眼前一亮的感觉,也实实在在抓住了我的痛点(windows cygwin 下 build 一次 5分钟让我怎么活),但深入对比下来看 parcel 除了快速上手,目前还是没有能太多能吸引我的地方,当然,这个对比过于简单不够客观。
如果在大规模项目上,parcel 的所有 module 都能通过缓存构建能大大增大对于 webpack 的优势,而且从上面的操作来看,一个简单的 build,webpack 要经过多轮繁琐的配置才能打到和 parcel 同样的效果,这已经让大多数刚接触 webpack 的人抓狂了。
但目前整个周边的生态和配置的灵活度离已经出世几年的 webpack 还有很长的路需要追赶。就目前来看 parcel 更多能适用在: