基于webpack Code Splitting实现react组件的按需加载

随着web应用功能越来越复杂,模块打包后体积越来越大,如何实现静态资源的按需加载,最大程度的减小首页加载模块体积和首屏加载时间,成为模块打包工具的必备核心技能。

webpack作为当下最为流行的模块打包工具,成为了react、vue等众多热门框架的官方推荐打包工具。其提供的Code Splitting(代码分割)特性正是实现模块按需加载的关键方式。

什么是Code Splitting

官方定义:

Code splitting is one of the most compelling features of webpack. It allows you to split your code into various bundles which you can then load on demand — like when a user navigates to a matching route, or on an event from the user. This allows for smaller bundles, and allows you to control resource load prioritization, which if used correctly, can have a major impact on your application load time

翻译过来大概意思就是:

代码分割功能允许将web应用代码分割为多个独立模块,当用户导航到一个匹配的路由或者派发某些特定事件时,来按需加载这些模块。这样就可以将大型的模块分割为多个小型的模块,实现系统资源加载的最大优化,如果使用得当,能够极大的减少我们的应用首屏加载时间。

Code splitting 分类

一、缓存和并行加载的资源分割

这种方法是将某些第三方基础框架模块(例如:moment、loadash)或者多个页面的公用模块拆分出来独立打包加载,通常这些模块改动频率很低,将其与业务功能模块拆分出来并行加载,一方面可以最大限度的利用浏览器缓存,另一方面也可以大大降低多页面系统的代码冗余度。

按照资源类型不同又可以分为js公共资源分割css资源分割两类。

1、js公共资源分割

例如:系统应用入口文件index.js中使用日期功能库 momentjs。

index.js

var moment = require('moment');
console.log(moment().format());

webpack.config.js

定义多个entry入口
  • main为主入口模块文件
  • vendor为公共基础库模块,名字可随意设定。称为initial chunk

var path = require('path');

module.exports = {
   entry: {
       main: './index.js',
       vendor: ['moment']
   },
   output: {
       filename: '[name].js',
       path: path.resolve(__dirname, 'dist')
   }
}

执行webpack打包命令:

webpack --progress --hide-modules

可以看到最终打包为两个js文件 main.jsvendor.js,但如果检查者两个文件会发现moment模块代码被重复打包到两个文件中,而这肯定不是我们想要的,这时候就需要 webpack的plugin发挥作用了。

使用CommonsChunkPlugin
var webpack = require('webpack');
var path = require('path');

module.exports = {
   entry: {
       main: './index.js',
       vendor: ['moment']
   },
   output: {
       filename: '[chunkhash:8].[name].js',
       path: path.resolve(__dirname, 'dist')
   },
   plugins: [
       new webpack.optimize.CommonsChunkPlugin({
            // vendor是包括公共的第三方代码,称为initial chunk
           name: 'vendor'
       })
   ]
}

执行webpack打包命令,我们发现moment只被打包进vendor.js中。

webpack运行时模块(manifest)
  • 在前面的步骤2当中webpack在浏览器中加载js模块的运行时代码块也打包进了vendor.js,如果为打包的js文件添加chunkhash,则每次修改 index.js后再次编译打包,由于运行时代码需要重新编译生成,导致vendor.js重新打包并生成新的chunkhash

webpack运行时代码块部分:

  • 实际项目中我们希望修改业务功能后打包时只重新打包业务模块,而不打包第三方公共基础库。这里我们可以将webpack的运行时代码提取到独立的manifest文件中,这样每次修改业务代码只重新打包生成业务代码模块main.js和运行时代码模块manifest.js,就实现了业务模块和公共基础库模块的分离。

  • names字段支持以数组格式来指定基础库模块名称运行时代码模块名称
module.exports = {
   entry: {
       main: './index.js',
       vendor: 'moment'
   },
   output: {
       filename: '[chunkhash:8].[name].js',
       path: path.resolve(__dirname, 'dist')
   },
   plugins: [
       new webpack.optimize.CommonsChunkPlugin({
           // manifest是包括webpack运行时runtime的块,可以称为entry chunk
           names: ['vendor', 'manifest']
       })
   ]
}

2、CSS代码分割

  • 实际项目开发当中经常使用webpack的css-loader来将css样式导入到js模块中,再使用style-loader将css样式以