vue 不使用 webpack,vue-router 怎么异步

1,597 阅读4分钟
原文链接: blog.zeromake.com

公司项目都是后台管理项,肯定是导航加主显示区的方式切换,然后就想着用vue_router看看。 然后发现vue_router的component是同步加载的。。要是后台功能多js就很大了,这个就是所谓的单页应用。 但是单页应用不用webpack打包手动把js写一个也太蛋疼了。然后发现了webpack用chunk可以异步会把每个路由的js单独打包。 具体的原理下面也会讲解。

零、一些注意事项

  1. 源码在这里

一、vue_router_demo

先来个vue_router的demo用requirejs加载其它js。

vue_router_demo.html

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Title</title>
    <script type="text/javascript" data-main="js/main.js"
    src="./bower_components/requirejs/require.js"></script>
</head>
<body>
<div id="app">
</div>
</body>
</html>

js/main.js

(function(require, define) {
    require.config({
        paths: {
            'vue': 'https://cdn.bootcss.com/vue/2.0.5/vue',
            'vue-router': 'https://cdn.bootcss.com/vue-router/2.0.1/vue-router'
        }
    });

    define(function(require, exports, module) {
        var Vue = require('vue');
        var VueRouter = require('vue-router');
        Vue.use(VueRouter);
        var router = new VueRouter({
            routes: [{
                    path: '/component1',
                    component: function(resolve) {
                        resolve(require('js/component1.js'));
                    }
                },
                {
                    path: '/component2',
                    component: function(resolve) {
                        resolve(require('js/component2.js'));
                    }
                },
            ]
        })
        var app = new Vue({
            el: '#app',
            router: router,
            template: '<div class="content">\
            <h1>Hello Content!</h1>\
            <p>\
             <router-link to="/component1">Go to Component1</router-link>\
             <router-link to="/component2">Go to Component2</router-link>\
            </p>\
            <router-view></router-view>\
            </div>'
        });
    });
})(require, define);

js/component1.js

(function(define){
    define(function(require, exports, module){
        return {
            template: '<span>component1</span>'
        }
    });
})(define);
我看了webpack的.vue编译完后导出的对象也不过是有特定属性的对象,必须有template或者render其中一个。

js/component2.js

(function(define){
    define(function(require, exports, module){
        return {
            template: '<h1>component2</h1>'
        }
    });
})(define);
再来一个不一样的。

示例效果图

vue_router_demo

源码

在git仓库的vue_router_demo分支。

总结

看了图我想你已经发现了问题明明component用的require但是怎么回事第一次刷新首页都引入了。 实际上因为requirejs的require会收集所有使用require的地方并一同加载甚至定义一个方法里就require一个模块,也会预先加载js。 这个结果照成我们的异步变成了多文件SPA在一开始就会把所有模块导入。如果想做这样的SPA不要使用这样的方法,请使用webpack打包成一个文件减少请求。

二、vue_router_async_demo

现在我们就来一步步分析怎么做到。

查找资料

通过搜索引擎的查找翻到了一篇博文分享(Angular 和 Vue)按需加载的项目实践优化方案 里面给了他的参考资料:

  1. 异步组件
  2. webpack

然后我写了一个测试效果是我想要的异步。然后看了生成的代码异步用的js的开头webpackJsonp([1,3],[...])。 而导入模块的地方使用__webpack_require__([1,3], resolve)还有__webpack_require__中有这样一段代码:

var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.charset = 'utf-8';
script.async = true;
script.src = ''// del;
head.appendChild(script);
这下就明白了__webpack_require__(chunkIds, callback)调用后查找是否已有chunkIds的模块有的话通过callback调用返回。 没有就通过chunkIds找到文件并在head中添加一个script加载新js然后js中调用webpackJsonp并通过callback返回模块。

好了这下找到方法了我们自己写个webpackJsonpwebpack_require就好了。代码可能有点多用注释的方式来解释。

js/webpackjsonp.js

(function(define) {
    define(function() {
        // 模块缓存区。
        var webpackJsonpModule = {};
        // 回调方法缓存区。
        var webpackInstallModule = {};
        // 引入的js所需调用的方法。
        var webpackJsonpCallback = function webpackJsonpCallback_(chunkName, Module) {
            // 判断模块名
            if (webpackJsonpModule[chunkName]) {
                console.warn('模块名: ' + chunkName + '已存在!为其旧模块重命名到:' + chunkName + '_')
                webpackJsonpModule[chunkName + '_'] = webpackJsonpModule[chunkName];
            }
            // 调用模块的方法获得模块并设置到模块缓存区。
            webpackJsonpModule[chunkName] = Module();
            // 取出回调方法
            resolve = webpackInstallModule[chunkName]
            if (resolve) {
                // 调用回调方法
                resolve(webpackJsonpModule[chunkName]);
                // 删除已有回调方法
                webpackInstallModule[chunkName] = undefined;
            }
        };
        // 添加script标签.
        var webpackJsonp = function webpackJsonp_(chunkName, resolve) {
                webpackInstallModule[chunkName] = resolve;
                var head = document.getElementsByTagName('head')[0];
                var script = document.createElement('script');
                script.type = 'text/javascript';
                script.charset = 'utf-8';
                script.async = true;
                script.src = chunkName;
                head.appendChild(script);
            }
            // 获取模块方法。
        var webpack_require = function webpack_require_(chunkName, resolve) {
                if (chunkName) {
                    var Module = webpackJsonpModule[chunkName];
                    if (Module) {
                        resolve(Module);
                    } else {
                        webpackJsonp(chunkName, resolve)
                    }
                }
            }
            // 将webpackJsonpCallback设置到全局上。
        window['webpackJsonpCallback'] = webpackJsonpCallback;
        // 返回webpack_require给requirejs调用。
        return webpack_require;
    });
})(define);
好了有了这个就可以做下面的了。

js/main.js

(function(require, define) {
    require.config({
        paths: {
            'vue': 'https://cdn.bootcss.com/vue/2.0.5/vue',
            'vue-router': 'https://cdn.bootcss.com/vue-router/2.0.1/vue-router',
            'webpackjsonp': '/js/webpackjsonp'
        }
    });

    define(function(require, exports, module) {
        var Vue = require('vue');
        var VueRouter = require('vue-router');
        Vue.use(VueRouter);
        var webpack_require = require('webpackjsonp');
        var router = new VueRouter({
            routes: [{
                    path: '/component1',
                    component: function(resolve) {
                        webpack_require('/js/component1.js', resolve);
                    }
                },
                {
                    path: '/component2',
                    component: function(resolve) {
                        webpack_require('/js/component2.js', resolve);
                    }
                },
            ]
        })
        var app = new Vue({
            el: '#app',
            router: router,
            template: '<div class="content">\
            <h1>Hello Content!</h1>\
            <p>\
             <router-link to="/component1">Go to Component1</router-link>\
             <router-link to="/component2">Go to Component2</router-link>\
            </p>\
            <router-view></router-view>\
            </div>'
        });
    });
})(require, define);
html不变把里面的component改好即可

js/component1.js

(function(define) {
    define('/js/component1.js', function(require, exports, module) {
        return {
            template: '<span>component1</span>'
        }
    });
})(webpackJsonpCallback);
这个改动就比较大了。

js/component2.js

(function(define) {
    define('/js/component2.js', function(require, exports, module) {
        return {
            template: '<h1>component2</h1>'
        }
    });
})(webpackJsonpCallback);
多了一个模块名。。这样效果不是很好希望下回能研究出不需要模块名的。

效果图

vue_router_async_demo

源码

在git仓库的vue_router_async_demo分支。