前言
随着前端三大框架的盛行,越来越多的前后端分离项目在服务器上跑了起来,随之而来,开发者也慢慢发现了这种开发模式所带来的弊端,其中之一就是首屏加载速度特别慢,因为虽然虚拟DOM 在更新视图方便比传统方式更加出色,但是首次加载静态资源时,我们的浏览器就需要花费更久的时间来处理这些Js了。
起因
从建立创建虚拟 dom 到render成真实的dom结构,首屏渲染肯定比传统直接返回 html方式肯定要慢一些。为此,加快首屏渲染速度,社区也推出了相应的服务端渲染方案,如基于 vue 的nuxt 和基于 react 的 next.js,并且有了一定的成效,可以说是开发层面目前能带来改善首屏加载的最佳方案了。
抛开开发层面,为了解决这个首屏渲染与应用加载问题,为什么我们不让内存和带宽都很吃紧的服务器解放出来,把我们的静态资源丢在 OSS上呢?因为普通的一个前端项目少则几兆,多则几十兆的静态资源,如果全部交给服务器来处理,先不说拉取时间长,花费的带宽多,成本上先不提,说不定还影响了服务器其他更重要的核心业务的运行,所以今天我就来说一下这个解决方案的具体步骤吧。
什么是 OSS?
首先有必要介绍一下 OSS,阿里云对象存储是这样介绍的
OSS,对象存储。海量、安全、低成本、高可靠的云存储服务,提供99.9999999999%的数据可靠性。使用RESTful API 可以在互联网任何位置存储和访问,容量和处理能力弹性扩展,多种存储类型供选择全面优化存储成本。
意思就是提供一个类似云盘的东西给你,而且访问的速度很快,你可以在里面储存你想要的任意文件和资源,同时提供了一套 API 给你,可以在项目的代码里面与这个云盘做交互。
目前市场上比较知名我大概知道这几家
- 七牛云
- 腾讯云
- 阿里云 该选谁呢?他们价格不一致,提供的免费额度也不一致,具体选择什么,还要看自己项目的需要,我这里选择七牛云讲解。 为什么选择七牛云? 因为免费>..< 。 10G免费空间,10G免费流量,对于搭建自己的博客网站已经绰绰有余了。
七牛云对象存储
本章内容是将如何使用对象存储的API在前端项目如何使用,至于如何开通七牛云对象存储这些基础内容不在本章介绍范围内,如有需要去此链接查看:一个非常详细的开通七牛云 oss 教程的传送门
开通好了,我们就拿到了一些关键的信息,如AccessKey、SecretKey、存储空间名、访问地址、存储区域。 有了这些,我们再来看一下七牛云对象存储的提供Node.js版本的开发接口。

我们这里主要是要用到文件上传功能,因此着重介绍上传文件的 API. 看一个最简单粗暴的用例,上传本地文件到远程,以表单方式
var localFile = "/Users/jemy/Documents/qiniu.mp4"; // 需要上传的路径
var formUploader = new qiniu.form_up.FormUploader(config); //config 是配置参数,后面会给示例
var putExtra = new qiniu.form_up.PutExtra();
var key='test.mp4'; // 上传后在 oss 中的文件名
// 文件上传
formUploader.putFile(uploadToken, key, localFile, putExtra, function(respErr,
respBody, respInfo) {
if (respErr) {
throw respErr;
}
if (respInfo.statusCode == 200) {
console.log(respBody);
} else {
console.log(respInfo.statusCode);
console.log(respBody);
}
});
因此我们只要逐步遍历我们的需要上传的文件夹就好了。 直接给用例,看似很长,其实没多少逻辑,就是遍历需要上传的文件夹。 以一个 Nuxt 项目为例,需要上传的是客户端的静态资源,本地路径为.nuxt/dist/client。 文件结构如下:

const qiniu = require('qiniu');//需要安装我们的七牛云 nodejs 的sdk
const fs = require('fs'); // 操作文件
const path = require('path'); // 读取路径
// 定义工作目录
const PUBLIC_PATH = path.join(__dirname, '/');
// 开始配置用户凭证
var accessKey = 'your accessKey';
var secretKey = 'your secretKey';
var mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
var options = {
scope: 'paintingstudio', //对象存储名
};
var putPolicy = new qiniu.rs.PutPolicy(options);
var uploadToken = putPolicy.uploadToken(mac);
var config = new qiniu.conf.Config();
// 空间对应的机房
config.zone = qiniu.zone.Zone_z2;
// 是否使用https域名
config.useHttpsDomain = true;
// 上传是否使用cdn加速
config.useCdnDomain = true;
/**
* 遍历文件夹递归上传
* @param {path} src 本地路径
* @param {string} dist oos文件夹名
* @param {boolean} isDir 是否为文件夹下文件
*/
async function addFileToOSSSync(src, dist, isDir) {
let docs = fs.readdirSync(src);
docs.forEach(function (doc) {
let _src = src + '/' + doc,
_dist = dist + '/' + doc;
let st = fs.statSync(_src);
// 判断是否为文件
if (st.isFile() && dist !== 'LICENSES`') {
putOSS(_src, !isDir ? doc : `img/${doc}`); //如果是文件夹下文件,文件名为 img/文件名
}
// 如果是目录则递归调用自身
else if (st.isDirectory()) {
addFileToOSSSync(_src, _dist, true);
}
});
}
/**
*单个文件上传至oss
*/
async function putOSS(src, dist) {
try {
var localFile = src;
var formUploader = new qiniu.form_up.FormUploader(config);
var putExtra = new qiniu.form_up.PutExtra();
var key = dist;
// 文件上传
await formUploader.putFile(uploadToken, key, localFile, putExtra, function (
respErr,
respBody,
respInfo
) {
if (respErr) {
throw respErr;
}
if (respInfo.statusCode === 200) {
console.log(key + '上传oss成功');
} else {
console.log(respInfo.statusCode);
console.log(respBody);
}
});
} catch (e) {
console.log('上传失败'.e);
}
}
/**
*上传文件启动
*/
async function upFile() {
try {
let src = PUBLIC_PATH + '.nuxt/dist/client'; //需要上传的路径
let docs = fs.readdirSync(src);
await addFileToOSSSync(src, docs);
} catch (err) {
console.log('上传oss失败', err);
}
}
upFile(); //开始上传
使用 node 命令或者在每次build 之后上传都可,看项目需要。

我这里选择每次 build 之后上传。 对了,别忘了将你的打包资源路径换成你的 OSS的域名。


通过一个Node 脚本,就解放了我们服务器,让它去做更需要它的事情去吧。