zoukankan      html  css  js  c++  java
  • [转] react-router4 + webpack Code Splitting

    项目升级为react-router4后,就尝试着根据官方文档进行代码分割。https://reacttraining.com/react-router/web/guides/code-splitting

    在实际项目中,js,css文件默认通过webpack打包的话会很大,动不动就好几兆。

    在实际场景中,我们需要缩短首屏的时间展现时间,需要将 首屏没有 涉及到 其他页面的 业务和组件 进行代码分离,按需加载。

    通过按需加载,如果只是修改了某个页面的逻辑,也不用整个项目文件加载,增加了浏览器缓存的利用

    下面就一步一步的介绍在我的项目中是怎么实现Code Splitting的。

    根据webpack文档 https://webpack.js.org/guides/code-splitting/ 推荐的代码分割的方式是  import(), 当然 require.ensure() 作为兼容,还是支持的。

    而react-router4 文档上说到的 bundle-loader 组件就是通过 require.ensure 实现的。

    实际项目中

    a.太小的文件打个包也没有太大的意义,最终考虑的是 每个一级菜单 作为分割点进行打包加载.

    b.能够通过参数配置 分割打包 还是 整体打包,尽可能的在webconfig中进行配置,可以参数化

    c.打包文件名字要有意义,每次打包只是某个module文件修改的话,不会影响其他js文件hash值,提高缓存利用

    下面介绍下最终的结果,其中有些坎坷的心路历程,只能简单的略过了

    1./router/moduleA.router.jsx,把需要打包在一起的文件整理到对用的文件下,方便统一管理

    2.webpack.config.js

    复制代码
    output: {
            //[chunkhash:8] 设置文件摘要,用于缓存
            filename: '[name].[chunkhash:8].bundle.js', //entry 对用生成文件的文件名规则
            chunkFilename : '[name].[chunkhash:8].js',  //不是entry定义的文件(分离打包的模块文件,提取的共同文件),对用生成文件的文件名规则
            path: TARGET,
            publicPath: '/public/'
        },
        plugins: [
            //...
            //...
            //** 设置打包id生成规则,以文件地址 + hash 形式,是生成的  webpack 模块id固定,参见参考文献
            new webpack.HashedModuleIdsPlugin(),
            //提取的共同文件插件配置
            new webpack.optimize.CommonsChunkPlugin({
                //在模块中如果存在的功用的话,也进行提取设置
                //如moduleA,moduleB 中都用了编辑器,entry中没有,则会抽出公用打包在一个  数字.hash.js 中
                async: true, 
                minChunks: 2 //有2处使用的js文件就提取
            }), 
            //vendor: entry文件中用到的共用文件打包在vendor文件
            //** manifest: 增加这个配置,则把一个加载的id信息统一到一个文件,这样就可以实现每次打包未改的文件生成的hash不变,参见参考文献
            new webpack.optimize.CommonsChunkPlugin({
                names: ['vendor', 'manifest']
            }),
            //对应的 chunks 加上 'manifest', 'vendor'
            new HtmlWebpackPlugin({
                filename: `${page.name}.html`,
                template: `${ROOT_PATH}/template.ejs`,
                chunks: ['manifest', 'vendor', page.name]
            }
        ],
        module: {
            rules: [{
                    test: /.router.jsx/,
                    loader: [
                        //根据文件后缀.router.jsx 设置规则,主要是name 和 regExp 的实现,这个可以查看bundle-loader源代码就能发现相关的支持
                        //现在的逻辑是取文件名,/router/moduleA.router.jsx 则打包成 moduleA.hash.js
                        'bundle-loader?lazy&name=[1]&regExp=([^\\\/]*)\.router\.jsx',
                        'babel-loader',
                    ],
                    exclude: /node_modules|assets/
                }, {
                    test: /.jsx?$/,
                    loader: 'babel-loader',
                    exclude: /node_modules|assets/
                }
            ]
        },
    复制代码
    bundle-loader name参数,regExp参数的应用是查看的源代码,一开始想看看是通过什么实现异步加载,就看见了相关插件的源代码
    require.ensure(),想要打包的文件打包在一起,只需要下面代码中的 chunkNameParam 设置成同一个值就可以了,
    实际情况考虑到更好的管理文件,就通过取.router.jsx前面的文件名 进行命名

      

     3.Bundle.jsx

    复制代码
     1 import React, { Component } from 'react'
     2 import { Route } from 'react-router-dom';
     3 const Loading = function () {
     4     return <div></div>
     5 }
     6 //注意props的传递,在组件与Switch,嵌套的时候会有涉及
     7 //增加lazyKey属性,对应moduleA.router.jsx对应的key值
     8 class Bundle extends Component {
     9     state = {
    10         mod: null
    11     }
    12     componentWillMount() {
    13         this.load(this.props)
    14     }
    15     componentWillReceiveProps(nextProps) {
    16         if (nextProps.load !== this.props.load) {
    17             this.load(nextProps)
    18         }
    19     }
    20     load(props) {
    21         var key = props.lazyKey || 'default';
    22         this.setState({
    23             mod: null
    24         })
    25         props.load(mod => {
    26             this.setState({
    27                 mod: mod[key] ? mod[key] : mod
    28             })
    29         })
    30     }
    31     render() {
    32         return this.state.mod?<this.state.mod {...this.props} />:<Loading/>
    33     }
    34 }
    35 //注意props的传递,在组件与Switch,嵌套的时候会有涉及
    36 //通过isLazy进行是否分离打包配置,LazyRoute组件不是必要的,各自需求各自处理
    37 class LazyRoute extends React.PureComponent{
    38     componentMap = {};
    39     render() {
    40         let {menu, ...props} = this.props;
    41         let {component, isLazy, componentKey, path} = menu;
    42         componentKey = componentKey || 'default';
    43         if (!isLazy) {
    44             return (<Route component={component[componentKey] || component} {...props}/>);
    45         } else {
    46             //通过this.componentMap进行缓存,防止不必要的组件重新加载
    47             if (!this.componentMap[path]) {
    48                 this.componentMap[path] = function(props)  {
    49                     return (<Bundle load={component} lazyKey={componentKey} {...props}></Bundle>)
    50                 }
    51             }
    52             return (<Route component={this.componentMap[path]} {...props}/>);
    53         } 
    54     }
    55 }
    56 export {Bundle as default, LazyRoute}
    复制代码

    实际的打包效果,还是可以的,代码得到了分离。有些地方还可以优化,比如 第3方lib包不怎么会变的,和 自己写的组件 进行不同的提取合并。文献中都有提及。

    极致的按需加载 和 异步加载,对代码 组件了解的要求比较高,现在主要还是通过webpack 公共提取 做基本的优化

    参考文件

    http://geek.csdn.net/news/detail/135599    使用 Webpack 打包单页应用的正确姿势

    https://sebastianblade.com/using-webpack-to-achieve-long-term-cache/   用 webpack 实现持久化缓存

  • 相关阅读:
    C#SortedList排序列表怎么样逆序输出
    使 SortList 实现重复键排序
    【转】delphi程序只允许运行一个实例的三种方法:
    Delphi中控制Excel(转载)
    spring和hibernate的集成
    使用Jedis操作redis
    使用java发送邮件
    error at ::0 can't find referenced pointcut...解决方法
    log4j简单的使用
    spring学习笔记三:Component注解(把POJO类实例化到spring的IOC容器中)
  • 原文地址:https://www.cnblogs.com/chris-oil/p/8457528.html
Copyright © 2011-2022 走看看