阿里云 oss 概述
对象存储 OSS(Object Storage Service)是阿里云提供的海量、安全、低成本、高持久的云存储服务。其数据设计持久性不低于 99.9999999999%(12 个 9),服务设计可用性不低于 99.995% 。OSS 具有与平台无关的 RESTful API 接口,您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
为什么要使用阿里云 oss
由于自身提供的服务器容量、速度有限,在数据量过于庞大的时候,自己搭建的服务器无法承受如此大的数据量,导致服务出现问题,因此可以选择把文件存储到 oss 上,每次请求数据通过 cdn 的形式获取。
为什么需要这个插件
为了减少重复工作。在每次 webpack build 时,把 build 的文件上传到 oss 中,并拿到 cdn 链接,并将其添加到生成的 index.html 中,如果每次发布产品的时候,都要进行这些步骤,那么无疑浪费了大把大把的摸鱼时间,加班、秃头接踵而来。将其打包为一个插件、一个自动化模块,每次打包的时候实现自动操作,那无疑能够减少相当多的工作量。
确认操作步骤
- 拿到 OSS 对象
- 删除目标 bucket 中原有文件 (delete function)
- 将 build 文件夹中的 js 文件上传 (put function)
- 获取所有文件的 cdn 路径 (signatureUrl function)
- 加入到 build 文件夹下的 index.html
拿到 OSS 对象
- 阅读阿里云 oss 的官方文档,了解相关 jssdk:github.com/ali-sdk/ali…
- 首先需要安装 ali-oss
yarn add --dev ali-oss
- 根据文档提示,使用 OSS 创建实例对象
const OSS = require('ali-oss');
const client = new OSS({
region: '<oss region>',
accessKeyId: '<Your accessKeyId>',
accessKeySecret: '<Your accessKeySecret>',
bucket: '<Your bucket name>'
});
- 此时就可以根据文档提供的 jssdk 来进行其他相关操作
删除目标 bucket 中原有文件
- 首先需要判断,需要删除哪些文件,
- 使用 node.js 提供的 readdirSync 方法,获取目录下的所有文件和文件夹
this.readDirsAndFiles = fs.readdirSync(this.filesPath)
- 使用 indexOf 判断文件是否是 .js 结尾的 js 文件
this.readJsFiles = this.readDirsAndFiles.filter(dir => dir.indexOf('.js') !== -1 && dir.indexOf('.json') === -1)
- 由于 bucket 中存储的文件是没有 .js 等文件后缀的,因此需要做一个“去尾”工作
this.sliceReadFiles = this.readJsFiles.map(file => file.slice(0, -3))
- 根据文件前缀删除文件
async handleDel(name, options) {
try {
await this.client.delete(name)
} catch (error) {
error.failObjectName = name
return error
}
}
async deletePrefix(prefix) {
const list = await this.client.list({
prefix: prefix
})
list.objects = list.objects || []
await Promise.all(list.objects.map((v) => this.handleDel(v.name)))
}
for (let i = 0; i < this.sliceReadFiles.length; i++) {
this.deletePrefix(this.sliceReadFiles[i])
}
使用 ali-oss 提供的 put() sdk,将 build 文件夹中的 js 文件上传
async uploadFile(name) {
await this.client.put(name, `${this.filesPath}/${name}.js`)
}
uploadFiles() {
for (let i = 0; i < this.readJsFiles.length; i++) {
const index = this.readJsFiles[i].indexOf('.js')
const name = this.readJsFiles[i].slice(0, index)
this.uploadFile(name)
}
}
获取所有文件的 cdn 路径 (signatureUrl function)
async getFileUrl(name) {
return await this.client.signatureUrl(name)
}
async getFilesUrls() {
for (let i = 0; i < this.readJsFiles.length; i++) {
const index = this.readJsFiles[i].indexOf('.js')
const name = this.readJsFiles[i].slice(0, index)
const fileUrl = await this.getFileUrl(name)
this.fileUrlsList.push({name, url: fileUrl})
}
}
加入到 build 文件夹下的 index.html,使用 node.js 的 readFile 和 writeFile 来读写文件
- 由于要获取 html 文件的 script 标签,暂时没有想到好的方法,直接使用字符串的 indexOf 方法来获取到 '<script src' 的位置,并以此为界限,分为 body 和 result 的部分
const index = data.toString().indexOf('<script src')
const body = data.toString().slice(0, index)
- 将通过 signatureUrl 方法获取到的 url 添加到 scriptsArr 数组中
let scriptsArr = []
if(fileUrlsList.length >= 1){
for(let i=0; i < fileUrlsList.length; i ++){
const url = fileUrlsList[i].url.toString()
scriptsArr.push(`<script src='${url}'/></script>`)
}
}
- 由于 slice 的切割,html 文件是缺少 闭合标签的,再次将其添加组合
const postfix = '</body></html>'
const result = body + scriptsArr.join('') + postfix
- 最后将其写入到 index.html 文件中
fs.writeFile(`${this.filesPath}/index.html`, result, (error) => {
if(error) throw error;
console.log('修改成功')
})
将其打包为一个 webpack 的插件
- 使用 class 类的形式,将代码放入 modules.js 中
class MyWebpackPlugin {
constructor({filesPath, region, accessKeyId, accessKeySecret, bucket}) {
this.fileUrlsList = []
this.filesPath = filesPath
this.client = new OSS({
region: region,
accessKeyId: accessKeyId,
accessKeySecret: accessKeySecret,
bucket: bucket,
})
}
async apply(compiler) {
compiler.hooks.afterEmit.tap('AliossWebpackPlugin', async (compilation) => {
//读取 .js 结尾的相关文件
this.readDirsAndFiles = fs.readdirSync(this.filesPath)
this.readJsFiles = this.readDirsAndFiles.filter(dir => dir.indexOf('.js') !== -1 && dir.indexOf('.json') === -1)
this.sliceReadFiles = this.readJsFiles.map(file => file.slice(0, -3))
//删除 oss bucket 的文件
await this.deleteAllPrefix()
// 将源文件夹中的文件上传
setTimeout(async() => {
await this.uploadFiles()
}, 1000)
//获取所有文件的相关路径 url
await this.getFilesUrls()
// 更改路径
await this.buildPath(this.fileUrlsList)
})
}
}
- 在 webpack.config.js 中导入
const AliosscdnWebpackPlugin = require('./module')
module.exports = {
...,
plugins: [
...,
new AliosscdnWebpackPlugin({
filesPath: <filesPath> // packaged path,
region: '<Your oss region>',
accessKeyId: '<Your oss accessKeyId>',
accessKeySecret: '<Your oss accessKeySecret>',
bucket: '<Your oss bucket name>'
})
]
}
- 由于考虑到 webpack.config.js 最终上传代码时也会上传到 github 中,因此需要考虑安全问题,将 oss 的相关信息 region、accessKeyId、accessKeySecret、bucket 属性单独放置到一个文件中,并将其放入 .gitignore 中。
将其发布到 npm 中
- package.json
{
"name": "my-webpack-plugin",
"version": "0.0.1",
...
}
- npm pulish
注意点
- 由于这个 plugin 要实现的是,在 webpack 打包完成之后,再获取到打包后的文件,然后将其上传到 oss 中,获取到每个文件的 cdn,最后将 cdn 放入 index.html 中。因此,根据 webpack 生命周期,需要在其打包完成后,再执行相关操作,也就是
afterEmit中。 - 由于是在 webpack 打包完成后进行的操作,因此最后一步写入操作,不能通过设置 webpack 的 output 来进行写入,会出现获取到的文件夹为空的结果,因为此时打包还未完成,打包的文件夹为空。