代码分割从两方面来讲:React Lazy 和 webpack两方面
每次单页应用打包出的文件又臭又长,是最蛋疼的事,浏览器刷个半天才出来网页让人恨不得找个地洞钻进去,这种事很尴尬,让人怀疑你这前端不专业
所以正题来了,这篇文章主要讲解
- 如何分模块拆分代码
- 如何提取公共库,避免重复打包(两个模块都用到jQuery,如何只打包进一个chunk)
- 如何打包需要的代码(比如一个ui库,你只用到一个Button,那么只打包Button)
- React组件如何实现懒加载
先讲 React Lazy
先上个最简单的demo,App.js和Child.js
// App.jsimport React from 'react';import Child from './Child';function App() { return ();}export default App;复制代码
// Child.jsimport React from 'react';class Child extends React.Component{ render(){ return我是子组件啊}}export default Child;复制代码
就这个目录结构,然后执行打包,打包之后如下:
可以看到Child组件被打包到了main.chunk.js里面 那么Child是否可以做懒加载呢?查找React文档,可以找到code split,那么用React lazy来对Child组件做一下懒加载,还是原来的组件App和Child
// App.jsimport React, { lazy, Suspense } from 'react';const Child = lazy(() => import('./Child'))function App() { return (}>Loading... );}export default App;复制代码
// Child.jsimport React from 'react';class Child extends React.Component{ render(){ return我是子组件啊}}export default Child;复制代码
其实api来说很简单,就两句代码,这就实现了懒加载
import React, { lazy, Suspense } from 'react';const Child = lazy(() => import('./Child'))复制代码
可是源码里可以注意到还有一个Suspense,而且不用Suspense的话,浏览器会给出一个提示:
从错误提示来看,lazy非要搭配Suspense使用了。那么Suspense是何方神圣呢?可以试想一下,既然是懒加载,那么当Child还未加载完成之前,这个视图怎么办?
bingo!Suspense就是为了处理这个的,让视图更友好,为懒加载组件做优雅降级,它叫加载指示器
好的,那已经加上lazy了,执行打包吧 再刷新下页面,发现Child组件被打包到了2.chunk.js里面,说明已经运行成功了。而且在Child加载成功之前,有个loading在提示。good!效果还不错可是问题来了。
阅读React lazy的官方文档。发现lazy传入一个函数,而且此函数需要返回一个Thenable,那么当没有返回Thenable的时候会怎么办呢?
发现浏览器挂了。。。这只是试下异常情况,所以最好还是按照React的官方文档去操作吧再从webpack谈下
webpack的代码分离有三种方法:
- 多个entry
- 插件来防止重复:SplitChunksPlugin
- 动态导入
先讲下最高端的,动态导入(以es6 import为?)
此章节讲解如何一个ui库,解构导入let { Button } = ui;只导入Button
这也是老生常谈的antd的import { Button } from 'antd',只导入Button问题
先上个反面教材吧
// index.jsimport { mainAdd } from './main';console.log(mainAdd(1, 2));复制代码
// main.jsconst mainAdd = (a, b) => { return a + b;}const mainMultiple = (a, b) => { return a * b;}export { mainAdd, mainMultiple}复制代码
webpack.config.js的配置entry就只配置index.js,来webpack打包一把
血崩啊,明明只是import了mainAdd,mainMultiple咋还打包进去了好的,反面教材讲完了,现在教大家如何修改,先修改一下文件目录
// webpack.config.jsconst path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { mode: 'development', entry: { index: './src/index.js' }, output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') }, plugins: [ new HtmlWebpackPlugin() ], module: { rules: [ { test: /\.js$/, loaders: 'babel-loader', include: path.join(__dirname, 'src'), options: { plugins: [ ["import", { "libraryName": "main", "libraryDirectory": "../../src/main"}], ] }, } ] }};复制代码
//index.jsimport { mainAdd } from 'main';console.log(mainAdd(1, 2));复制代码
// main目录下的main-add.jsconst mainAdd = (a, b) => { return a + b;}export default mainAdd;复制代码
// main目录下的main-multiple.jsconst mainMultiple = (a, b) => { return a * b;}export default mainMultiple;复制代码
好的,再来执行一次打包,发现只打包进了mainAdd,而mainMultiple没有打包进去。
总结一下,主要用到一个babel的知识点
- 需要用到babel-plugin-import插件。顾名思义,就是个动态import的plugin,然后配置babel-loader的options
options: { plugins: [ ["import", { "libraryName": "main", "libraryDirectory": "../../src/main"}], ]},复制代码
libraryName指代的是import { mainAdd } from 'main'这里的main -> 库名,libraryDirectory是main下面的哪个文件夹。比如antd/lib/button,这里libraryDirectory配置的是lib,这里默认是指向node_modules下的。我是放在src/main目录下,所以这里配置为"../../src/main"
webpack Entry多入口
这个就不多说了,大家用的都比较多
webpack 公共库拆分打包
还是老习惯,先来个反面教材
// index.jsimport _ from 'lodash';import { each } from 'jquery';console.log( _.join(['index', 'module', 'loaded!'], ' '));复制代码
// main.jsimport { each } from 'jquery';let arr = ['zhangsan', 'lisi'];each(arr, (idx, item) => { console.log(item);})复制代码
// webpack.config.jsconst path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { mode: 'development', entry: { index: './src/index.js', another: './src/main.js' }, output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') }, plugins: [ new HtmlWebpackPlugin() ], module: { rules: [ { test: /\.js$/, loaders: ['babel-loader'], include: path.join(__dirname, 'src') } ] }};复制代码
两个入口,打包一把
又血崩了,发现index.bundle.js打包进了jquery,another.bundle.js也打包进了jquery,可以打开两个bundle.js进去查找下jquery看看是否打包进去。那么理想状态下,应该是一份jquery,一份index,一份main,三份代码。用官方文档SplitChunksPlugin来试一把,index.js main.js webpack.config.js代码几乎不变。
// webpack.config.js中加上一项配置optimization: { splitChunks: { chunks: 'all' }}复制代码
再打包一下
发现公共库被提取出来了,index和another两个文件只包含的业务代码看一下chunk的里面的代码,发现只有verdors~other~index.bundle.js里面有require("jquery"),而vendors~index.bundle.js里面是直接用jquery的,并没有引入jquery源码
说明我们的效果已经达到了。
至此四个功能已经讲完了,自己写个demo来演练下吧,有问题可以加我微信demo0808
- React的lazy懒加载
- webpack的entry多入口
- SplitChunksPlugin提取公共库代码
- babel-plugin-import动态导入antd下的Button