最近使用Webpack遇到了一个坑。

我们构建前端项目的时候,往往希望第三方库(vendors)和自己写的代码可以分开打包,因为第三方库往往不需要经常打包更新。对此Webpack的文档建议用CommonsChunkPlugin来单独打包第三方库。

entry: {
  vendor: ["jquery", "other-lib"],
  app: "./entry"
}
new CommonsChunkPlugin({
  name: "vendor",

  // filename: "vendor.js"
  // (Give the chunk a different name)

  minChunks: Infinity,
  // (with more entries, this ensures that no other module
  //  goes into the vendor chunk)
})

通常为了对抗缓存,我们会给售出文件的文件名中加入hash的后缀——但是——我们编辑了app部分的代码后,重新打包,发现vendor的hash也变化了!

这么一来,意味着每次发布版本的时候,vendor代码都要刷新,即使我并没有修改其中的代码。这样并不符合我们分开打包的初衷。

带着问题我浏览了Github上的讨论,发现了一个神器:dll。

Dll是Webpack最近新加的功能,我在网上并没有找到什么中文的介绍,所以在这里我就简单介绍一下。

Dll这个概念应该是借鉴了Windows系统的dll。一个dll包,就是一个纯纯的依赖库,它本身不能运行,是用来给你的app引用的。

打包dll的时候,Webpack会将所有包含的库做一个索引,写在一个manifest文件中,而引用dll的代码(dll user)在打包的时候,只需要读取这个manifest文件,就可以了。

这么一来有几个好处:

  1. Dll打包以后是独立存在的,只要其包含的库没有增减、升级,hash也不会变化,因此线上的dll代码不需要随着版本发布频繁更新。

  2. App部分代码修改后,只需要编译app部分的代码,dll部分,只要包含的库没有增减、升级,就不需要重新打包。这样也大大提高了每次编译的速度。

  3. 假设你有多个项目,使用了相同的一些依赖库,它们就可以共用一个dll。

如何使用呢?

首先要先建立一个dll的配置文件,entry只包含第三方库:

const webpack = require('webpack');

const vendors = [
  'antd',
  'isomorphic-fetch',
  'react',
  'react-dom',
  'react-redux',
  'react-router',
  'redux',
  'redux-promise-middleware',
  'redux-thunk',
  'superagent',
];

module.exports = {
  output: {
    path: 'build',
    filename: '[name].[chunkhash].js',
    library: '[name]_[chunkhash]',
  },
  entry: {
    vendor: vendors,
  },
  plugins: [
    new webpack.DllPlugin({
      path: 'manifest.json',
      name: '[name]_[chunkhash]',
      context: __dirname,
    }),
  ],
};

webpack.DllPlugin的选项中,path是manifest文件的输出路径;name是dll暴露的对象名,要跟output.library保持一致;context是解析包路径的上下文,这个要跟接下来配置的dll user一致。

运行Webpack,会输出两个文件一个是打包好的vendor.js,一个就是manifest.json,长这样:

{
  "name": "vendor_ac51ba426d4f259b8b18",
  "content": {
    "./node_modules/antd/dist/antd.js": 1,
    "./node_modules/react/react.js": 2,
    "./node_modules/react/lib/React.js": 3,
    "./node_modules/react/node_modules/object-assign/index.js": 4,
    "./node_modules/react/lib/ReactChildren.js": 5,
    "./node_modules/react/lib/PooledClass.js": 6,
    "./node_modules/react/lib/reactProdInvariant.js": 7,
    "./node_modules/fbjs/lib/invariant.js": 8,
    "./node_modules/react/lib/ReactElement.js": 9,
    
    ............

Webpack将每个库都进行了编号索引,之后的dll user可以读取这个文件,直接用id来引用。

Dll user的配置:

const webpack = require('webpack');

module.exports = {
  output: {
    path: 'build',
    filename: '[name].[chunkhash].js',
  },
  entry: {
    app: './src/index.js',
  },
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('./manifest.json'),
    }),
  ],
};

DllReferencePlugin的选项中,context需要跟之前保持一致,这个用来指导Webpack匹配manifest中库的路径;manifest用来引入刚才输出的manifest文件。

运行Webpack之后,结果如下:

对比一下不做分离的情况下打包的结果:

速度快了,文件也小了。

平时开发的时候,修改代码后重新编译的速度会大大减少,节省时间。

如果有多个项目,使用相同的一套库,你可以在打包的时候引用相同的manifest文件,这样就可以在项目之间共享了。

参考:

如果觉得我的文章对你有用,请随意赞赏
已赞赏

14 条评论
Invoker · 2016年07月15日

这种方案和使用externals属性相比有什么优势吗?

+1 回复

xiaoyann · 2016年07月15日

我尝试了下dll发现 user chunk变大了,然后我还得想办法把 dll 放到 html里去。感觉不如这样做简单:https://segmentfault.com/a/1190000005969488#articleHeader14, 既然webpack的chunkhash不可靠,那就不用呗,自己计算

+1 回复

Mcorce · 2016年07月15日

好像并没有什么卵用。你不会每天频繁更新项目吧,要编译速度有什么用?缓存是要对抗,但是前端缓存根本不可靠,不知道什么时候就被清理掉了。

回复

神猪_uuid · 2016年07月15日

CommonChunk因为有各个模块的manifest,所以app的代码变了,vendor也变,把这些manifest提出去就好,官方有例子。不过还是dll好

回复

0

我有试着用ChunkManifestPlugin提出一个chunk-manifest.json文件,但是怎么引入呢?每次都要手动赋值吗

array_huang · 2016年07月15日

谢谢PO主分享,我感觉这个dll方案很好,想应用到我的项目当中,那么我有一个问题想请教一下:我的项目是一个多页应用,目前是用CommonsChunkPlugin(minChunks: 5)把第三方库以及我自己写的一些多个页面都会用到的业务逻辑module打包到一起,因此基本上每个页面只加载俩js,一个是前面提到的这个打包的js,另外一个就是仅在本页面使用到的entry.js。我的问题是,应用了这个DllPlugin后,我的那些公用业务逻辑js要怎么处理呢?还是继续用CommonsChunkPlugin来打包么?

回复

dmyang · 2016年08月01日

打包好的vendor.js,只能通过script标签引入了吧,如果是HtmlWebpackPlugin自动引用的js就会报错。

回复

chenjsh36 · 2016年10月14日

external 也可以告诉webpack说某个库不需要你打包了,但会有个情况,例如当我在external里面设置了react,如果我只是在代码里import了react,那么webpack完全不理你,而当我引用了react的一个动画库如import React from 'react';
import ReactAddonsCssTransitionGroup from 'react-addons-css-transition-group';时,由于这个库的源代码里引用了react,会导致webpack再次去打包整个react,原因是webpack不知道这个库依赖的文件就是react,这就是external的缺陷所在吧

回复

muzidx · 2016年11月30日

请问作者vendors里面需要打包的选项,如果是自己的组件库,路径应该怎么写才对呢?我直接写的vendros = {
react,
redux,
......
} 这些都是从node_modules路径下找的?我的组件a.js 引入的话是从什么路径下找呢?

回复

paranoidjk · 2月26日

@chenjsh36 请教下你的这个结论有经过验证码? 感觉不太科学,既然已经 external 了 react, 就算依赖的react-addons-css-transition-group 也依赖了react, webpack 在resolve的时候为何就 不知道这个库依赖的文件就是react ? 仍然是知道的啊

回复

吴广海 · 3月17日

@paranoidjk 不知道 因为 用的 require('react/lib/ReactTransitionGroup');
https://github.com/facebook/r...

回复

webpack.DllReferencePlugin可以把打包的js链接放入html中吗?我试了,没成功

回复

颜海镜 · 8月8日

太繁琐

回复

阿川 · 8月22日

"通常为了对抗缓存,我们会给售出文件的文件名中加入hash的后缀——但是——我们编辑了app部分的代码后,重新打包,发现vendor的hash也变化了!"
可以直接定义一个lib的entry,直接用CommonsChunkPlugin配置runtime和manifest,即可解决这个问题。

回复

载入中...
王成 王成

479 声望

发布于专栏

前沿开发团队

Make the world be a better place by coding!

538 人关注