2020年春节已过,本来打算回郑州,却因为新型冠状病毒感染肺炎的疫情公司推迟了上班的时间,我也推迟了去郑州的时间,在家多陪娃几天。以前都是在书房学习写博客,今天比较特殊,抱着电脑,在楼顶晒着太阳,陪着家人,写着博客。

前面的几篇文章主要告诉大家如何安装、配置webpack、webpack实现样式分离等,今天这篇文章主要跟大家分享如果webpack如何实现代码分片。

现在工程项目中,实现高性能应用的其中重要的一点就是让用户每次只加载必要的资源,优先级别不太高的资源采用延迟加载等技术渐进地进行加载获取。

Webpack 作为打包工具所特有的一项技术就是代码分片技术,通过这项技术我们可以把代码按照特定的形式进行拆分,使用按需加载资源,不必要全部加载下来。
代码分片可以有效降低首屏加载资源的大小,但是我们同时又面临着其他问题,比如如何对项目模块进行分片,分片后的资源如何进行管理等等。今天我们需要对这些问题进行分析解决。

### 利用入口划分代码

在Webpack中,配置参数中每个入口都将生成一个对应的资源文件,通过入口的配置我们可以进行一些简单有效的代码拆分。

对于项目中常常会引入一些第三方库和工具,这些一般不会改动的,可以把它们单独放在一个入口中,由该入口的资源不会经常更新,因此可以有效利用客户端缓存这些资源,让用户不必在每次请求页面时候都重新加载。

//webpack.config.js
entry: {
    index: './index.js',
    lib: ['lib-1', 'lib-2']
}

//index.html
<script src="dist/lib.js"></script>
<script src="dist/index.js"></script>

这种拆分方法主要适合于那些将接口绑定在全局对象上的库,因为业务代码中的模块无法直接引用库中的模块,二者属于不同的依赖树。

对于多页面应用来说,我们可以利用入口划分的方式拆分代码。比如,为每一个页面创建一个入口,并放入只涉及该页面的代码,同时再创建一个入口来包含所有公共模块,并使每个页面都进行加载。但是这样仍会带来公共模块与业务模块处于不同依赖树的问题。另外,很多时候不是所有的页面都需要这些公共模块。这就需要我们利用webpack专用的插件来解决这种问题了。

CommonsChunkPlugin

CommonsChunkPlugin是webpack4之前内部自带的插件,webpack4之后用的是SplitChunks。CommonsChunkPlugin主要是用来提取第三方库和公共模块,避免首屏加载的bundle文件或者按需加载的bundle文件体积过大,从而导致加载时间过长,是一把优化项目的利器。

优点:

  • 开发过程中减少了重复模块打包,可以提升开发速度;
  • 减小整体资源体积;
  • 合理分片后的代码可以更有效地利用客户端缓存。
    首页通过一个简单的例子也说明,假设我们当前项目中有a.js 和b.js 两个入口文件,并且都引入了react,下面是未使用CommonsChunkPlugin的配置
//webpack.config.js
module.exports = {
    entry: {
        a: './a.js',
        b: './b.js'
    },
    output: {
        filename: '[name].js'
    }
}

//a.js
import React from 'React'
... //省略

// b.js
import React from 'React'
... //省略

如果打包,从打包的资源体积可以看出,react被分别打包到a.js 和b.js中。

更改webpack.config.js,添加CommonsChunkPlugin配置

const webpack = require('webpack');
module.exports = {
    entry: {
        a: './a.js',
        b: './b.js'
    },
    output: {
        filename: '[name].js'
    },
    plugins: [
    new webpack.optimize.CommonsChunkPlugin({
    name: 'commons',
    filename: 'commons.js'
})
]
}

在配置文件的头部引入Webpack,接着使用其内部CommonsChunkPlugin函数创建了一个插件实例,并传入配置对象,配置参数可以理解为

  • name:用于指定公共chunk的名字
  • filename: 提取后的资源文件名
    打包后可以看到,产出的资源中多了一个commons.js,而a.js 和b.js文件的体积也减少了,这是由于把react及依赖的模块提到commons.js的原因。
    不过我们需要注意的是,我们需要在页面引入其他j s之前,先引入公用的commons.js文件。

在提取公共模块方面,CommonsChunkPlugin可以满足很多场景的需求,但是它也有一些欠缺的地方。
1)一个CommonsChunkPlugin只能提取一个vendor,假如我们想提取多个vendor则需要配置多个插件,这会增加很多重复的配置代码。

2)前面我们提到的manifest实际上会使浏览器多加载一个资源,这对于页面渲染速度是不友好的。

3)由于内部设计上的一些缺陷,CommonsChunkPlugin在提取公共模块的时候会破坏掉原有Chunk中模块的依赖关系,导致难以进行更多的优化。比如在异步Chunk的场景下CommonsChunkPlugin并不会按照我们的预期正常工作。

optimization.SplitChunks

optimization.SplitChunks(简称SplitChunks)是Webpack 4为了改进CommonsChunk-Plugin而重新设计和实现的代码分片特性。它不仅比CommonsChunkPlugin功能更加强大,还更简单易用。

配置文件web pack.config.js为:

const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
  context: path.join(__dirname, './src'),
  entry: {
    index: './index.js'
  },
  output: {
    // path: path.join(__dirname, 'dist'),
    filename: 'index.js',
    publicPath: '/dist/'
  },
  mode: 'development',
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', {
          loader: 'css-loader',
          options: {
            modules: {
              localIdentName: '[path][name]__[local]--[hash:base64:5]',
            }
          }
        }]
      },
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
            presets: [
              [
                'env', {
                  modules: false
                }
              ]
            ]
          }
        }
      }
    ],
  }
}

需要引入的两个index.js文件和index2.js文件

// index
import index2 from  './index2.js';
import React from 'react'
document.write('index.js', React.version);

//index2
import React from 'react'
document.write('index2.js', React.version);

使用optimization.splitChunks替代了CommonsChunkPlugin,并指定了chunks的值为all,这个配置项的含义是,SplitChunks将会对所有的chunks生效(默认情况下,SplitChunks只对异步chunks生效,并且不需要配置)
打包结果如下图:

原本我们打包的结果应该是index.js,但是由于SplitChunks的存在,又生成了一个vendors~index.index.js,并且把react提取到了里面。
运行的效果如下图:

在使用CommonsChunkPlugin的时候,我们大多数时候是通过配置项将特定入口中的特定模块提取出来,也就是更贴近命令式的方式。而SplitChunks的不同之处在于我们只需要设置一些提取条件,如提取的模式、提取模块的体积等,当某些模块达到这些条件后就会自动被提取出来。SplitChunks的使用更像是声明式的。

SplitChunks默认情形下的提取条件:

  • 提取后的chunk可被共享或者来自node_modules目录。这一条很容易理解,被多次引用或处于node_modules中的模块更倾向于是通用模块,比较适合被提取出来。
  • 提取后的Javascript chunk体积大于30kB(压缩和gzip之前),CSS chunk体积大于50kB。这个也比较容易理解,如果提取后的资源体积太小,那么带来的优化效果也比较一般。
  • 在按需加载过程中,并行请求的资源最大值小于等于5。按需加载指的是,通过动态插入script标签的方式加载脚本。我们一般不希望同时加载过多的资源,因为每一个请求都要花费建立链接和释放链接的成本,因此提取的规则只在并行请求不多的时候生效。
  • 在首次加载时,并行请求的资源数最大值小于等于3。和上一条类似,只不过在页面首次加载时往往对性能的要求更高,因此这里的默认阈值也更低。

SplitChunks提取方式

SplitChunks 默认的提取方式是异步提取,当我们在chunks上配置参数为all的时候,不是异步资源也可以提取。

SplitChunks 配置

optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      minRemainingSize: 0,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 6,
      maxInitialRequests: 4,
      automaticNameDelimiter: '~',
      name: true,
      automaticNameMaxLength: 30,
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }

(1)匹配模式通过chunks我们可以配置SplitChunks的工作模式。它有3个可选值,分别为async(默认)、initial和all。async即只提取异步chunk,initial则只对入口chunk生效(如果配置了initial则上面异步的例子将失效),all则是两种模式同时开启。

(2)匹配条件minSize、minChunks、maxAsyncRequests、maxInitialRequests都属于匹配条件,

(3)命名配置项name默认为true,它意味着SplitChunks可以根据cacheGroups和作用范围自动为新生成的chunk命名,并以automaticNameDelimiter分隔。如vendors~a~b~c.js意思是cacheGroups为vendors,并且该chunk是由a、b、c三个入口chunk所产生的。

(4)cacheGroups可以理解成分离chunks时的规则。默认情况下有两种规则——defaultVendors和default。defaultVendors用于提取所有node_modules中符合条件的模块,default则作用于被多次引用的模块。我们可以对这些规则进行增加或者修改,如果想要禁用某种规则,也可以直接将其置为false。当一个模块同时符合多个cacheGroups时,则根据其中的priority配置项确定优先级。

总结

有关webpack实现代码分片的几种方法:合理地规划入口,使用Commons-ChunkPlugin或SplitChunks就暂时分享到这里,这仅代表个人的观点,如想了解更多请扫描二维码


最后友情提醒大家要戴口罩,勤洗手,尽量减少外出,做好预防 措施,远离新型冠状病毒。

Webpack实战(八):教你搞懂webpack如果实现代码分片(code splitting)的更多相关文章

  1. (20/24) webpack实战技巧:watch实现热打包和添加代码备注

    在前面的学习中,我们一直使用webpack-dev-server充当(本地)服务器和完成打包任务,但是当出项目团队联合开发,共同使用一个服务器时,这时候我们需要实时进行打包以确保团队间能进行联调或者进 ...

  2. 一篇文章教你搞懂日志采集利器 Filebeat

    关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 本文使用的Filebeat是7.7.0的版本,文章将从如下几个方面说明: Filebeat是什 ...

  3. 五个小例子教你搞懂 JavaScript 作用域问题

    众所周知,JavaScript 的作用域和其他传统语言(类C)差别比较大,掌握并熟练运用JavaScript 的作用域知识,不仅有利于我们阅读理解别人的代码,也有助于我们编写自己的可靠代码. 下面笔者 ...

  4. 硬核!八张图搞懂 Flink 端到端精准一次处理语义 Exactly-once(深入原理,建议收藏)

    Flink 在 Flink 中需要端到端精准一次处理的位置有三个: Source 端:数据从上一阶段进入到 Flink 时,需要保证消息精准一次消费. Flink 内部端:这个我们已经了解,利用 Ch ...

  5. 教你搞懂Jenkins安装部署!

    前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i Jenkins介绍 Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用 ...

  6. webpack Code Splitting浅析

    Code Splitting是webpack的一个重要特性,他允许你将代码打包生成多个bundle.对多页应用来说,它是必须的,因为必须要配置多个入口生成多个bundle:对于单页应用来说,如果只打包 ...

  7. 30分钟手把手教你学webpack实战

    30分钟手把手教你学webpack实战 阅读目录 一:什么是webpack? 他有什么优点? 二:如何安装和配置 三:理解webpack加载器 四:理解less-loader加载器的使用 五:理解ba ...

  8. 每天记录一点:NetCore获得配置文件 appsettings.json vue-router页面传值及接收值 详解webpack + vue + node 打造单页面(入门篇) 30分钟手把手教你学webpack实战 vue.js+webpack模块管理及组件开发

    每天记录一点:NetCore获得配置文件 appsettings.json   用NetCore做项目如果用EF  ORM在网上有很多的配置连接字符串,读取以及使用方法 由于很多朋友用的其他ORM如S ...

  9. Webpack实战(五):轻松读懂Webpack如何分离样式文件

    在上一篇文章中我给大家分享了预处理器(loader),里面讲到了style-loader 和css-loader,有关样式引入的问题,但是上面的样式文件只是引入到style标签里面,并不是我想要的样式 ...

随机推荐

  1. C++ 排序引用的优化

    链接:https://www.nowcoder.com/acm/contest/83/B来源:牛客网 题目描述 第一次期中考终于结束啦!沃老师是个语文老师,他在评学生的作文成绩时,给每位学生的分数都是 ...

  2. Mysql.linux登录数据库

    //mysql -hlocalhost -uroot -p //-h数据库地址 -u用户名 -p密码 -P端口号(P大写)//-p可省略,会提示输入密码. mysql -h127. -uroot -p ...

  3. 洛谷 P1658 购物

    题目链接 题目描述 你就要去购物了,现在你手上有N种不同面值的硬币,每种硬币有无限多个.为了方便购物,你希望带尽量少的硬币,但要能组合出1到X之间的任意值. 题目分析 题目要求组合出1到X之间的任意值 ...

  4. 02.flask-script

    网址:https://pypi.org/project/Flask-Script/ 文档:https://flask-script.readthedocs.io/en/latest/ 1.安装 2.新 ...

  5. Qt Installer Framework翻译(3-2)

    添加组件 如果用户在初始安装期间未选择所有可安装组件,则后续也可以使用包管理器从仓库中获取剩余组件进行安装.包管理器是维护工具的一部分,该维护工具在初始安装过程中与应用程序一起被安装.仅当包含组件的仓 ...

  6. 【实战】使用 Kettle 工具将 mysql 数据增量导入到 MongoDB 中

    最近有一个将 mysql 数据导入到 MongoDB 中的需求,打算使用 Kettle 工具实现.本文章记录了数据导入从0到1的过程,最终实现了每秒钟快速导入约 1200 条数据.一起来看吧~ 一.K ...

  7. ReactNative---卡顿问题及性能优化

    ReactNative性能优化 在reactnative 中如果要更改DOM中的数据显示,只有通过setState方法来实现:但是当setState时,要刷新整个DOM:在一般情况先还能保证体验,但是 ...

  8. Spring-cloud微服务实战【七】:服务熔断与降级hystrix

      在之前的文章中,我们先后介绍了eureka,ribbon,feign,使用eureka集群的方式来保证注册中心的高可用,在eureka中使用ribbon进行负载均衡,使用feign接口替换手动编码 ...

  9. Marginalize

    在David M.Blei 的Distance Dependent Chinese Restaurant Processes 中提到:DDCRP 的一个重要性质,也是和dependent DP 的一个 ...

  10. Mavn 项目 引入第三方jar包 导致ClassNotFoundException

    案例 我有一个Maven构建的项目,项目模块之间有依赖关系,我需要用到一个本地的jar包,而该jar包不能通过配置pom.xml文件从远程仓库自动下载,于是我直接导入该jar包到其中一个项目,不通过p ...