精华内容
下载资源
问答
  • 路由懒加载

    2020-03-08 00:47:22
  • VUE路由懒加载及组件懒加载 一、为什么要使用路由懒加载  为给客户更好的客户体验,首屏组件加载速度更快一些,解决白屏问题。 懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的...

    VUE路由懒加载及组件懒加载

    一、为什么要使用路由懒加载

      为给客户更好的客户体验,首屏组件加载速度更快一些,解决白屏问题。

       懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时。

    二、定义

      懒加载简单来说就是延迟加载或按需加载,即在需要的时候的时候进行加载。

    三、使用

      常用的懒加载方式有两种:即使用vue异步组件 和 ES中的import

    1、未用懒加载,vue中路由代码如下

     

     

            import Vue from 'vue'
                    import Router from 'vue-router'
                    import HelloWorld from '@/components/HelloWorld'
    
                    Vue.use(Router)
    
                    export default new Router({
                      routes: [
                        {
                          path: '/',
                          name: 'HelloWorld',
                          component:HelloWorld
                        }
                      ]
                    })

     

     

    2、vue异步组件实现懒加载

        方法如下:component:resolve=>(require(['需要加载的路由的地址']),resolve)

     

    import Vue from 'vue'
    import Router from 'vue-router'
      /* 此处省去之前导入的HelloWorld模块 */
    Vue.use(Router)
    
    export default new Router({
      routes: [
        {
          path: '/',
          name: 'HelloWorld',
          component: resolve=>(require(["@/components/HelloWorld"],resolve))
        }
      ]
    })

     

     

    3、ES 提出的import方法,(------最常用------)

        方法如下:const HelloWorld = ()=>import('需要加载的模块地址')

        (不加 { } ,表示直接return)

     

    import Vue from 'vue'
    import Router from 'vue-router'
    
    Vue.use(Router)
    
    const HelloWorld = ()=>import("@/components/HelloWorld")
    export default new Router({
      routes: [
        {
          path: '/',
          name: 'HelloWorld',
          component:HelloWorld
        }
      ]
    })
    

     

     

    四、组件懒加载

     相同与路由懒加载,

    1、原来组件中写法

     

    <template>
      <div class="hello">
      <One-com></One-com>
      1111
      </div>
    </template>
    
    <script>
    import One from './one'
    export default {
      components:{
        "One-com":One
      },
      data () {
        return {
          msg: 'Welcome to Your Vue.js App'
        }
      }
    }
    </script>
    

     

     

    2、const方法

     

    <template>
      <div class="hello">
      <One-com></One-com>
      1111
      </div>
    </template>
    
    <script>
    const One = ()=>import("./one");
    export default {
      components:{
        "One-com":One
      },
      data () {
        return {
          msg: 'Welcome to Your Vue.js App'
        }
      }
    }
    </script>

     

    3、异步方法

     

    <template>
      <div class="hello">
      <One-com></One-com>
      1111
      </div>
    </template>
    
    <script>
    export default {
      components:{
        "One-com":resolve=>(['./one'],resolve)
      },
      data () {
        return {
          msg: 'Welcome to Your Vue.js App'
        }
      }
    }
    </script>

    展开全文
  • 前言说起路由懒加载,大家很快就知道怎么实现它,但是问到路由懒加载的原理,怕有一部分小伙伴是一头雾水了吧。下面带大家一起去理解路由懒加载的原理。路由懒加载也可以叫做路由组件懒加载,最常用的是通过 import...

    前言

    说起路由懒加载,大家很快就知道怎么实现它,但是问到路由懒加载的原理,怕有一部分小伙伴是一头雾水了吧。下面带大家一起去理解路由懒加载的原理。

    路由懒加载也可以叫做路由组件懒加载,最常用的是通过 import() 来实现它。

    function load(component) {
        return () => import(`views/${component}`)
    }

    然后通过Webpack编译打包后,会把每个路由组件的代码分割成一一个js文件,初始化时不会加载这些js文件,只当激活路由组件才会去加载对应的js文件。

    在这里先不管Webpack是怎么按路由组件分割代码,只管在Webpack编译后,怎么实现按需加载对应的路由组件js文件。

    一、准备工作

    1、搭建项目

    想要理解路由懒加载的原理,建议从最简单的项目开始,用Vue Cli3搭建一个项目,其中只包含一个路由组件。在main.js只引入vue-router,其它统统不要。

    main.js

    import Vue from 'vue';
    import App from './App.vue';
    import Router from 'vue-router';
    Vue.use(Router);
    //路由懒加载
    function load(component) {
        return () => import(`views/${component}`)
    }
    // 路由配置
    const router = new Router({
        mode: 'history',
        base: process.env.BASE_URL,
        routes: [
            {
                path: '/',
                name: 'home',
                component: load('Home'),
                meta: {
                    title: '首页'
                }
            },
        ]
    });
    new Vue({
        router,
        render: h => h(App)
    }).$mount('#app')

    views/Home.vue

    <template>
        <div>
            {{tip}}
        </div>
    </template>
    <script>
    export default {
        data(){
            return {
                tip:'欢迎使用Vue项目'
            }
        }
    }
    </script>

    2、webpackChunkName

    利用 webpackChunkName ,使编译打包后的js文件名字能和路由组件一一对应,修改一下load函数。

    function load(component) {
        return () => import(/* webpackChunkName: "[request]" */ `views/${component}`)
    }

    3、去掉代码压缩混淆

    去掉代码压缩混淆,便于我们阅读编译打包后的代码。在vue.config.js中配置

    module.exports={
        chainWebpack:config => {
            config.optimization.minimize(false);
        },
    }

    4、npm run build

    执行命令 npm run build ,编译打包后的dist文件结构如下所示

    v2-c311c1530baa844856d9ce82c2334a60_b.jpg

    其中Home.67f3cd34.js就是路由组件Home.vue编译打包后对应的js文件。

    二、分析index.html

    v2-9a801b0f5237531c35197d37d8cf6fb9_b.jpg

    从上面我们可以看到,先用link定义Home.js、app.js、chunk-vendors.js这些资源和web客户端的关系。

    ref=preload
    rel=prefetch
    as=script
    

    然后在body里面加载了chunk-vendors.js、app.js这两个js资源。可以看出web客户端初始化时候就加载了这个两个js资源。

    三、分析chunk-vendors.js

    chunk-vendors.js可以称为项目公共模块集合,代码精简后如下所示,

    (window["webpackJsonp"] = window["webpackJsonp"] || []).push([["chunk-vendors"],{
        "01f9":(function(module,exports,__webpack_require__){
            ...//省略
        })
        ...//省略
    }])

    从代码中可以看出,执行chunk-vendors.js,仅仅把下面这个数组 pushwindow["webpackJsonp"] 中,而数组第二项是个对象,对象的每个value值是一个函数表达式,不会执行。就这样结束了,当然不是,我们带着 window["webpackJsonp"] 去app.js中找找。

    四、分析app.js

    app.js可以称为项目的入口文件。

    app.js里面是一个自执行函数,通过搜索 window["webpackJsonp"] 可以找到如下相关代码。

    (function(modules){
        //省略...
        var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
        var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
        jsonpArray.push = webpackJsonpCallback;
        jsonpArray = jsonpArray.slice();
        for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
        var parentJsonpFunction = oldJsonpFunction;
        //省略...
    }({
        0:(function(module, exports, __webpack_require__) {
            module.exports = __webpack_require__("56d7");
        })
        //省略...
    }))
    • 先把 window["webpackJsonp"] 赋值给 jsonpArray
    • jsonpArraypush 方法赋值给 oldJsonpFunction
    • webpackJsonpCallback 函数拦截 jsopArraypush 方法,也就是说调用 window["webpackJsonp"]push 方法都会执行 webpackJsonpCallback 函数。
    • jsonpArray 浅拷贝一下再赋值给 jsonpArray
    • 因为执行chunk-vendors.js中的 window["webpackJsonp"].pushpush 方法还未被 webpackJsonpCallback 函数拦截,所以要循环 jsonpArray ,将每项作为参数传入 webpackJsonpCallback 函数并调用。
    • jsonpArraypush 方法再赋值给 parentJsonpFunction

    1、webpackJsonpCallback函数

    接下来我们看一下 webpackJsonpCallback 这个函数。

    (function(modules){
        function webpackJsonpCallback(data) {
            var chunkIds = data[0];
            var moreModules = data[1];
            var executeModules = data[2];
            var moduleId, chunkId, i = 0, resolves = [];
            for (; i < chunkIds.length; i++) {
                chunkId = chunkIds[i];
                if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId)
                && installedChunks[chunkId]) {
                    resolves.push(installedChunks[chunkId][0]);
                }
                installedChunks[chunkId] = 0;
            }
            for (moduleId in moreModules) {
                if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
                    modules[moduleId] = moreModules[moduleId];
                }
            }
            if (parentJsonpFunction) parentJsonpFunction(data);
            while (resolves.length) {
                resolves.shift()();
            }
            deferredModules.push.apply(deferredModules, executeModules || []);
            return checkDeferredModules();
        };
        var installedChunks = {
            "app": 0
        };
        //省略...
    }({
        0:(function(module, exports, __webpack_require__) {
            module.exports = __webpack_require__("56d7");
        })
        //省略...
    }))

    想知道 webpackJsonpCallback 函数有什么作用,要先弄明白 modulesinstalledChunksdeferredModules 这三个变量的作用。

    • module是指任意的代码块,chunk是webpack处理过程中被分组的module的合集。
    • modules 缓存所有的module(代码块),调用 modules 中的module就可以执行里面的代码。
    • installedChunks 缓存所有chunk的加载状态,如果 installedChunks[chunk] 为0,代表chunk已经加载完毕。
    • deferredModules 中每项也是一个数组,例如 [module,chunk1,chunk2,chunk3] ,其作用是如果要执行module,必须在chunk1、chunk2、chunk3都加载完毕后才能执行。

    if (parentJsonpFunction) parentJsonpFunction(data) 这句代码在多入口项目中才有作用,在前面提到过 jsonpArraypush 方法被赋值给 parentJsonpFunction ,调用 parentJsonpFunction 是真正把chunk中push方法中的参数push到 window["webpackJsonp"] 这个数组中。

    比如说现在项目有两个入口,app.js和app1.js,app.js中缓存一些module,在app1.js就可以通过 window["webpackJsonp"] 来调用这些module,调用代码如下。

    for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);

    再来理解 webpackJsonpCallback 函数是不是清楚了很多,接下来看一下checkDeferredModules 这个函数。

    2、checkDeferredModules函数

    var deferredModules = [];
    var installedChunks = {
        "app": 0
    }
    function checkDeferredModules() {
        var result;
        for (var i = 0; i < deferredModules.length; i++) {
            var deferredModule = deferredModules[i];
            var fulfilled = true;
            for (var j = 1; j < deferredModule.length; j++) {
                var depId = deferredModule[j];
                if (installedChunks[depId] !== 0) fulfilled = false;
            }
            if (fulfilled) {
                deferredModules.splice(i--, 1);
                result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
            }
        }
        return result;
    }
    • 循环 deferredModules ,创建变量 fulfilled 表示 deferredModule 中的chunk加载情况, true 表示全部加载完毕, false 表示未全部加载完毕。
    • j=1 开始循环 deferredModule 中的chunk,因为 deferredModule[0] 是module,如果 installedChunks[chunk]!==0 ,则这个chunk未加载完毕,把变量 fulfilled 设置为 false 。循环结束后返回result。
    • 经循环 deferredModule 中的chunk并判断chunk的加载状态后, fulfilled 还是为true,则调用 __webpack_require__ 函数,将 deferredModule[0] (module)作为参数传入执行。
    • deferredModules.splice(i--, 1) ,删除满足条件的deferredModule,并将i减一,其中 i-- 是先使用i,然后在减一。

    因为在 webpackJsonpCallback 函数中 deferredModules[] ,所以回到主体函数继续往下看。

    deferredModules.push([0, "chunk-vendors"]);
    return checkDeferredModules();

    按上面逻辑分析后,会执行 __webpack_require__(0) ,那么来看一下 __webpack_require__ 这个函数。

    3、__webpack_require__函数

    var installedModules = {};
    function __webpack_require__(moduleId) {
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        var module = installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        };
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
        module.l = true;
        return module.exports;
    }

    从代码可知 __webpack_require__ 就是一个执行module的方法。

    installedModules
    webpackJsonpCallback
    

    所以执行 __webpack_require__(0) ,其实就是执行下面的代码。

    (function (module, exports, __webpack_require__) {
        module.exports = __webpack_require__("56d7");
    }),

    在里面又用 __webpack_require__ 执行id为56d7的module,我们找到对应的module继续看,看一下里面关键的代码片段。

    function load(component) {
        return function () {
            return __webpack_require__("9dac")("./".concat(component));
        };
    }
    var routes = [{
        path: '/',
        name: 'home',
        component: load('Home'),
        meta: {
            title: '首页'
        }
    }, {
        path: '*',
        redirect: {
            path: '/'
        }
    }];

    看到这里是不是非常熟悉了,就是配置路由的地方。 load 还是作为加载路由组件的函数,里面用 __webpack_require__("9dac") 返回的方法来执行加载路由组件,我们来看一下 __webpack_require__("9dac")

    (function (module, exports, __webpack_require__) {
        var map = {
            "./Home": [
                "bb51",
                "Home"
            ],
            "./Home.vue": [
                "bb51",
                "Home"
            ]
        };
        function webpackAsyncContext(req) {
            if (!__webpack_require__.o(map, req)) {
                return Promise.resolve().then(function () {
                    var e = new Error("Cannot find module '" + req + "'");
                    e.code = 'MODULE_NOT_FOUND';
                    throw e;
                });
            }
            var ids = map[req], id = ids[0];
            return __webpack_require__.e(ids[1]).then(function () {
                return __webpack_require__(id);
            });
        }
        webpackAsyncContext.keys = function webpackAsyncContextKeys() {
            return Object.keys(map);
        };
        webpackAsyncContext.id = "9dac";
        module.exports = webpackAsyncContext;
    })

    4、webpackAsyncContext函数

    其中的关键函数为 webpackAsyncContext ,调用 load('Home') 时, req'./Home'__webpack_require__.o 方法为

    __webpack_require__.o = function (object, property) {
        return Object.prototype.hasOwnProperty.call(object, property);
    };

    这个方法就是判断在变量 map 中有没有key为 ./Home 的项,如果没有抛出 Cannot find module './Home' 的错误。有执行 __webpack_require__.e 方法,参数为 Home

    5、__webpack_require__.e方法

    var installedChunks = {
        "app": 0
    }
    __webpack_require__.p = "/";
    function jsonpScriptSrc(chunkId) {
        return __webpack_require__.p + "js/" + ({ "Home": "Home" }[chunkId] || chunkId) +
        "." + { "Home": "37ee624e" }[chunkId] + ".js"
    }
    __webpack_require__.e = function requireEnsure(chunkId) {
        var promises = [];
        var installedChunkData = installedChunks[chunkId];
        if (installedChunkData !== 0) {
            if (installedChunkData) {
                promises.push(installedChunkData[2]);
            } else {
                var promise = new Promise(function (resolve, reject) {
                    installedChunkData = installedChunks[chunkId] = [resolve, reject];
                });
                promises.push(installedChunkData[2] = promise);
                var script = document.createElement('script');
                var onScriptComplete;
                script.charset = 'utf-8';
                script.timeout = 120;
                if (__webpack_require__.nc) {
                    script.setAttribute("nonce", __webpack_require__.nc);
                }
                script.src = jsonpScriptSrc(chunkId);
                var error = new Error();
                onScriptComplete = function (event) {
                    // 避免IE内存泄漏。
                    script.onerror = script.onload = null;
                    clearTimeout(timeout);
                    var chunk = installedChunks[chunkId];
                    if (chunk !== 0) {
                        if (chunk) {
                            var errorType = event && 
                            (event.type === 'load' ? 'missing' : event.type);
                            var realSrc = event && event.target && event.target.src;
                            error.message = 'Loading chunk ' + chunkId 
                            + ' failed.n(' + errorType + ': ' + realSrc + ')';
                            error.name = 'ChunkLoadError';
                            error.type = errorType;
                            error.request = realSrc;
                            chunk[1](error);
                        }
                        installedChunks[chunkId] = undefined;
                    }
                };
                var timeout = setTimeout(function () {
                    onScriptComplete({ type: 'timeout', target: script });
                }, 120000);
                script.onerror = script.onload = onScriptComplete;
                document.head.appendChild(script);
            }
        }
        return Promise.all(promises);
    };

    __webpack_require__.e 方法是实现懒加载的核心 ,在这个方法里面处理了三件事情。

    installedChunks
    

    chunk加载的三种状态

    • installedChunks[chunkId]0 ,代表该chunk已经加载完毕。
    • installedChunks[chunkId]undefined ,代表该chunk加载失败、加载超时、从未加载过。
    • installedChunks[chunkId]Promise 对象,代表该chunk正在加载。

    chunk加载超时处理

    script.timeout = 120;
    var timeout = setTimeout(function () {
        onScriptComplete({ type: 'timeout', target: script });
    }, 120000);

    script.timeout = 120 代表该chunk加载120秒后还没加载完毕则超时。

    setTimeout 设置个120秒的计时器,在120秒后执行 onScriptComplete({ type: 'timeout', target: script })

    在看一下 onScriptComplete 函数

    var onScriptComplete = function (event) {
        // 避免IE内存泄漏。
        script.onerror = script.onload = null;
        clearTimeout(timeout);
        var chunk = installedChunks[chunkId];
        if (chunk !== 0) {
            if (chunk) {
                var errorType = event && (event.type === 'load' ? 'missing' : event.type);
                var realSrc = event && event.target && event.target.src;
                error.message = 'Loading chunk ' + chunkId 
                + ' failed.n(' + errorType + ': ' + realSrc + ')';
                error.name = 'ChunkLoadError';
                error.type = errorType;
                error.request = realSrc;
                chunk[1](error);
            }
            installedChunks[chunkId] = undefined;
        }
    };

    此时chunkId为 Home ,加载是Home.js,代码是

    (window["webpackJsonp"] = window["webpackJsonp"] || []).push([["Home"],{
        "bb51":(function(module, __webpack_exports__, __webpack_require__){
            //省略...
        })
    }]))

    在前面有提到 window["webpackJsonp"] 的push方法被 webpackJsonpCallback 函数拦截了,如果Home.js加载成功会自动执行,随后会执行 webpackJsonpCallback 函数,其中有 installedChunks[chunkId] = 0; 会把 installedChunks['Home'] 的值置为0。

    也就是说,如果Home.js加载超时了,就不能执行,就不能将 installedChunks['Home'] 的值置为0,所以此时 installedChunks['Home'] 的值还是 Promise 对象。那么就会进入以下代码执行,最后 chunk[1](error) 将错误抛出去。

    var chunk = installedChunks[chunkId];
    if(chunk!==0){
        if(chunk){
            var errorType = event && (event.type === 'load' ? 'missing' : event.type);
            var realSrc = event && event.target && event.target.src;
            error.message = 'Loading chunk ' + chunkId 
            + ' failed.n(' + errorType + ': ' + realSrc + ')';
            error.name = 'ChunkLoadError';
            error.type = errorType;
            error.request = realSrc;
            chunk[1](error);
        }
    }

    chunk[1] 其实就是reject函数,在以下代码中给它赋值的。

    var promise = new Promise(function (resolve, reject) {
        installedChunkData = installedChunks[chunkId] = [resolve, reject];
    });

    chunk加载失败处理

    加载失败分为两种情况,一是Home.js资源加载失败,二是资源加载成功了,但是执行Home.js里面代码出错了导致失败,所以chunk加载失败处理的代码要这么写

    script.onerror = script.onload = onScriptComplete;

    后面处理的方式和处理加载超时的一样。

    __webpack_require__.e 最后返回是一个 Promise 对象。回到 webpackAsyncContext 函数中

    return __webpack_require__.e(ids[1]).then(function () {
        return __webpack_require__(id);
    });

    __webpack_require__.e(ids[1]) 执行成功后,执行 __webpack_require__(id); ,此时id为bb51。那么又回到 __webpack_require__ 函数中了。在前面提过 __webpack_require__ 函数的作用就是执行module。id为bb51的nodule是在Home.js内,在 webpackJsonpCallback 函数有以下代码

    function webpackJsonpCallback(data) {
        var moreModules = data[1];
        for (moduleId in moreModules) {
            if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
                modules[moduleId] = moreModules[moduleId];
            }
        }
    }

    五、分析Home.js

    Home.js

    (window["webpackJsonp"] = window["webpackJsonp"] || []).push([["Home"],{
        "bb51":(function(module, __webpack_exports__, __webpack_require__){
            //省略...
        })
    }]))

    可以看出moreModules就是 {"bb51":(function(module, __webpack_exports__, __webpack_require__){})} ,

    循环moreModules,把Home.js里面的module缓存到app.js里面的modules中。

    再看 __webpack_require__ 函数中有这段代码

    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

    这样就执行了Home.js里面的module,在module里面有渲染页面的一系列的方法,就把Home.vue这个路由组件页面渲染出来了。

    到这里路由组件懒加载的整个流程就结束了,也详细介绍了怎么加载chunk和怎么执行module。前言

    说起路由懒加载,大家很快就知道怎么实现它,但是问到路由懒加载的原理,怕有一部分小伙伴是一头雾水了吧。下面带大家一起去理解路由懒加载的原理。

    路由懒加载也可以叫做路由组件懒加载,最常用的是通过 import() 来实现它。

    function load(component) {
        return () => import(`views/${component}`)
    }

    然后通过Webpack编译打包后,会把每个路由组件的代码分割成一一个js文件,初始化时不会加载这些js文件,只当激活路由组件才会去加载对应的js文件。

    在这里先不管Webpack是怎么按路由组件分割代码,只管在Webpack编译后,怎么实现按需加载对应的路由组件js文件。

    一、准备工作

    1、搭建项目

    想要理解路由懒加载的原理,建议从最简单的项目开始,用Vue Cli3搭建一个项目,其中只包含一个路由组件。在main.js只引入vue-router,其它统统不要。

    main.js

    import Vue from 'vue';
    import App from './App.vue';
    import Router from 'vue-router';
    Vue.use(Router);
    //路由懒加载
    function load(component) {
        return () => import(`views/${component}`)
    }
    // 路由配置
    const router = new Router({
        mode: 'history',
        base: process.env.BASE_URL,
        routes: [
            {
                path: '/',
                name: 'home',
                component: load('Home'),
                meta: {
                    title: '首页'
                }
            },
        ]
    });
    new Vue({
        router,
        render: h => h(App)
    }).$mount('#app')

    views/Home.vue

    <template>
        <div>
            {{tip}}
        </div>
    </template>
    <script>
    export default {
        data(){
            return {
                tip:'欢迎使用Vue项目'
            }
        }
    }
    </script>

    2、webpackChunkName

    利用 webpackChunkName ,使编译打包后的js文件名字能和路由组件一一对应,修改一下load函数。

    function load(component) {
        return () => import(/* webpackChunkName: "[request]" */ `views/${component}`)
    }

    3、去掉代码压缩混淆

    去掉代码压缩混淆,便于我们阅读编译打包后的代码。在vue.config.js中配置

    module.exports={
        chainWebpack:config => {
            config.optimization.minimize(false);
        },
    }

    4、npm run build

    执行命令 npm run build ,编译打包后的dist文件结构如下所示

    v2-c311c1530baa844856d9ce82c2334a60_b.jpg

    其中Home.67f3cd34.js就是路由组件Home.vue编译打包后对应的js文件。

    二、分析index.html

    v2-9a801b0f5237531c35197d37d8cf6fb9_b.jpg

    从上面我们可以看到,先用link定义Home.js、app.js、chunk-vendors.js这些资源和web客户端的关系。

    ref=preload
    rel=prefetch
    as=script
    

    然后在body里面加载了chunk-vendors.js、app.js这两个js资源。可以看出web客户端初始化时候就加载了这个两个js资源。

    三、分析chunk-vendors.js

    chunk-vendors.js可以称为项目公共模块集合,代码精简后如下所示,

    (window["webpackJsonp"] = window["webpackJsonp"] || []).push([["chunk-vendors"],{
        "01f9":(function(module,exports,__webpack_require__){
            ...//省略
        })
        ...//省略
    }])

    从代码中可以看出,执行chunk-vendors.js,仅仅把下面这个数组 pushwindow["webpackJsonp"] 中,而数组第二项是个对象,对象的每个value值是一个函数表达式,不会执行。就这样结束了,当然不是,我们带着 window["webpackJsonp"] 去app.js中找找。

    四、分析app.js

    app.js可以称为项目的入口文件。

    app.js里面是一个自执行函数,通过搜索 window["webpackJsonp"] 可以找到如下相关代码。

    (function(modules){
        //省略...
        var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
        var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
        jsonpArray.push = webpackJsonpCallback;
        jsonpArray = jsonpArray.slice();
        for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
        var parentJsonpFunction = oldJsonpFunction;
        //省略...
    }({
        0:(function(module, exports, __webpack_require__) {
            module.exports = __webpack_require__("56d7");
        })
        //省略...
    }))
    • 先把 window["webpackJsonp"] 赋值给 jsonpArray
    • jsonpArraypush 方法赋值给 oldJsonpFunction
    • webpackJsonpCallback 函数拦截 jsopArraypush 方法,也就是说调用 window["webpackJsonp"]push 方法都会执行 webpackJsonpCallback 函数。
    • jsonpArray 浅拷贝一下再赋值给 jsonpArray
    • 因为执行chunk-vendors.js中的 window["webpackJsonp"].pushpush 方法还未被 webpackJsonpCallback 函数拦截,所以要循环 jsonpArray ,将每项作为参数传入 webpackJsonpCallback 函数并调用。
    • jsonpArraypush 方法再赋值给 parentJsonpFunction

    1、webpackJsonpCallback函数

    接下来我们看一下 webpackJsonpCallback 这个函数。

    (function(modules){
        function webpackJsonpCallback(data) {
            var chunkIds = data[0];
            var moreModules = data[1];
            var executeModules = data[2];
            var moduleId, chunkId, i = 0, resolves = [];
            for (; i < chunkIds.length; i++) {
                chunkId = chunkIds[i];
                if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId)
                && installedChunks[chunkId]) {
                    resolves.push(installedChunks[chunkId][0]);
                }
                installedChunks[chunkId] = 0;
            }
            for (moduleId in moreModules) {
                if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
                    modules[moduleId] = moreModules[moduleId];
                }
            }
            if (parentJsonpFunction) parentJsonpFunction(data);
            while (resolves.length) {
                resolves.shift()();
            }
            deferredModules.push.apply(deferredModules, executeModules || []);
            return checkDeferredModules();
        };
        var installedChunks = {
            "app": 0
        };
        //省略...
    }({
        0:(function(module, exports, __webpack_require__) {
            module.exports = __webpack_require__("56d7");
        })
        //省略...
    }))

    想知道 webpackJsonpCallback 函数有什么作用,要先弄明白 modulesinstalledChunksdeferredModules 这三个变量的作用。

    • module是指任意的代码块,chunk是webpack处理过程中被分组的module的合集。
    • modules 缓存所有的module(代码块),调用 modules 中的module就可以执行里面的代码。
    • installedChunks 缓存所有chunk的加载状态,如果 installedChunks[chunk] 为0,代表chunk已经加载完毕。
    • deferredModules 中每项也是一个数组,例如 [module,chunk1,chunk2,chunk3] ,其作用是如果要执行module,必须在chunk1、chunk2、chunk3都加载完毕后才能执行。

    if (parentJsonpFunction) parentJsonpFunction(data) 这句代码在多入口项目中才有作用,在前面提到过 jsonpArraypush 方法被赋值给 parentJsonpFunction ,调用 parentJsonpFunction 是真正把chunk中push方法中的参数push到 window["webpackJsonp"] 这个数组中。

    比如说现在项目有两个入口,app.js和app1.js,app.js中缓存一些module,在app1.js就可以通过 window["webpackJsonp"] 来调用这些module,调用代码如下。

    for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);

    再来理解 webpackJsonpCallback 函数是不是清楚了很多,接下来看一下checkDeferredModules 这个函数。

    2、checkDeferredModules函数

    var deferredModules = [];
    var installedChunks = {
        "app": 0
    }
    function checkDeferredModules() {
        var result;
        for (var i = 0; i < deferredModules.length; i++) {
            var deferredModule = deferredModules[i];
            var fulfilled = true;
            for (var j = 1; j < deferredModule.length; j++) {
                var depId = deferredModule[j];
                if (installedChunks[depId] !== 0) fulfilled = false;
            }
            if (fulfilled) {
                deferredModules.splice(i--, 1);
                result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
            }
        }
        return result;
    }
    • 循环 deferredModules ,创建变量 fulfilled 表示 deferredModule 中的chunk加载情况, true 表示全部加载完毕, false 表示未全部加载完毕。
    • j=1 开始循环 deferredModule 中的chunk,因为 deferredModule[0] 是module,如果 installedChunks[chunk]!==0 ,则这个chunk未加载完毕,把变量 fulfilled 设置为 false 。循环结束后返回result。
    • 经循环 deferredModule 中的chunk并判断chunk的加载状态后, fulfilled 还是为true,则调用 __webpack_require__ 函数,将 deferredModule[0] (module)作为参数传入执行。
    • deferredModules.splice(i--, 1) ,删除满足条件的deferredModule,并将i减一,其中 i-- 是先使用i,然后在减一。

    因为在 webpackJsonpCallback 函数中 deferredModules[] ,所以回到主体函数继续往下看。

    deferredModules.push([0, "chunk-vendors"]);
    return checkDeferredModules();

    按上面逻辑分析后,会执行 __webpack_require__(0) ,那么来看一下 __webpack_require__ 这个函数。

    3、__webpack_require__函数

    var installedModules = {};
    function __webpack_require__(moduleId) {
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        var module = installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        };
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
        module.l = true;
        return module.exports;
    }

    从代码可知 __webpack_require__ 就是一个执行module的方法。

    installedModules
    webpackJsonpCallback
    

    所以执行 __webpack_require__(0) ,其实就是执行下面的代码。

    (function (module, exports, __webpack_require__) {
        module.exports = __webpack_require__("56d7");
    }),

    在里面又用 __webpack_require__ 执行id为56d7的module,我们找到对应的module继续看,看一下里面关键的代码片段。

    function load(component) {
        return function () {
            return __webpack_require__("9dac")("./".concat(component));
        };
    }
    var routes = [{
        path: '/',
        name: 'home',
        component: load('Home'),
        meta: {
            title: '首页'
        }
    }, {
        path: '*',
        redirect: {
            path: '/'
        }
    }];

    看到这里是不是非常熟悉了,就是配置路由的地方。 load 还是作为加载路由组件的函数,里面用 __webpack_require__("9dac") 返回的方法来执行加载路由组件,我们来看一下 __webpack_require__("9dac")

    (function (module, exports, __webpack_require__) {
        var map = {
            "./Home": [
                "bb51",
                "Home"
            ],
            "./Home.vue": [
                "bb51",
                "Home"
            ]
        };
        function webpackAsyncContext(req) {
            if (!__webpack_require__.o(map, req)) {
                return Promise.resolve().then(function () {
                    var e = new Error("Cannot find module '" + req + "'");
                    e.code = 'MODULE_NOT_FOUND';
                    throw e;
                });
            }
            var ids = map[req], id = ids[0];
            return __webpack_require__.e(ids[1]).then(function () {
                return __webpack_require__(id);
            });
        }
        webpackAsyncContext.keys = function webpackAsyncContextKeys() {
            return Object.keys(map);
        };
        webpackAsyncContext.id = "9dac";
        module.exports = webpackAsyncContext;
    })

    4、webpackAsyncContext函数

    其中的关键函数为 webpackAsyncContext ,调用 load('Home') 时, req'./Home'__webpack_require__.o 方法为

    __webpack_require__.o = function (object, property) {
        return Object.prototype.hasOwnProperty.call(object, property);
    };

    这个方法就是判断在变量 map 中有没有key为 ./Home 的项,如果没有抛出 Cannot find module './Home' 的错误。有执行 __webpack_require__.e 方法,参数为 Home

    5、__webpack_require__.e方法

    var installedChunks = {
        "app": 0
    }
    __webpack_require__.p = "/";
    function jsonpScriptSrc(chunkId) {
        return __webpack_require__.p + "js/" + ({ "Home": "Home" }[chunkId] || chunkId) +
        "." + { "Home": "37ee624e" }[chunkId] + ".js"
    }
    __webpack_require__.e = function requireEnsure(chunkId) {
        var promises = [];
        var installedChunkData = installedChunks[chunkId];
        if (installedChunkData !== 0) {
            if (installedChunkData) {
                promises.push(installedChunkData[2]);
            } else {
                var promise = new Promise(function (resolve, reject) {
                    installedChunkData = installedChunks[chunkId] = [resolve, reject];
                });
                promises.push(installedChunkData[2] = promise);
                var script = document.createElement('script');
                var onScriptComplete;
                script.charset = 'utf-8';
                script.timeout = 120;
                if (__webpack_require__.nc) {
                    script.setAttribute("nonce", __webpack_require__.nc);
                }
                script.src = jsonpScriptSrc(chunkId);
                var error = new Error();
                onScriptComplete = function (event) {
                    // 避免IE内存泄漏。
                    script.onerror = script.onload = null;
                    clearTimeout(timeout);
                    var chunk = installedChunks[chunkId];
                    if (chunk !== 0) {
                        if (chunk) {
                            var errorType = event && 
                            (event.type === 'load' ? 'missing' : event.type);
                            var realSrc = event && event.target && event.target.src;
                            error.message = 'Loading chunk ' + chunkId 
                            + ' failed.n(' + errorType + ': ' + realSrc + ')';
                            error.name = 'ChunkLoadError';
                            error.type = errorType;
                            error.request = realSrc;
                            chunk[1](error);
                        }
                        installedChunks[chunkId] = undefined;
                    }
                };
                var timeout = setTimeout(function () {
                    onScriptComplete({ type: 'timeout', target: script });
                }, 120000);
                script.onerror = script.onload = onScriptComplete;
                document.head.appendChild(script);
            }
        }
        return Promise.all(promises);
    };

    __webpack_require__.e 方法是实现懒加载的核心 ,在这个方法里面处理了三件事情。

    installedChunks
    

    chunk加载的三种状态

    • installedChunks[chunkId]0 ,代表该chunk已经加载完毕。
    • installedChunks[chunkId]undefined ,代表该chunk加载失败、加载超时、从未加载过。
    • installedChunks[chunkId]Promise 对象,代表该chunk正在加载。

    chunk加载超时处理

    script.timeout = 120;
    var timeout = setTimeout(function () {
        onScriptComplete({ type: 'timeout', target: script });
    }, 120000);

    script.timeout = 120 代表该chunk加载120秒后还没加载完毕则超时。

    setTimeout 设置个120秒的计时器,在120秒后执行 onScriptComplete({ type: 'timeout', target: script })

    在看一下 onScriptComplete 函数

    var onScriptComplete = function (event) {
        // 避免IE内存泄漏。
        script.onerror = script.onload = null;
        clearTimeout(timeout);
        var chunk = installedChunks[chunkId];
        if (chunk !== 0) {
            if (chunk) {
                var errorType = event && (event.type === 'load' ? 'missing' : event.type);
                var realSrc = event && event.target && event.target.src;
                error.message = 'Loading chunk ' + chunkId 
                + ' failed.n(' + errorType + ': ' + realSrc + ')';
                error.name = 'ChunkLoadError';
                error.type = errorType;
                error.request = realSrc;
                chunk[1](error);
            }
            installedChunks[chunkId] = undefined;
        }
    };

    此时chunkId为 Home ,加载是Home.js,代码是

    (window["webpackJsonp"] = window["webpackJsonp"] || []).push([["Home"],{
        "bb51":(function(module, __webpack_exports__, __webpack_require__){
            //省略...
        })
    }]))

    在前面有提到 window["webpackJsonp"] 的push方法被 webpackJsonpCallback 函数拦截了,如果Home.js加载成功会自动执行,随后会执行 webpackJsonpCallback 函数,其中有 installedChunks[chunkId] = 0; 会把 installedChunks['Home'] 的值置为0。

    也就是说,如果Home.js加载超时了,就不能执行,就不能将 installedChunks['Home'] 的值置为0,所以此时 installedChunks['Home'] 的值还是 Promise 对象。那么就会进入以下代码执行,最后 chunk[1](error) 将错误抛出去。

    var chunk = installedChunks[chunkId];
    if(chunk!==0){
        if(chunk){
            var errorType = event && (event.type === 'load' ? 'missing' : event.type);
            var realSrc = event && event.target && event.target.src;
            error.message = 'Loading chunk ' + chunkId 
            + ' failed.n(' + errorType + ': ' + realSrc + ')';
            error.name = 'ChunkLoadError';
            error.type = errorType;
            error.request = realSrc;
            chunk[1](error);
        }
    }

    chunk[1] 其实就是reject函数,在以下代码中给它赋值的。

    var promise = new Promise(function (resolve, reject) {
        installedChunkData = installedChunks[chunkId] = [resolve, reject];
    });

    chunk加载失败处理

    加载失败分为两种情况,一是Home.js资源加载失败,二是资源加载成功了,但是执行Home.js里面代码出错了导致失败,所以chunk加载失败处理的代码要这么写

    script.onerror = script.onload = onScriptComplete;

    后面处理的方式和处理加载超时的一样。

    __webpack_require__.e 最后返回是一个 Promise 对象。回到 webpackAsyncContext 函数中

    return __webpack_require__.e(ids[1]).then(function () {
        return __webpack_require__(id);
    });

    __webpack_require__.e(ids[1]) 执行成功后,执行 __webpack_require__(id); ,此时id为bb51。那么又回到 __webpack_require__ 函数中了。在前面提过 __webpack_require__ 函数的作用就是执行module。id为bb51的nodule是在Home.js内,在 webpackJsonpCallback 函数有以下代码

    function webpackJsonpCallback(data) {
        var moreModules = data[1];
        for (moduleId in moreModules) {
            if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
                modules[moduleId] = moreModules[moduleId];
            }
        }
    }

    五、分析Home.js

    Home.js

    (window["webpackJsonp"] = window["webpackJsonp"] || []).push([["Home"],{
        "bb51":(function(module, __webpack_exports__, __webpack_require__){
            //省略...
        })
    }]))

    可以看出moreModules就是 {"bb51":(function(module, __webpack_exports__, __webpack_require__){})} ,

    循环moreModules,把Home.js里面的module缓存到app.js里面的modules中。

    再看 __webpack_require__ 函数中有这段代码

    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

    这样就执行了Home.js里面的module,在module里面有渲染页面的一系列的方法,就把Home.vue这个路由组件页面渲染出来了。

    到这里路由组件懒加载的整个流程就结束了,也详细介绍了怎么加载chunk和怎么执行module。

    展开全文
  • 懒加载简单来说就是延迟加载或按需加载,即在需要的时候的时候进行加载。这篇文章主要介绍了vue路由懒加载及组件懒加载 ,需要的朋友可以参考下
  • vue-router路由懒加载 懒加载:也叫延迟加载,即在需要的时候进行加载,随用随载。 懒加载的意义:像vue这种单页面应用,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,需要加载的内容...

    vue-router路由懒加载

    懒加载:也叫延迟加载,即在需要的时候进行加载,随用随载。

    懒加载的意义:像vue这种单页面应用,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,时间过长,会出现长时间的白屏,即使做了loading也是不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时,减少资源的浪费
    简单的说就是:进入首页不用一次加载过多资源造成用时过长

    懒加载写法
     在这里插入图片描述
     非懒加载的路由配置:
     在这里插入图片描述
      还有一点:是我遇到的如果你遇到了就看下——就是用了懒加载后打完包直接运行那个index.html会报错,报文件引用错误其实是打包时候路径配置有点问题修改下就好了
      在这里插入图片描述
     找到build下面的webpack.prod.conf.js 添加 publicPath:"./",

    react-router路由懒加载

    传统的两种方式
    import()
    符合ECMAScript提议的import()语法,该提案与普通 import 语句或 require 函数的类似,但返回一个 Promise 对象。这意味着模块时异步加载的

    webpack v2+ 使用

    使用方式

    
    function component() {
     return import( /* webpackChunkName: "lodash" */ 'lodash').then(_ => {
      var element = document.createElement('div');
      element.innerHTML = _.join(['Hello', 'webpack'], ' ');
      return element;
     }).catch(error => 'An error occurred while loading the component');
    }
     
    // 或者使用async
     
    async function getComponent() {
     var element = document.createElement('div');
     const _ = await import(/* webpackChunkName: "lodash" */ 'lodash');
     element.innerHTML = _.join(['Hello', 'webpack'], ' ');
     return element;
    }
    

    require.ensure
    webpack指定的使用方式

    webpack v1 v2 指定使用方式

    使用方式

    require.ensure([], function(require){
      var list = require('./list');
      list.show();'list');
     
    <!-- Router -->
    const Foo = require.ensure([], () => {
      require("Foo");
    }, err => {
      console.error("We failed to load chunk: " + err);
    }, "chunk-name");
     
    //react-router2 or 3
    <Route path="/foo" getComponent={Foo} />
    

    lazyload-loader
    相对于前两种,此种方式写法更为简洁。

    使用方式

    // webpack 配置文件中 使用lazyload-loader(必须将lazuyload-loader 放置在use的最右侧)
     
    module: {
      rules: [
       {
        test: /\.(js|jsx)$/,,
        use: [
         'babel-loader',
         'lazyload-loader'
        ]
       },
    

    业务代码中

    // 使用lazy! 前缀 代表需要懒加载的Router
     
    import Shop from 'lazy!./src/view/Shop';
     
    // Router 正常使用
    <Route path="/shop" component={Shop} />
    
    展开全文
  • 路由懒加载与组件懒加载 一、为什么要使用路由懒加载  为给客户更好的客户体验,首屏组件加载速度更快一些,解决白屏问题。 二、定义 懒加载简单来说就是延迟加载或按需加载,即在需要的时候的时候进行加载。 三、...
  • 为什么要使用路由懒加载  为给客户更好的客户体验,首屏组件加载速度更快一些,解决白屏问题。 二、定义  懒加载简单来说就是延迟加载或按需加载,即在需要的时候的时候进行加载。 三、使用  常用的懒加载...
  • Vue路由懒加载.md

    2021-01-14 10:08:31
    Vue路由懒加载.md
  • vue路由懒加载和组件懒加载其实原理一样 常用的懒加载方式有两种:即使用vue异步组件 和 ES中的import 路由懒加载 ES 提出的import方法, 方法如下:const HelloWorld = ()=>import('需要加载的模块地址') ...
  • VUE项目路由懒加载

    2019-04-11 11:57:43
    路由懒加载

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,404
精华内容 961
关键字:

路由懒加载