前言

最近重新看了一遍 webpack 提取公共文件的配置。原来觉得这东西是个玄学,都是 “凭感觉” 配置。这篇文章将以解决实际开发遇到的问题为核心,悉数利用 webpack 提取独立文件(模块)的应用。

独立文件在实际开发中一般有两种:

第三方模块 如 Vue React jQuery 等

项目开发编写的独立模块(模块),对于 MPA 多页面开发来说是封装出的一些方法库比如 utils.getQueryString() 或者是每个页面的共同操作;对于SPA 应用来说没有特别的需要分离出模块,但是针对首屏渲染速度的提升,可以将 某些独立模块分离出来实现按需加载。

分离出独立文件的目的:

独立文件一般很少更改或者不会更改,webpack 没必要每次打包进一个文件中,独立文件提取出可以长期缓存。

提升 webpack 打包速度

提取第三方模块

配置externals

Webpack 可以配置 externals 来将依赖的库指向全局变量,从而不再打包这个库。

// webpack.config.js 中

module.exports = {

entry: {

app: __direname +'/app/index.js'

}

externals: {

jquery: 'window.jQuery'

}

...

}

// 模板 html 中

...

...

// 入口文件 index.js

import $ from 'jquery'

其实就是 script 标签引入的jquery 挂载在window下 其他类型 externals 的配置可以去官网查看,这种方法不算是打包提取第三方模块,只是一个变量引入,不是本文讨论的重点。

利用CommonsChunkPlugin

CommonsChunkPlugin 插件是专门用来提取独立文件的,它主要是提取多个入口 chunk 的公共模块。他的配置介绍如下:

配置属性

配置介绍

name 或者 names

chunk 的名称 如果是names数组 相当于对每个name进行插件实例化

filename

这个common chunk 的文件输出名

minChunks

通常情况为一个整数,至少有minChunks个chunk使用了该模块,该模块才会被移入[common chunk]里 minChunks 还可以是Infinity意思为没有任何模块被移入,只是创建当前这个 chunk,这通常用来生成 jquery 等第三方代码库。minChunks还可以是一个返回布尔值的函数,返回 true 该模块会被移入 common chunk,否则不会。默认值是 chunks 的长度。

chunks

元素为chunk名称的数组,插件将从该数组中提取common chunk 可见 minChunks 应该小予chunks的长度,且大于1。如果没有 所有的入口chunks 会被选中

children

默认为false 如果为true 相当于为上一项chunks配置为chunk的子chunk 用于代码分割code split

async

默认为false 如果为true 生成的common chunk 为异步加载,这个异步的 common chunk 是 name 这个 chunk 的子 chunk,而且跟 chunks 一起并行加载

minSize

如果有指定大小,那么 common chunk 的文件大小至少有 minSize 才会被创建。非必填项。

创建一个如下图的目录

package.json 如下

{

"name": "webpacktest",

"version": "1.0.0",

"description": "",

"directories": {

"doc": "doc"

},

"scripts": {

"start": "webpack"

},

"author": "abzerolee",

"license": "ISC",

"devDependencies": {

"html-webpack-plugin": "^2.30.1",

"webpack": "^3.8.1"

},

"dependencies": {

"underscore": "^1.8.3",

}

}

a.js 引入了 underscore 需要进行了数组去重操作,现在需要将underscore分离为独立文件。

// webpack.config.js

entry: {

a: __dirname +'/app/a.js',

vendor: ['underscore']

},

output: {

path: __dirname +'/dist',

filename: '[name].[chunkhash:6].js',

chunkFilename: '[name].[id].[chunkhash:6].js'

},

plugins: [

new webpack.optimize.CommonsChunkPlugin({

name: 'vendor',

}),

new HtmlWebpackPlugin({

template: __dirname +'/app/index.html'

})

]

// a.js

let _ = require('underscore');

let arr = _.filter([1,2,3,2,3,3,5,5], (v, i, self) => self.indexOf(v) === i);

console.log('unique:' +arr);

这样underscore就分离进了 vendor 块,注意的是需要在入口定义 要输出的 [ 独立文件名 ]: [ 需要分离的模块数组 ], 然后在CommonsChunkPlugin中配置 name : [独立文件名]。

当然也可以不用在入口定义,如vue-cli 就是在 在CommonsChunk中配置了minChunks。我们的第三方模块都是通过npm 安装在node_modules 目录下,我们可以通过minChunks 判断模块路径是否含有node_module 来返回true 或 false,前文有介绍minChunks的含义。配置如下:

entry: {

a: __dirname +'/app/a.js', // **注意** 入口没定义vendor

},

output: {

path: __dirname +'/dist',

filename: '[name].[chunkhash:6].js',

chunkFilename: '[name].[id].[chunkhash:6].js'

},

plugins: [

new webpack.optimize.CommonsChunkPlugin({

name: 'vendor',

minChunks: function(module) {

let flag = module.context && module.context.indexOf('node_modules') !== -1;

console.log(module.context, flag);

return flag;

}

}),

new HtmlWebpackPlugin({

template: __dirname +'/app/index.html'

})

]

上述两种方式,对于多页面还是单页面都是可应用的。但是现在的问题是每次入口文件 a.js 修改之后都会造成 vendor重新打包。那么如何解决这个问题呢。

manifest 处理第三方模块应用

我们将 a.js 做一个简单修改:

// 原来

- console.log('unique:' +arr);

// 修改后

+ console.log(arr);

重新打包发现vendor的hash变化了相当于重新打包了underscore,解决的方法是利用一个 manifest 来记录 vendor 的 id ,如果vendor没改变,则不需要重新打包。这就有两种解决方式 :

1. 利用manifest.js

利用CommonsChunkPlugin的chunks特性,提取出 webpack定义的异步加载代码,配置如下:

entry: {

a: __dirname +'/app/a.js',

},

output: {

path: __dirname +'/dist',

filename: '[name].[chunkhash:6].js',

chunkFilename: '[name].[id].[chunkhash:6].js'

},

plugins: [

new webpack.optimize.CommonsChunkPlugin({

name: 'vendor',

minChunks: function(module) {

let flag = module.context && module.context.indexOf('node_modules') !== -1;

console.log(module.context, flag);

return flag;

}

}),

new webpack.optimize.CommonsChunkPlugin({

name: 'manifest',

chunks: ['vendor'],

}),

new HtmlWebpackPlugin({

template: __dirname +'/app/index.html'

})

]

还是修改了 a.js 之后发现 vendor的 hash 值没有变化,如下图:

这里要注意的是chunks: [ 独立文件名 ]。但是,又有但是,要是这么就配置没问题了,就不能叫做玄学了,修改 a.js 的内部代码没问题,如果修改了 require 的模块引入,vendor的hash又有变化了,当然我们可以尽量避免修改文件的依赖引入,但是终归不是最完美的方式。那么终极解决方法是什么呢?DllReferencePlugin,DllPlugin。

2. 利用DllReferencePlugin,DllPlugin

既然动态打包的时候建立 manifest 不行,那么能不能直接把他打包成一个纯净的依赖库,本身无法运行,只是让我们的app 来引入。

那么我们需要完成两步,先webpack.DllPlugin打包dll(纯净的第三方独立文件),然后用DllReferencePlugin 在我们的应用中引用,这样的好处是如果下一个项目还是使用一样的依赖比如react react-dom react-router,可以直接引入这个dll。

配置文件如下:

entry: {

vendor: ['underscore']

},

output: {

path: __dirname +'/dist',

filename: '[name].js',

library: '[name]',

},

plugins: [

new webpack.DllPlugin({

path: __dirname +'/dist/manifest.json',

name: '[name]',

context: __dirname,

}),

],

根据上述配置打包结果如上图,dist目录下现在有一个vender.js 和 manifest.json 注意这里输出的路径配置。DllPlugin配置介绍如下:

配置项

介绍

path

path 是 manifest.json 文件的输出路径,这个文件会用于后续的业务代码打包;

name

name 是 dll 暴露的对象名,要跟 output.library 保持一致;

context

context 是解析包路径的上下文,这个要跟接下来配置的 webpack.config.js 一致。

之后在我们的应用中引入中,配置如下:

entry: {

a: __dirname +'/app/a.js',

},

output: {

path: __dirname +'/dist',

filename: '[name].[chunkhash:6].js',

chunkFilename: '[name].[id].[chunkhash:6].js'

},

plugins: [

new webpack.DllReferencePlugin({

context: __dirname,

manifest: require('./dist/manifest.json'),

}),

new HtmlWebpackPlugin({

template: __dirname +'/app/index.html'

})

]

根据上述配置打包得到a.3e6285.js index.html 如上图,浏览器中打开index.html会显示

Uncaught ReferenceError: vendor is not defined

这里需要在 index.html 中 a.3e6285.js 插入 script 标签

再打开index.html 可以控制台打印出了数组去重的结果。插入标签的这一步可以在打包好独立文件之前,就在模板html 中插入。

到了这里,提取第三方模块的方法,避免重复打包的方法都介绍完毕了。接下来是配置提取自己编写的公共模块方法。

提取项目公共模块

单页面应用的公共模块没有必要提取出单独的文件,因为不必考虑复用的情况。但是对于打包生成的文件过大,我们又想分离出几个模块有需要的时候才加载,其实这并不是提取公共模块,而是代码分割,通过:

require.ensure(dependencies: String[], callback: function(require), chunkName: String)

在callback中定义的 require的模块将会独立打包,并且插入在 html 的head标签,这里就不做更多介绍了。

多页面应用是有必要抽取公共模块的,比如a.js 引用了lib1, b.js 也引用了 lib1 那么lib1,那么我们肯定希望在提取出 lib1 同时还可以提取出第三方库,配置文件如下:

// a.js

let _ = require('underscore');

let lib1 = require('./lib1');

console.log('this is entry_a import lib1');

let arr = _.filter([1,2,3,2,3,3,5,5], (v, i, self) => self.indexOf(v) === i);

console.log(arr);

// b.js

require('./lib1');

var b = 'b';

console.log('this is entry_b import lib1');

// webpack.config.js

entry: {

a: __dirname +'/app/a.js',

b: __dirname +'/app/b.js',

vendor: ['underscore'],

},

output: {

path: __dirname +'/dist',

filename: '[name].[chunkhash:6].js',

chunkFilename: '[name].[id].[chunkhash:6].js'

},

plugins: [

new webpack.optimize.CommonsChunkPlugin({

name: ['chunk', 'vendor'],

minChunks: 2,

}),

new webpack.optimize.CommonsChunkPlugin({

name: 'manifest',

chunks: ['vendor']

}),

new HtmlWebpackPlugin({

template: __dirname +'/app/index.html',

filename: __dirname +'/dist/a.html',

chunks: ['a', 'chunk', 'vendor', 'manifest'],

}),

new HtmlWebpackPlugin({

template: __dirname +'/app/index.html',

filename: __dirname +'/dist/b.html',

chunks: ['b', 'chunk', 'vendor', 'manifest'],

}),

]

}

通过打包后发现生成了如下文件:

可以明确看出生成了chunk.d09623.js 而且 其中就是我们的lib1.js 的库的代码。这里要注意的是Commons.ChunkPlugin的配置 当name 给定数组之后从入口文件中选取 共同引用超过 minChunks 次数的模块打包进name 数组的第一个模块,然后name 数组后面的块 'vendor' 依次打包(查找entry里的key,没有找到相关的key就生成一个空的块),最后一个块包含webpack生成的在浏览器上使用各个块的加载代码,所以插入到页面中最后一个块要最先加载,加载顺序由name数组自右向左。

这里我们使用manifest 去提取了 webpackJsonp 的加载代码,为了防止重复打包库文件,这在前文已经提到过。所以vendor中的加载代码在mainfest.js 中,修改a.js 的console.log, 重新打包后的文件可以发现chunk.d0962e.js, vendor.98054b.js都没有重新打包

所以总结来讲就是多入口配置CommonsChunk

new webpack.optimize.CommonsChunkPlugin({

name: ['生成的项目公共模块文件名', '第三方模块文件名'],

minChunks: 2,

}),

html公共模块提取出去,webpack 填坑之路--提取独立文件(模块)相关推荐

  1. webpack踩坑之路 (2)——图片的路径与打包

    webpack踩坑之路 (2)--图片的路径与打包 刚开始用webpack的同学很容易掉进图片打包这个坑里,比如打包出来的图片地址不对或者有的图片并不能打包进我们的目标文件夹里(bundle).下面我 ...

  2. 填坑之路!SpringBoot导包坑之spring-boot-starter-parent

    填坑之路!SpringBoot导包坑之spring-boot-starter-parent 大誌 2018-11-14 21:03:25 104522 收藏 83 分类专栏: Bug 文章标签: Sp ...

  3. Android Studio 3.0~3.x正式版填坑之路

    序言 总看别人的文章,今天尝试着自己来写一篇.在逛论坛时候,无意间发现Android Studio 3.0正式版本推送更新了,早听说AS 3.0添加了许多新功能,然后手贱迫不及待地想先睹为快,结果正中 ...

  4. 一款车载GPS定位产品后端服务器架构的填坑之路(一)

    文章名字取得有些唬人.这里说"架构"二字也是有些夸大,其实也就是实现一些简单的位置解析功能.数据存储等功能.整理出来,也只是给后来者一些借鉴.希望看到的能够去除糟粕,取其精华. 2 ...

  5. 填坑之路——使用阿里云OSS上传文件

    如下,引入aliyun-oss失败: com.alibaba.cloud:aliyun-oss-spring-boot-starter:unknown 原因: 在2.2.0.RELEASE以后的版本中 ...

  6. NIOS_II填坑之路——EPCS出现“Cannot open flash device”解决办法

    读写EPCS出现"Cannot open flash device"的解决办法 SOPC填坑--第n天 代码看了千万遍,Debug千万遍,alt_flash_open_dev(EP ...

  7. 微信小程序填坑之路其一:wx.request发送与服务端接受

    一.序言 应公司要求要求,要用小程序开发一个信息录入系统.没办法只能听话来填坑. 先介绍一下环境:客户端--小程序:服务端--java:数据库--mysql:服务器--centos7 需求:客户端输入 ...

  8. H5嵌入原生开发小结----兼容安卓与ios的填坑之路

    一开始听说开发H5,以为就是做适配现代浏览器的移动网页,心想不用管IE了,欧也.到今天,发现当初too young too simple,兼容IE和兼容安卓与IOS,后者让你更抓狂.接下来数一下踩过的 ...

  9. gmod的css模块放哪里,webpack打包css报错找不到模块?

    环境:webpack3.10.0 webpack.config.js配置如下: const path = require('path'); const HtmlWebpackPlugin = requ ...

最新文章

  1. AD 10 原理图编译错误
  2. DB2查询结果显示n行
  3. sparksql(2)——dataframe的ap-printSchema、withColum、count、drop、describe、select
  4. 象过河软件试用版_比肩许银川蒋川王天一,象棋软件下出神一样的残局,看完叹为观止...
  5. java中Jackson_java 中的好东西 jackson
  6. Asf PHP扩展框架之预警模块介绍
  7. 将Session写入数据库
  8. 三分钟,带你了解PLM
  9. 人工智能--技术发展史
  10. BIOS 和 EFI 启动光盘制作
  11. 天源财富:XING Mobility与嘉实多合作 为EV提供先进的浸没式冷却电池系统
  12. Cadence电路原理图全部变成黄色如何解决?
  13. 《亲密关系》——[美] 罗兰·米勒 (Rowland S. Miller)
  14. 安卓 linux it之家,IT之家安卓版 7.07:紧凑排版+适配华为小米魅族OV系统级推送等...
  15. hehe 今天今天偶然发现自己的博客可以使用了^_^请问那个.net的网页编辑器哪里可以找到阿?
  16. 很漂亮的蓝色经典CSS导航菜单代码
  17. Meltdown与Spectre:处理器漏洞揭秘
  18. 解决VS Code连接远程服务器使用Python中的matplotiib包画图无法显示的问题
  19. 「 Adams 」提高Adams软件工作性能方法讲解
  20. 无刷电机常用的磁性编码器资料收集

热门文章

  1. html不换行溢出省略号代替,css控制不溢出,不换行,溢出部分省略号显示
  2. BZOJ-1923-外星千足虫-SDOI2010
  3. 2020年, video captioning论文汇总
  4. numpy之reshape()
  5. Luogu P4708 画画 (Burnside引理、组合计数)
  6. 编程 中文等宽_UG编程经典教程
  7. Java提高班(六)反射和动态代理(JDK Proxy和Cglib)
  8. 深入java虚拟机学习 -- 类的加载机制(续)
  9. codevs 2924 数独挑战
  10. 关于STM32系统构架的一点见解