webpack3的CommonsChunkPlugin插件详解

webpack打出来的包在不做处理的情况下是非常大的,所有依赖都被塞进一个文件中,文件中有业务代码,有业务代码依赖的第三方库代码,还有webpack生成的运行时代码等。这样的一个文件不方便静态资源缓存,并且初始化页面的时候下载了所有的JS这是没必要的,拖慢了页面速度。所以对于webpack打包的资源文件进行分割按需加载是很重要的一件事情。

webpack4都出来了为啥要写一篇关于webpack3的文章。

目前webpack3应用的还是很多,并且学习相关知识协查找过相关资料很多遍,所以这次总结一下通过webpack3分割代码的方法,方便后期需要的时候方便查阅。

在webpack3中使用的分割thunk方法主要是使用webpack自带的插件(webpack.optimize.CommonsChunkPlugin)实现的。

首先通过webpack来构建项目

目录结构:

|— src

​ |— index.html

​ |— indexa.js

​ |— indexb.js

|— webpack.config.js

|— node_modules

​ |— jquery/

indexa.js

import $ from 'jquery'$() // 调用一下console.log('我是indexa.js')

indexb.js

import $ from 'jquery'$() // 调用一下console.log('我是indexb.js')

webpack.config.js

// output中的path需要绝对路径
let path = require('path')
// 用于将打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {entry: {indexa: './src/indexa.js',indexb: './src/indexb.js'},output: {path: path.resolve('./dist/'),filename: '[name].[chunkHash].js'},plugins: [new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})]
}

上面的示例中有两个入口,一个是pagea.js,另一个是pageb.js。这两个入口都引入了jquery.js,并且打包结果中我们看到jquery.js被同时打到了两个入口文件中。

在这里插入图片描述

提取公共第三方库jquery

这样的结果显然不是我们期望的,我们期望两个入口都引入的jquery被打到单独的包中,然后在两个入口引入这个包即可。这就需要借助webpack.optimize.CommonsChunkPlugin插件,下面修改webpack.config.js文件为:

// output中的path需要绝对路径
let path = require('path')
// 用于将打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')
let webpack = require('webpack')
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {indexa: './src/indexa.js',indexb: './src/indexb.js',jquery: ['jquery'] // 依赖的第三方库node_modules中},output: {path: path.resolve('./dist/'),filename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({name: 'jquery', // 如果有该名称的chunk则选择这个chunk提取公共文件,这里是jquery,如果没有则生成的文件是这个名称的chunk}),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})]
}

通过CommonsChunkPlugin插件我们提取了在indexa和indexb中都引入的jquery库这样打包结果中pagea.js和pageb.js就大幅减小了。

在这里插入图片描述

提取自定义公共模块

通常我们不止有第三方的公共模块,我们自己也会写一些公用的工具方法。现加入公用工具方法文件utils.js。

utils.js

function common () {console.log('我是工具方法')
}export {common
}

修改pagea.js文件

import $ from 'jquery'
import {common} from './utils.js'common() // 新加的$() // 调用一下console.log('我是indexa.js')

修改pageb.js文件

import $ from 'jquery'
import {common} from './utils.js'common() // 新加的$() // 调用一下console.log('我是indexb.js')

打包后发现自己的公共方法文件被打包到了jquery……js文件中了,我们并不希望这样,因为第三方库一般是不会修改的,我们希望每次打包第三方库的名称不变,这样有助于客户端缓存。所以我们需要从当前的jquery…js中提取出自己的公共方法文件。

在这里插入图片描述

分离utils.js文件和jquery等第三方库文件

  1. 修改webpack.config.js
// output中的path需要绝对路径
let path = require('path')
// 用于将打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')
let webpack = require('webpack')
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {indexa: './src/indexa.js',indexb: './src/indexb.js',jquery: ['jquery'] // 依赖的第三方库node_modules中},output: {path: path.resolve('./dist/'),filename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({name: 'jquery', // 如果有该名称的chunk则选择这个chunk提取公共文件,这里是jquery,如果没有则生成的文件是这个名称的chunkminiChunks: Infinity // 这样就只会打包出自身chunk和 webpack生成的一些文件}),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})]
}

通过打包结果发现我们自定义的模块确实从jquery中提取了出来,但是却打到了每个引入的页面中,这也是我们接受不了的。

在这里插入图片描述

  1. 从单个页面中分离公共方法

修改webpack.config.js文件如下:

// output中的path需要绝对路径
let path = require('path')
// 用于将打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')
let webpack = require('webpack')
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {indexa: './src/indexa.js',indexb: './src/indexb.js',jquery: ['jquery'] // 依赖的第三方库node_modules中},output: {path: path.resolve('./dist/'),filename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({name: 'jquery', // 如果有该名称的chunk则选择这个chunk提取公共文件,这里是jquery,如果没有则生成的文件是这个名称的chunkminChunks: Infinity // 这样就只会打包出自身chunk和 webpack生成的一些文件}),new CommonsChunkPlugin({name: 'utils',chunks: ['indexa', 'indexb']}),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})]
}

打包结果如下,可以发现utils被提取了出来,这样就我们的目的就达到了。

在这里插入图片描述

minChunks的函数值

我们发现第三方库我们是通过entry字段手动添加的,这样比较麻烦,不能以后添加一个第三方库我们就手动修改一下entry的jquery数组。

我们可以通过minChunks的值传入一个函数来做,函数返回true则会被打包。修改webpack.config.js文件如下:

// output中的path需要绝对路径
let path = require('path')
// 用于将打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')
let webpack = require('webpack')
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {indexa: './src/indexa.js',indexb: './src/indexb.js',// 已经不需要了 jquery: ['jquery'] // 依赖的第三方库node_modules中},output: {path: path.resolve('./dist/'),filename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({name: "vendor", // 修改jquery名称为vendor,第三方库集合minChunks: function (module, ) {// node_modules中出来的都打到这个文件中return module.context && module.context.includes("node_modules");}}),new CommonsChunkPlugin({name: 'utils',chunks: ['indexa', 'indexb']}),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})]
}

打包结果如下,只是将jquery…js的名称换成了vendor…js其他没有任何变化。

在这里插入图片描述

让moduleId固定下来

通过上面两张截图的观察我们可以发现indexb…js的chunkHash不一样了,但是我们并没有修改文件内容。这是因为webpack生成模块的moduleId在变化。让moduleId停止变化的插件有两个,一个是HashedModuleIdsPlugin,还有一个是NamedModulesPlugin。

HashedModuleIdsPlugin: 该插件会根据模块的相对路径生成一个四位数的hash作为模块id, 建议用于生产环境。

NamedModulesPlugin: 当开启 HMR 的时候使用该插件会显示模块的相对路径,建议用于开发环境。

我们就选择生产环境用的插件,修改webpack.config.js文件如下:

// output中的path需要绝对路径
let path = require('path')
// 用于将打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')
let webpack = require('webpack')
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {indexa: './src/indexa.js',indexb: './src/indexb.js',// 已经不需要了 jquery: ['jquery'] // 依赖的第三方库node_modules中},output: {path: path.resolve('./dist/'),filename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({name: "vendor", // 修改jquery名称为vendor,第三方库集合minChunks: function (module) {// node_modules中出来的都打到这个文件中return module.context && module.context.includes("node_modules");}}),new CommonsChunkPlugin({name: 'utils',chunks: ['indexa', 'indexb']}),// 固定下来模块的moduleIdnew HashedModuleIdsPlugin(),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})]
}

runtime和manifest

其实vender中不止有node_module文件夹中的包,还包括

runtime: 指在浏览器运行时,webpack 用来连接模块化的应用程序的所有代码。其中包含:在模块交互时,连接模块所需的加载和解析逻辑。包括浏览器中的已加载模块的连接,以及懒加载模块的执行逻辑。

manifest: 当编译器(compiler)开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 “Manifest”,当完成打包并发送到浏览器时,会在运行时通过 Manifest 来解析和加载模块。无论你选择哪种模块语法,那些 importrequire 语句现在都已经转换为 __webpack_require__ 方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够查询模块标识符,检索出背后对应的模块。

当模块做出改变的时候manifest也会改变,同时也会导致vender改变,最后导致vender的缓存失效,这种失效并不是因为vender本身内容的改变导致的,所以我们需要分离runtime和manifest。

提取runtime和manifest

修改webpack.config.js文件如下:

// output中的path需要绝对路径
let path = require('path')
// 用于将打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')
let webpack = require('webpack')
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {indexa: './src/indexa.js',indexb: './src/indexb.js'},output: {path: path.resolve('./'),filename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({name: "vendor", // 修改jquery名称为vendor,第三方库集合minChunks: function (module) {// node_modules中出来的都打到这个文件中return module.context && module.context.includes("node_modules");}}),new CommonsChunkPlugin({name: 'utils',chunks: ['indexa', 'indexb']}),new CommonsChunkPlugin({name: 'manifest',minChunks: Infinity}),// 固定下来模块的moduleIdnew HashedModuleIdsPlugin(),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})]
}

在这里插入图片描述

children字段和async字段的作用

children和async作用于动态加载模块。如果没有设置children,那么在动态引入的多个脚本中公用的部分并不会被提取出来。如果设置了childrend: true,则公共部分会被提取到主脚本中。进一步设置async字段,那么提取出来的公共部分不会在主脚本中,而会生成一个单独文件异步引入。

如果动态引入脚本和主脚本有公共的部分,那么及时没有设置children和async字段也会被提取。

为了去除上面的干扰重建目录。

|— src|— index.html|— index.js|— child1.js|— child2.js|— webpack.config.js|— node_modules|— jquery/

index.js

改文件为主脚本,会动态引入两个脚本child1.js和child2.js

require.ensure(['./child1.js'], function () {
})require.ensure(['./child2.js'], function () {
})

child1.js

import $ from 'jquery'
import {common} from './utils.js'$()
common()console.log('我是child1.js')

child2.js(和child1.js内容相同)

import $ from 'jquery'
import {common} from './utils.js'$()
common()console.log('我是child2.js')

webpack.config.js

先正常打包,观察结果。

// output中的path需要绝对路径
let path = require('path')
// 用于将打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {entry: {index: './src/index.js'},output: {path: path.resolve('./'),filename: '[name].[chunkHash].js',chunkFilename: '[name].[chunkHash].js'},plugins: [new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})]
}

下图我们可以看出除了index.js还多了两个js,这两个就是通过动态加载引入的js被单独打包了,并且这两个js中公共的部分并没有被提取。还可以注意到这时候index.js是很小的。

在这里插入图片描述

提取异步加载的js中公共部分

// output中的path需要绝对路径
let path = require('path')
// 用于将打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {index: './src/index.js'},output: {path: path.resolve('./'),filename: '[name].[chunkHash].js',chunkFilename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({children: true}),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})]
}

添加children选项之后:

在这里插入图片描述

动态引入进来的文件的公共部分被提取到主块中了。两个动态引入文件的尺寸减小并且主脚本的尺寸变大了。

将动态引入的部分单独打包

// output中的path需要绝对路径
let path = require('path')
// 用于将打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {index: './src/index.js'},output: {path: path.resolve('./'),filename: '[name].[chunkHash].js',chunkFilename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({children: true,async: true}),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})]
}

我们可以看到index.js文件又减小了并且多了一个文件。

在这里插入图片描述

以上就是我理解的CommonsChunkPlugin插件中children和async的用法。

关于自定义动态引入脚本打包的名字可参考webpack中实现按需加载

注:

hash:一个随机值,每次打包都会改变,建议用于开发。

chunkHash: 根据文件内容生成一个随机值,建议用于生产便于缓存。

Infinity:创建一个公共chunk,但是不包含任何模块,内部是一些webpack生成的runtime代码和chunk自身包含的模块(如果chunk存在的话)。

多CommonsChunkPlugin:第二次使用CommonsChunkPlugin插件的时候如果不指定chunks默认针对前一个CommonsChunkPlugin插件生成的chunk做提取。

children部分主脚本:引入异步脚本的脚本。

总结:

  1. node_modules中第三方库的提取可以通过miniChunks传入function来控制。
  2. 分离之后chunkHash还会变是应为moduleId在改变可以使用插件HashedModuleIdsPlugin来固定下来
  3. manifest和runtime文件提取可以通过miniChunks: Infinity 来完成
  4. children字段设置为true,提取异步引入子文件的公共部分到主文件中
  5. async字段配合children字段使用,提取异步引入子文件的公共部分到单独文件中

参考

webpack4:连奏中的进化

Webpack4之SplitChunksPlugin规则

详解CommonsChunkPlugin的配置和用法

CommonsChunkPlugin中children和async属性详解

Webpack2中的NamedModulesPlugin与HashedModuleIdsPlugin

runtime和manifest

hashed-module-ids-plugin

NamedModulesPlugin

webpack中ensure方法和CommonsChunkPlugin中的children选项

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/362119.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

详细介绍jQuery.outerWidth() 函数具体用法

outerWidth()函数用于设置或返回当前匹配元素的外宽度。外宽度默认包括元素的内边距(padding)、边框(border),但不包括外边距(margin)部分的宽度。你也可以指定参数为true,以包括外边距(margin)部分的宽度。如下图: 如果你要获取其它情况的宽…

SQLite 入门教程(一)基本控制台(终端)命令

一、基本简介 SQLite 是一个自持的(self-contained)、无服务器的、零配置的、事务型的关系型数据库引擎。因为他很小,所以也可以作为嵌入式数据库内建在你的应用程序中。SQLite 被应用在 Solaris 10操作系统、Mac OS 操作系统、iPhone 和 Sky…

Web前端开发css基础样式总结

颜色和单位的使用 颜色 用颜色的名字表示颜色,比如:red 用16进制表示演示 比如:#FF0000 用rgb数值表示颜色,rgb(红,绿,蓝),每个值都在0-255之间 一般都用16进制表示颜色 单位 1. px像素(Pixel)。像素,与分辨率…

nth-child(n)和nth-of-type(n)

:nth-child(n)&#xff1a;父元素的子元素的第n个 :nth-of-type(n)&#xff1a;父元素的 某类 子元素的第n个 注&#xff1a;n从0起&#xff0c;元素index从1起 nth-child(n)和nth-of-type(n)例&#xff1a; <head><style>body :nth-child(2) {height: 30px;ba…

记录您的里程和社区运行情况:Java EE 7真实体验

miles2run.org是跟踪跑步活动并与亲朋好友共享的简便方法。 可以创建然后跟踪基于天或基于距离的目标。 它还允许创建社区运行目标&#xff0c;并使多个跑步者参与并跟踪他们朝着该目标的活动。 您也可以找出本地跑步者并与他们联系。 该项目已开始&#xff0c;以帮助跟踪#Jav…

oracle在group by时某列有多个值的拼接

最近编码过程中出现了group by后&#xff0c;某些列会有多个值&#xff0c;而我需要把这些多个值的列进行拼接的情况&#xff0c;和大家分享一下。 有如下表student&#xff1a; 我们希望以class分组&#xff0c;每组的信息平铺&#xff0c;效果如下 分组首先想到的肯定是group…

在Spring中使用Asciidoctor:使用Spring MVC渲染Asciidoc文档

Asciidoc是一种基于文本的文档格式&#xff0c;因此如果要将文档提交到版本控制系统中并跟踪不同版本之间的更改&#xff0c;它非常有用。 这使Asciidoc成为编写书籍&#xff0c;技术文档&#xff0c;常见问题解答或用户手册的理想工具。 创建Asciidoc文档后&#xff0c;很可能…

http强缓存在firefox和chrome之间的差异

最近回顾HTTP缓存知识的时候发现了一个问题&#xff0c;设置了强缓存和协商缓存的文件&#xff0c;点击浏览器的刷新按钮的时候在chrome和firefox中的表现不一样。具体表现为chrome走强缓存没有再次发起请求。firefox却在强缓存没有失效的情况下发了请求&#xff0c;返回了304走…

垃圾收集器–串行,并行,CMS,G1(以及Java 8中的新增功能)

4个Java垃圾收集器–错误的选择如何严重影响性能 在2014年&#xff0c;对于大多数开发人员来说&#xff0c;还有两件事仍然是个谜&#xff1a;垃圾收集和了解异性。 由于我对后者知之甚少&#xff0c;所以我认为我会对前者大吃一惊&#xff0c;特别是因为在该领域中&#xff0c…

基于webpack的react脚手架

一、前言&#xff1a;react的cli开发模式太过于简单&#xff0c;好多东西都要自己配置 二、这里有个简单的配置&#xff0c;可以直接上手开发(不熟悉webpack和npm的绕路)&#xff0c;已经完成的配置如下 1&#xff1a;默认ejs模板 2&#xff1a;编译less、scss 3&#xff1…

不要错过使用jOOλ或jOOQ编写Java 8 SQL单行代码的机会

越来越多的人通过为他们的业务采用功能性编程来赶上我们平台的最新更新。 在Data Geekery &#xff0c;我们将Java 8用于jOOQ集成测试&#xff0c;因为将新的Streams API与lambda表达式一起使用使生成临时测试数据变得非常容易。 但是&#xff0c; 我们并不认为JDK提供了尽可…

node实现简单的群体聊天工具

一、使用的node模块 1、express当做服务器 2、socket.io 前后通信的桥梁 3、opn默认打开浏览器的模块(本质上用不到) 难点&#xff1a;前后通信 源码地址&#xff1a;https://github.com/wjf444128852/SimpleChat 欢迎clone并运行、扩展改进、issues 二、基础样子&#…

使用Spring Security的多租户应用程序的无状态会话

从前&#xff0c; 我发表了一篇文章&#xff0c;解释了构建无状态会话的原理 。 巧合的是&#xff0c;我们再次针对多租户应用程序执行同一任务。 这次&#xff0c;我们将解决方案集成到Spring Security框架中&#xff0c;而不是自己构建身份验证机制。 本文将解释我们的方法和…

What?一个 Dubbo 服务启动要两个小时!

前言 前几天在测试环境碰到一个非常奇怪的与 dubbo 相关的问题&#xff0c;事后我在网上搜索了一圈并没有发现类似的帖子或文章&#xff0c;于是便有了这篇。 希望对还未碰到或正在碰到的朋友有所帮助。 现象 现象是这样的&#xff0c;有一天测试在测试环境重新部署一个 dubbo …

使用Project Lombok减少Java应用程序中的样板代码

对Java编程语言最常提出的批评之一是它需要大量的样板代码 。 对于简单的类尤其如此&#xff0c;该类只需要存储一些值就可以。 您需要这些值的getter和setter&#xff0c;也许您还需要一个构造函数&#xff0c;覆盖equals&#xff08;&#xff09;和 hashcode&#xff08;&am…

十六、CI框架之数据库操作get用法

一、使用数据库的Get方法读取内容&#xff0c;如下代码&#xff1a; 二、数据库如下&#xff1a; 二、效果如下&#xff1a; 转载于:https://www.cnblogs.com/tianpan2019/p/11141809.html

WCF系列(二) -- 使用配置文件构建和使用WCF服务

当然&#xff0c;配置一个ServiceHost除了上面说的完全使用代码的方式&#xff0c;更好的方式是使用配置文件&#xff0c;把一些可能需要修改的属性跟代码分离&#xff0c;放到配置文件中&#xff0c;这样可以提供服务配置的灵活性&#xff0c;也更容易维护。 看看前面那个不用…

如何使用github搭建个人博客

1、去github官网注册个人帐号&#xff1a;没有的&#xff1a;猛戳这里去注册&#xff0c;比如我的账户名&#xff1a;wjf444128852&#xff0c;我的已经汉化(可在github里搜索github如何汉化有插件) 2、点击仓库-新建&#xff0c;仓库名字必须是&#xff1a;你的github帐号.git…

Spring Boot和Spring Data REST –通过REST公开存储库

使用Spring Boot和Spring Data REST&#xff0c;通过REST公开Spring Data存储库非常容易。 使用最少的代码&#xff0c;您可以创建遵循HATEOAS原理的JPA实体的REST表示。 我决定重用Spring PetClinic的JPA实体&#xff08;业务层&#xff09;作为本文的基础。 应用基础 PetCli…

jQuery Ajax – Servlets集成:构建完整的应用程序

网上有很多教程&#xff0c;它们解释了有关使用servlet和JSP页面进行Java Web开发的一些知识&#xff0c;但是&#xff0c;我从来没有找到对于初学者来说足够简洁&#xff0c;简单的教程。 这样的教程应该解释创建一个简单的Web应用程序的整个过程&#xff0c;包括前端&#xf…