ZEROMAKE | keep codeing and thinking!
2016-11-07 | vue

vue 不使用构建工具懒加载

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
<!DOCTYPE html>
2
<html lang="zh">
3
<head>
4
<meta charset="UTF-8" />
5
<meta name="viewport" content="width=device-width, initial-scale=1" />
6
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
7
<title>Title</title>
8
<script
9
type="text/javascript"
10
data-main="js/main.js"
11
src="./bower_components/requirejs/require.js"
12
></script>
13
</head>
14
<body>
15
<div id="app"></div>
16
</body>
17
</html>

js/main.js

1
(function(require, define) {
2
require.config({
3
paths: {
4
vue: "https://cdn.bootcss.com/vue/2.0.5/vue",
5
"vue-router": "https://cdn.bootcss.com/vue-router/2.0.1/vue-router"
6
}
7
});
8
9
define(function(require, exports, module) {
10
var Vue = require("vue");
11
var VueRouter = require("vue-router");
12
Vue.use(VueRouter);
13
var router = new VueRouter({
14
routes: [
15
{
16
path: "/component1",
17
component: function(resolve) {
18
resolve(require("js/component1.js"));
19
}
20
},
21
{
22
path: "/component2",
23
component: function(resolve) {
24
resolve(require("js/component2.js"));
25
}
26
}
27
]
28
});
29
var app = new Vue({
30
el: "#app",
31
router: router,
32
template: '<div class="content">\
33
<h1>Hello Content!</h1>\
34
<p>\
35
<router-link to="/component1">Go to Component1</router-link>\
36
<router-link to="/component2">Go to Component2</router-link>\
37
</p>\
38
<router-view></router-view>\
39
</div>'
40
});
41
});
42
})(require, define);

js/component1.js

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

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

js/component2.js

1
(function(define) {
2
define(function(require, exports, module) {
3
return {
4
template: "<h1>component2</h1>"
5
};
6
});
7
})(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
var head = document.getElementsByTagName("head")[0];
2
var script = document.createElement("script");
3
script.type = "text/javascript";
4
script.charset = "utf-8";
5
script.async = true;
6
script.src = ""; // del;
7
head.appendChild(script);

这下就明白了__webpack_require__(chunkIds, callback)调用后查找是否已有chunkIds的模块有的话通过callback调用返回。
没有就通过chunkIds找到文件并在head中添加一个script加载新 js 然后 js 中调用webpackJsonp并通过callback返回模块。

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

js/webpackjsonp.js

1
(function(define) {
2
define(function() {
3
// 模块缓存区。
4
var webpackJsonpModule = {};
5
// 回调方法缓存区。
6
var webpackInstallModule = {};
7
// 引入的js所需调用的方法。
8
var webpackJsonpCallback = function webpackJsonpCallback_(
9
chunkName,
10
Module
11
) {
12
// 判断模块名
13
if (webpackJsonpModule[chunkName]) {
14
console.warn(
15
"模块名: " +
16
chunkName +
17
"已存在!为其旧模块重命名到:" +
18
chunkName +
19
"_"
20
);
21
webpackJsonpModule[chunkName + "_"] =
22
webpackJsonpModule[chunkName];
23
}
24
// 调用模块的方法获得模块并设置到模块缓存区。
25
webpackJsonpModule[chunkName] = Module();
26
// 取出回调方法
27
resolve = webpackInstallModule[chunkName];
28
if (resolve) {
29
// 调用回调方法
30
resolve(webpackJsonpModule[chunkName]);
31
// 删除已有回调方法
32
webpackInstallModule[chunkName] = undefined;
33
}
34
};
35
// 添加script标签.
36
var webpackJsonp = function webpackJsonp_(chunkName, resolve) {
37
webpackInstallModule[chunkName] = resolve;
38
var head = document.getElementsByTagName("head")[0];
39
var script = document.createElement("script");
40
script.type = "text/javascript";
41
script.charset = "utf-8";
42
script.async = true;
43
script.src = chunkName;
44
head.appendChild(script);
45
};
46
// 获取模块方法。
47
var webpack_require = function webpack_require_(chunkName, resolve) {
48
if (chunkName) {
49
var Module = webpackJsonpModule[chunkName];
50
if (Module) {
51
resolve(Module);
52
} else {
53
webpackJsonp(chunkName, resolve);
54
}
55
}
56
};
57
// 将webpackJsonpCallback设置到全局上。
58
window["webpackJsonpCallback"] = webpackJsonpCallback;
59
// 返回webpack_require给requirejs调用。
60
return webpack_require;
61
});
62
})(define);

好了有了这个就可以做下面的了。

js/main.js

1
(function(require, define) {
2
require.config({
3
paths: {
4
vue: "https://cdn.bootcss.com/vue/2.0.5/vue",
5
"vue-router": "https://cdn.bootcss.com/vue-router/2.0.1/vue-router",
6
webpackjsonp: "/js/webpackjsonp"
7
}
8
});
9
10
define(function(require, exports, module) {
11
var Vue = require("vue");
12
var VueRouter = require("vue-router");
13
Vue.use(VueRouter);
14
var webpack_require = require("webpackjsonp");
15
var router = new VueRouter({
16
routes: [
17
{
18
path: "/component1",
19
component: function(resolve) {
20
webpack_require("/js/component1.js", resolve);
21
}
22
},
23
{
24
path: "/component2",
25
component: function(resolve) {
26
webpack_require("/js/component2.js", resolve);
27
}
28
}
29
]
30
});
31
var app = new Vue({
32
el: "#app",
33
router: router,
34
template: '<div class="content">\
35
<h1>Hello Content!</h1>\
36
<p>\
37
<router-link to="/component1">Go to Component1</router-link>\
38
<router-link to="/component2">Go to Component2</router-link>\
39
</p>\
40
<router-view></router-view>\
41
</div>'
42
});
43
});
44
})(require, define);

html 不变把里面的 component 改好即可

js/component1.js

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

这个改动就比较大了。

js/component2.js

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

多了一个模块名。。这样效果不是很好希望下回能研究出不需要模块名的。

效果图

vue_router_async_demo

源码

在 git 仓库的 vue_router_async_demo 分支。