Webpack3+React16代码分割的实现


Posted in Javascript onMarch 03, 2021

项目背景

最近项目里有个webpack版本较老的项目,由于升级和换框架暂时不被leader层接受o(???)o,只能在现有条件进行优化。

webpack3 + react16

webpack v3配置检查

很明显项目的配置是从v1继承过来的,v1->v3的升级较为简单,参考官网https://webpack.js.org/migrate/3/即可。

loaders变为rules
不再支持链式写法的loader,json-loader不需要配置
UglifyJsPlugin插件需要自己开启minimize

分析现有包的问题

使用webpack-bundle-analyzer构建包后,如图

Webpack3+React16代码分割的实现

问题非常明显:

除了zxcvbn这个较大的包被拆出来,代码就简单的打包为了vender和app,文件很大。

动态import拆分vender

Webpack3+React16代码分割的实现

分析vender的代码,某些大包,例如libphonenumber.js,使用场景不是很频繁,将它拆出来,当使用到相关特性时再请求。

参考react官方代码分割指南, https://react.docschina.org/docs/code-splitting.html#import

import { PhoneNumberUtil } from 'google-libphonenumber'
function usePhoneNumberUtil(){
  // 使用PhoneNumberUtil
}

修改为动态 import() 方式,then和async/await都支持用来获取异步数据

const LibphonenumberModule = () => import('google-libphonenumber')
function usePhoneNumberUtil(){
  LibphonenumberModule().then({PhoneNumberUtil} => {
    // 使用PhoneNumberUtil
  })
}

当 Webpack 解析到该语法时,会自动进行代码分割。

修改后的效果:

Webpack3+React16代码分割的实现

libphonenumber.js(1.chunk.js)从vender中拆分出来了,并且在项目实际运行中,只有当进入usePhoneNumberUtil流程时,才会向服务器请求libphonenumber.js文件。

基于路由的代码分割

React.lazy

参考react官方代码分割指南-基于路由的代码分割,https://react.docschina.org/docs/code-splitting.html#route-based-code-splitting。

拆分前示例:

import React from 'react';
import { Route, Switch } from 'react-router-dom';
const Home = import('./routes/Home');
const About = import('./routes/About');

const App = () => (
<Router>
 <Suspense fallback={<div>Loading...</div>}>
  <Switch>
   <Route exact path="/" component={Home}/>
   <Route path="/about" component={About}/>
  </Switch>
 </Suspense>
</Router>
);

拆分后示例:

import React, { lazy } from 'react';
import { Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
// 路由配置不变
)

拆分后效果:

app.js按照路由被webpack自动拆分成了不同的文件,当切换路由时,才会拉取目标路由代码文件。

Webpack3+React16代码分割的实现

命名导出

该段引用自 https://react.docschina.org/docs/code-splitting.html#named-exports 。

React.lazy 目前只支持默认导出(default exports)。如果你想被引入的模块使用命名导出(named exports),你可以创建一个中间模块,来重新导出为默认模块。这能保证 tree shaking 不会出错,并且不必引入不需要的组件。

// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));

自己实现AsyncComponent

React.lazy包裹的懒加载路由组件,必须要添加Suspense。如果不想强制使用,或者需要自由扩展lazy的实现,可以定义实现AsyncComponent,使用方式和lazy一样。

import AsyncComponent from './components/asyncComponent.js'
const Home = AsyncComponent(() => import('./routes/Home'));
const About = AsyncComponent(() => import('./routes/About'));
// async load component
function AsyncComponent(getComponent) {
 return class AsyncComponent extends React.Component {
  static Component = null
  state = { Component: AsyncComponent.Component }

  componentDidMount() {
   if (!this.state.Component) {
    getComponent().then(({ default: Component }) => {
     AsyncComponent.Component = Component
     this.setState({ Component })
    })
   }
  }
  // component will be unmount
  componentWillUnmount() {
   // rewrite setState function, return nothing
   this.setState = () => {
    return
   }
  }
  render() {
   const { Component } = this.state
   if (Component) {
    return <Component {...this.props} />
   }
   return null
  }
 }
}

common业务代码拆分

在完成基于路由的代码分割后,仔细看包的大小,发现包的总大小反而变大了,2.5M增加为了3.5M。

Webpack3+React16代码分割的实现

从webpack分析工具中看到,罪魁祸首就是每一个单独的路由代码中都单独打包了一份components、utils、locales一类的公共文件。

使用webapck的配置将common部分单独打包解决。

components文件合并导出

示例是将components下的所有文件一起导出,其他文件同理

function readFileList(dir, filesList = []) {
 const files = fs.readdirSync(dir)
 files.forEach((item) => {
  let fullPath = path.join(dir, item)
  const stat = fs.statSync(fullPath)
  if (stat.isDirectory()) {
   // 递归读取所有文件
   readFileList(path.join(dir, item), filesList)
  } else {
   /\.js$/.test(fullPath) && filesList.push(fullPath)
  }
 })
 return filesList
}
exports.commonPaths = readFileList(path.join(__dirname, '../src/components'), [])

webpack配置抽离common

import conf from '**';
module.exports = {
 entry: {
  common: conf.commonPaths,
  index: ['babel-polyfill', `./${conf.index}`],
 },
 ... //其他配置
 plugins:[
  new webpack.optimize.CommonsChunkPlugin('common'),
  ... // other plugins
 ]
}

在webpack3中使用CommonsChunkPlugin来提取第三方库和公共模块,传入的参数 common 是entrty已经存在的chunk, 那么就会把公共模块代码合并到这个chunk上。

提取common后的代码

将各个路由重复的代码提取出来后,包的总大小又变为了2.5M。多出了一个common的bundle文件。(common过大,其实还可以继续拆分)

Webpack3+React16代码分割的实现

总结

webpack打包还有很多可以优化的地方,另外不同webpack版本之间也有点差异,拆包思路就是提取公共,根据使用场景按需加载。

到此这篇关于Webpack3+React16代码分割的实现的文章就介绍到这了,更多相关Webpack3+React16代码分割内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
使用javascript实现简单的选项卡切换
Jan 09 Javascript
js插件YprogressBar实现漂亮的进度条效果
Apr 20 Javascript
详解Document.Cookie
Dec 25 Javascript
javascript图片切换综合实例(循环切换、顺序切换)
Jan 13 Javascript
JS面向对象编程详解
Mar 06 Javascript
jquery中封装函数传递当前元素的方法示例
May 05 jQuery
vue router2.0二级路由的简单使用
Jul 05 Javascript
vue 添加vux的代码讲解
Nov 30 Javascript
js删除数组中的元素delete和splice的区别详解
Feb 03 Javascript
微信小程序实现类似微信点击语音播放效果
Mar 30 Javascript
bootstrap table列和表头对不齐的解决方法
Jul 19 Javascript
Nuxt 项目性能优化调研分析
Nov 07 Javascript
微信小程序input抖动问题的修复方法
Mar 03 #Javascript
微信小程序组件生命周期的踩坑记录
Mar 03 #Javascript
vite2.0+vue3移动端项目实战详解
Mar 03 #Vue.js
基于JavaScript实现简单的轮播图
Mar 03 #Javascript
js面向对象方式实现拖拽效果
Mar 03 #Javascript
Vue多选列表组件深入详解
Mar 02 #Vue.js
Vue2.x-使用防抖以及节流的示例
Mar 02 #Vue.js
You might like
PHP中exec函数和shell_exec函数的区别
2014/08/20 PHP
php+resumablejs实现的分块上传 断点续传功能示例
2017/04/18 PHP
ThinkPHP框架整合微信支付之Native 扫码支付模式一图文详解
2019/04/09 PHP
Laravel使用swoole实现websocket主动消息推送的方法介绍
2019/10/20 PHP
浅谈laravel-admin form中的数据,在提交后,保存前,获取并进行编辑
2019/10/21 PHP
php屏蔽错误及提示的方法
2020/05/10 PHP
从sohu弄下来的flash中展示图片的代码
2007/04/27 Javascript
js中的数组Array定义与sort方法使用示例
2013/08/29 Javascript
js判断运行jsp页面的浏览器类型以及版本示例
2013/10/30 Javascript
异步动态加载JS并运行(示例代码)
2013/12/13 Javascript
Javascript自定义函数判断网站访问类型是PC还是移动终端
2014/01/10 Javascript
详解JavaScript的策略模式编程
2015/06/24 Javascript
在JavaScript的jQuery库中操作AJAX的方法讲解
2015/08/15 Javascript
jquery分页插件jquery.pagination.js使用方法解析
2016/04/01 Javascript
浅析Javascript中bind()方法的使用与实现
2016/05/30 Javascript
关于JavaScript 原型链的一点个人理解
2016/07/31 Javascript
JS返回只包含数字类型的数组实例分析
2016/12/16 Javascript
微信小程序网络请求wx.request详解及实例
2017/05/18 Javascript
详解Vue组件插槽的使用以及调用组件内的方法
2018/11/13 Javascript
JavaScript使用Math.random()生成简单的验证码
2019/01/21 Javascript
微信小程序textarea层级过高的解决方法
2019/03/04 Javascript
Vue scrollBehavior 滚动行为实现后退页面显示在上次浏览的位置
2019/05/27 Javascript
Python矩阵常见运算操作实例总结
2017/09/29 Python
在Python中合并字典模块ChainMap的隐藏坑【推荐】
2019/06/27 Python
如何使用Python实现斐波那契数列
2019/07/02 Python
python实现多进程通信实例分析
2019/09/01 Python
使用spring mvc+localResizeIMG实现HTML5端图片压缩上传的功能
2016/12/16 HTML / CSS
什么是JNDI的上下文?如何初始化JNDI上下文
2012/03/10 面试题
房产公证书范本
2014/04/10 职场文书
个人维稳承诺书
2015/05/04 职场文书
刑事附带民事诉讼答辩状
2015/05/22 职场文书
我的法兰西岁月观后感
2015/06/09 职场文书
2016医师资格考试考生诚信考试承诺书
2016/03/25 职场文书
【海涛教你打DOTA】死灵飞龙第一视角解说
2022/04/01 DOTA
Python 装饰器(decorator)常用的创建方式及解析
2022/04/24 Python
解决spring.thymeleaf.cache=false不起作用的问题
2022/06/10 Java/Android