vue 不使用 webpack,vue_router 怎么异步

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

零、一些注意事项

  1. 源码在这里

一、vue_router_demo

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

vue_router_demo.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<!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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
(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

1
2
3
4
5
6
7
(function(define) {
    define(function(require, exports, module) {
        return {
            template: "<span>component1</span>"
        };
    });
})(define);

我看了 webpack 的.vue 编译完后导出的对象也不过是有特定属性的对象,必须有template或者render其中一个。

js/component2.js

1
2
3
4
5
6
7
(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__中有这样一段代码:

1
2
3
4
5
6
7
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
(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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
(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

1
2
3
4
5
6
7
(function(define) {
    define("/js/component1.js", function(require, exports, module) {
        return {
            template: "<span>component1</span>"
        };
    });
})(webpackJsonpCallback);

这个改动就比较大了。

js/component2.js

1
2
3
4
5
6
7
(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 分支。