这篇博客的起源来源于同事的一次cos部署指导,为了便于下次使用,特记录于此.
整体一知半解版本:
1. 首先打包好的文件夹下会包含一个 index.html
文件和 static
文件夹,这个 static
文件夹就会包含我们需要的静态资源,使用者打开网页的时候,index.html会去找同目录下的static内的资源,而这个过程是很慢的,index.html文件很小还没有什么感觉,但要是加载打包好的静态资源速度就会变慢。
2. 这个时候就轮到我们的主角cos登场了,当我们将static文件夹发送到cos内,相当于使用者就近从服务器拿取静态资源,速度无疑快很多。
3. 所以整体步骤为:
1. 更改打包index.html内的引用位置为cos路径
2. 将static文件夹发送至cos服务器路径下
3. 将index.html发送至后台服务器路径下
对象存储(Cloud Object Storage,简称:COS)是腾讯云提供的面向非结构化数据,支持 HTTP/HTTPS 协议访问的分布式存储服务,它能容纳海量数据并保证用户对带宽和容量扩充无感知,可以作为大数据计算与分析的数据池。1
简而言之,cos就是类似天猫超市方式,在离我们最近的地方构建仓库,把我们需要的资源就近调拨给我们;之前的上传服务器方式,类似淘宝普通商家,从商家那里发送资源给我们,这样就会造成住的远的人要很久才能取到资源。
因为我是基于vue部署于腾讯云cos,参考Node.js SDK
同事推荐使用的官方工具COSBrowser,下载操作系统对应的安装包就好。
下载好后安装就好,但是登陆的时候需要的是之前获取的SecretId、SecretKey作为登录凭据。登录就可以看到cos根路径下文件夹了。
scp2
npm i scp2 -g
or cnpm i scp2 --save
build/build.js
内// 引用
const client = require('scp2') // 自动打包部署插件
// 实际使用
/** 自动部署 */
client.scp('dist/', {
port: ##, // 端口号,纯数字
host: '###.###.###.###', // 字符串,IP
username: '#########', // 登陆服务器用户名,找负责后台服务的大腿或者运维大兄弟要去吧,记得要打开读写权限
password: '**********', // 登陆服务器密码,同上
path: 'home/www' // 这个是要放在服务器上的路径
}, function (err) {
if (err) {
console.log(chalk.red(' Send failed with errors.\n' + err))
} else {
console.log(chalk.cyan(' Send files to server success.\n'));
}
})
这里我使用了sftp协议,所以是22端口。所以需要配置端口号、主机IP、用户名、密码、存放路径。缺陷:这个插件有时会出现打包未完成,即开始向cos传文件最终导致向cos上传文件失败,在电脑性能不行的时候体现尤为明显,故不推荐使用
腾讯云cos帮助插件
安装
npm i cos-nodejs-sdk-v5 --save
or cnpm i cos-nodejs-sdk-v5 --save
使用演示,以我的代码为例:
index.html
是纯正则表达式,不要单引号。region
和之前所属地域对应,具体详询官方文档// 引用 腾讯云cos
const CosPlugin = require('cos-webpack') // 上传腾讯云cos插件
// 这里直接写在参数webpackConfig内好像无法判断,
// 所以写在了后面,
// 使用push添加
webpackConfig.plugins.push(
new CosPlugin({ // 配置 Plugin
secretId: '****************',
secretKey: '***************',
bucket: '******************', // COS 存储对象名称,格式为对象名称加应用ID (APPID),如: bucket-1250000000
region: 'ap-beijing', // COS 存储地域
exclude: /index.html$/, // 可选,排除特定文件,正则表达式,如: /index.html$/
path: 'static/pro/' // 存储路径, 默认为 [hash]
})
)
只简单介绍一下我的修(zhe)改(teng)之旅。
相关要修改文件目录:
project
├── package.json
├── build
│ ├── cos.js
│ ├── secure-file-copy.js
│ └── webpack.base.conf.js
└── config
├── index.js
└── prod.env.js
整体修改思路:
index.html
文件的静态引用资源路径为cos上的资源路径,这里需要修改 webpack.base.conf.js
secure-file-copy.js
文件将 index.html
推送至后台服务器cos.js
文件将 static
文件夹推送至后台服务器{
"scripts": {
"deploy:pre": "cross-env COS_ENV=pre npm run build && cross-env COS_ENV=pre node build/cos.js && cross-env COS_ENV=pre node build/secure-file-copy.js",
"deploy:prod": "cross-env COS_ENV=prod npm run build && cross-env COS_ENV=prod node build/cos.js && cross-env COS_ENV=prod node build/secure-file-copy.js "
}
}
set
, set ENV=pre
设定环境变量,用于后边的打包区分不同的环境...pre
和 &&
之间没有空格,是为了避免之后的识别出现空格的问题export
, export ENV=pre
设定为了跨平台,所以我毅然决然的使用了 cross-env
包,但是有个问题就是我没有办法定义贯穿三个脚本的共享变量,所以如你所见,我写了三次环境变量,望有知道怎么整的大神不吝赐教,拜谢
先修改静态资源引用路径为cos路径:
const config = require('../config'); // 这个文件的修改后面说
/**
* 根据环境变量,确定静态资源引用路径
* @param {String} process.env.COS_ENV
* - pre 预览版
* - prod 正式版
* @author simorel
*/
if (process.env.hasOwnProperty('COS_ENV') && process.env.COS_ENV) {
webpackConfig.output.publicPath =
process.env.COS_ENV === 'pre'
? config.pre.assetsPublicPath
: config.prod.assetsPublicPath
}
这里使用 scp2
插件:
'use strict'
/**
* 将指定文件发送至服务器
* @author simorel
*/
const client = require('scp2') // 文本发送插件
const chalk = require('chalk');
const config = require('../config');
const FILE_PATH = 'dist/index.html'; // 文件路径,仅发送index.html作为页面入口,其他所有静态资源都放在cos上
if (process.env.hasOwnProperty('COS_ENV') && process.env.COS_ENV) {
const scpConfig =
process.env.COS_ENV === 'pre'
? config.pre.server
: config.prod.server;
client.scp(FILE_PATH, {
port: 22,
host: scpConfig.host,
username: scpConfig.username,
password: scpConfig.password,
path: scpConfig.path
}, function (err) {
if (err) {
console.log(chalk.red(' Send failed with errors.\n' + err))
} else {
console.log(chalk.cyan(' Send files to server success.\n'));
}
})
}
重点说说使用的插件吧:
'use strict';
const fs = require('fs'); // node的文件插件
const ora = require('ora'); // 日志打印归于一行插件
const chalk = require('chalk'); // 日志美化插件
const COS = require('cos-nodejs-sdk-v5'); // 腾讯cos插件
const glob = require('glob'); // 遍历文件夹插件
const config = require('../config'); // 配置文件
const FILES_PATH = 'dist/**/*.*'; // 打包后静态资源路径
const IGNORE_PATH = '*/index.html'; // 忽略index.html文件
/**
* 发送文件至cos类
* @class DeployCos
*/
class DeployCos {
constructor() {
this.filesPath = FILES_PATH; // 打包后的静态资源路径
this.cos = this._createCosInstance();
this.cosConfig = this._getCosConfig();
this.spinner = this._createSpinner();
this.total = 0; // 总文件数
this.main();
}
/**
* 获取文件路径,并上传至cos
* @memberof DeployCos
*/
main() {
glob(
this.filesPath,
{
ignore: [IGNORE_PATH]
},
(err, fileNames) => {
this.total = fileNames.length;
let promiseArr = fileNames.map((file, index) => {
return this._uploadFile(file, index + 1); // 因为index从零开始,所以这里加一
});
Promise.all(promiseArr)
.then(res => {
this.spinner.succeed(); // 结束上传进度条打印
console.log(
chalk.cyan('\n Send files to tencent cos success.\n')
);
})
.catch(err => {
console.log(
chalk.red(' Send failed with errors.\n' + err)
);
});
}
);
}
/**
* 创建cos实例
*/
_createCosInstance() {
return new COS({
SecretId: config.cos.secretId,
SecretKey: config.cos.secretKey
});
}
/**
* 创建进度条
*/
_createSpinner() {
return ora({
text: this._getTip(0, 0),
color: 'green'
}).start();
}
/**
* 根据环境变量获取cos配置
* @memberof DeployCos
*/
_getCosConfig() {
return process.env.COS_ENV === 'pre' ? config.pre.cos : config.prod.cos;
}
/**
* 展示进度提示条
* @param {number} index 当前文件索引
* @param {number} sum 总文件数
* @memberof DeployCos
*/
_getTip(index, sum) {
let percentage = sum === 0 ? 0 : Math.round((index / sum) * 100);
return `Uploading to Tencent COS: ${
percentage === 0 ? '' : percentage + '% '
}${index}/${sum} files uploaded`;
}
/**
* 实际调用cos接口上传文件函数
* @param {String} file 文件相对路径
* @param {number} index 文件计数器
* @memberof DeployCos
*/
_uploadFile(file, index) {
return new Promise((resolve, reject) => {
/**
* 因为不希望cos上的路径多一层dist,所以这里移除
* - 不移除,形如: [cos路径]/[dist/static/xx]
* - 移除后,形如: [cos路径]/[static/xx]
*/
let removeDistFilePath = file.replace('dist/', '');
/** 上传文件基础配置 */
let baseConfig = {
Bucket: config.cos.bucket, // 存储桶 格式:test-1250000000
Region: config.cos.region, // 地域
Key: `${this.cosConfig.path}${removeDistFilePath}` // [cos存储路径]/[文件相对存储路径]
};
// 根据文件大小判断使用何种方式上传,大于1M的使用分块上传
if (fs.statSync(file).size > 1024 * 1024) {
this.cos.sliceUploadFile(
{
...baseConfig,
FilePath: file
},
(err, data) => {
this.spinner.text = this._getTip(index, this.total);
return err ? reject(err) : resolve(data);
}
);
} else {
this.cos.putObject(
{
...baseConfig,
Body: fs.createReadStream(file),
ContentLength: fs.statSync(file).size // putObject接口必须传文件大小
},
(err, data) => {
this.spinner.text = this._getTip(index, this.total);
return err ? reject(err) : resolve(data);
}
);
}
});
}
}
new DeployCos(); // 执行推送
/**
* 预览版(静态资源推送至cos)
* @author simorel
*/
pre: {
/** 静态资源引用路径 */
assetsPublicPath: '####',
/** 服务器 */
server: {
path: '####',
host: '####',
username: '####',
password: '####'
},
/** cos */
cos: {
path: '####'
}
},
/**
* 正式版(静态资源推送至cos)
* @author simorel
*/
prod: {
/** 静态资源引用路径,cos上的路径 */
assetsPublicPath: 'http(s)://###',
/** 服务器 */
server: {
path: '###',
host: '###',
username: '###',
password: '###'
},
/** cos */
cos: {
path: '###/###/###/'
}
},
/**
* 腾讯云cos基础配置
*/
cos: {
secretId: '#########',
secretKey: '#########',
bucket: '#########', // COS 存储对象名称,格式为对象名称加应用 ID,如: bucket-1250000000
region: '#########' // COS 存储地域
}
'use strict';
const prodConfig = {
NODE_ENV: '"production"'
};
/**
* @author simorel
*/
if (process.env.hasOwnProperty('COS_ENV') && process.env.COS_ENV) {
/**
* 注意(https://www.webpackjs.com/plugins/define-plugin/):
* 因为这个插件直接执行文本替换,给定的值必须包含字符串本身内的实际引号。
* 通常,有两种方式来达到这个效果,使用 '"production"', 或者使用 JSON.stringify('production')。
*/
prodConfig.COS_ENV = JSON.stringify(process.env.COS_ENV);
}
module.exports = prodConfig;
cos命名参考 ↩︎