前言

  阅读本文前,我们假设你已经至少熟练使用vue-cli进行vue+webpack的项目创建与开发,熟悉Node.js基本命令行指令的操作:

Bowl.js是什么?

  bowl饿了么前端团队开发的一个用 localStorage 来缓存脚本和样式资源的加载器。在获取脚本和样式之后,这个小巧的 JavaScript 库会将它们保存到浏览器的 localStorage 中。当这个文件下次再被请求的时候,bowl 将会从 localStorage 中读取并将它插入到页面中。
  类似basket.js,bowl 也是受到了它的启发。目前和 basket 相比,bowl 还可以不需要额外配置就可以缓存加载 CSS 资源,不支持缓存(如跨域)和不需要缓存的资源也可以使用 bowl 进行普通加载。资源之间存在相互依赖关系的可以通过声明依赖关系来让 bowl 进行分析并按照依赖顺序加载,并不需要类似 require.js 之类的解决方案(实际上 require.js 也要在模块内部声明依赖关系的)。

创建一个项目

  为了方便演示,我们使用vue-cli新建一个项目:

1
2
3
$ vue init webpack test
$ cd test/
$ yarn install # 或者使用 npm install

  构建vue项目

1
$ yarn run build # 或者使用 npm run build

  构建完毕后,我们为了测试服务的效果,安装一个静态服务 node-static

1
2
$ yarn add node-static -D # 或者使用 npm install node-static -D
$ touch server.js

  简单创建一个静态服务器

1
2
3
4
5
6
7
8
// server.js
var static = require('node-static');
var file = new static.Server('./dist');
require('http').createServer(function (request, response) {
request.addListener('end', function () {
file.serve(request, response);
}).resume();
}).listen(3000);

  使用Nodejs启动

1
$ node server.js

  打开 http://localhost:3000 不出意外,你应该能看到vue.js的初始模版。打开项目的/dist/index.html, 可见源码类似如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>test</title>
<link href=/static/css/app.558a0abfebd23588b34381e01fe275cc.css rel=stylesheet>
</head>
<body>
<div id=app></div>
<script type=text/javascript src=/static/js/manifest.75c28d54435a0e9567b7.js></script>
<script type=text/javascript src=/static/js/vendor.b02463ae6f65cfb3a3f3.js></script>
<script type=text/javascript src=/static/js/app.067051308e1a99fd2d7c.js></script>
</body>
</html>

  观察源码,可以看到Webpack在构建的时候,会构建一个css文件:app.558a0abfebd23588b34381e01fe275cc.css,三个js文件:manifest.75c28d54435a0e9567b7.js, vendor.b02463ae6f65cfb3a3f3.js, app.067051308e1a99fd2d7c.js,当然文件的hash是不同的。

使用bowl进行改造测试

  综上,我们使用bowl缓存这四个文件到浏览器的localStorage中,以便在生产环境快速加载应用。修改/dist/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<head>
<meta charset=utf-8>
<title>test</title>
<!-- 引入bowl.js -->
<script src="//unpkg.com/bowl.js/lib/bowl.min.js"></script>
</head>
<body>
<div id=app></div>
<script>
var bowl = new Bowl();
bowl.add([
{ url: "/static/css/app.558a0abfebd23588b34381e01fe275cc.css", key: "style" },
{ url: "/static/js/manifest.75c28d54435a0e9567b7.js", key: "manifest" },
{ url: "/static/js/vendor.b02463ae6f65cfb3a3f3.js", key: "vendor", dependencies: ["manifest"] }, // 注意必须注明 dependencies 实现顺序加载
{ url: "/static/js/app.067051308e1a99fd2d7c.js", key: "app", dependencies: ["manifest", "vendor"] }
]);
bowl.inject();
</script>
</body>
</html>

  回到浏览器刷新,可以发现这四个文件均被使用xhr异步请求进行加载了


  打开localStorage管理器,发现文件已被缓存

  继续刷新,发现这四个文件不会再被请求,完全由Bowl.js进行本地加载。对于使用Vue.js全家桶套件来说:vuex vue-router 加上 axios 和其他一些UI组件 最后构建之后 这四个文件至少800KB,由于Webpack打包会生成hash文件名,并且Bowl.js会根据 keyurl 进行匹配更新,所以非常适合使用该方式来进行优化缓存。

集成到vue-cli的模板

  为了省去手动改造/dist/index.html文件的麻烦,我们打算直接集成到vue-cli生成的模版中,实现使用 $ yarn run build 命令后,自动为我们改造成bowl版。为了不影响 $ yarn run dev 的开发环境配置,我们在项目根目录新建一个index.prod.html

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
<html>
<head>
<meta charset=utf-8>
<title>test</title>
<!-- 引入bowl.js -->
<script src="//unpkg.com/bowl.js/lib/bowl.min.js"></script>
</head>
<body>
<div id=app>
<p>加载中...</p>
</div>
<script>
<%
var resources = [];
var keys = ["manifest", "vendor", "app"];
var dependencies = [0, [keys[0]],
[keys[0], keys[1]]
];
// css
for (var css in htmlWebpackPlugin.files.css) {
resources.push({
url: htmlWebpackPlugin.files.css[css],
key: "style"
});
}
// js
var i = -1;
for (var chunk in htmlWebpackPlugin.files.chunks) {
i++;
resources.push({
url: htmlWebpackPlugin.files.chunks[chunk].entry,
key: keys[i],
dependencies: dependencies[i] || []
});
}
%>
/**
* 使用Bowl进行缓存
*/
var b = new Bowl();
b.add(<%=JSON.stringify(resources) %>);
b.inject();
</script>
</body>
</html>

  接着打开build/webpack.prod.conf.js,大约在43行的位置(在Webpack的plugins属性下)。把

1
2
3
4
5
6
7
8
9
10
11
12
13
14
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),

修改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.prod.html', // 使用生成模式的模板
inject: false, // 不要自动插入script标签
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
minifyJS: true // 内容压缩
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),

  再次构建项目 $ yarn run build 可以发现 /dist/index.html已经自动加入Bowl.js相关逻辑代码。

参考链接

bowl.js官网
localStorage的黑科技-js和css缓存机制
Vue.js中文官网
Webpack中文官网