起因
因公司移动端项目想要提升访问速度,领导命令将打包后的资源放在阿里云服务器上,那牛马就只能干活了
思考方案
如果只是上传文件至阿里云其实很简单,官方api调用一下即可。
但是现在都是工程化,最好是跟着打包命令一起,一键完成,省心省力。并且最好能复用,没办法,开发就是喜欢搞组件复用。。。
实施阶段
1、新建个项目,封装上传流程
忽略文件应该不用解释,重点就是index.js封装文件,具体代码如下
项目以上传到npm源,有兴趣可以查看www.npmjs.com/package/vit…
const color = require('picocolors')
const { globSync } = require('glob')
const path = require('path')
const OSS = require('ali-oss')
const { URL } = require('url')
const { normalizePath } = require('vite')
const { execSync } = require('child_process')
module.exports = function vitePluginOssCdn(options) {
let baseConfig = '/', buildConfig = {}, env = {}, GIT_INFO = {}
if (options.enabled !== void 0 && !options.enabled) {
return
}
return {
name: 'vite-plugin-oss-cdn',
enforce: 'post',
apply: 'build',
configResolved(config) {
baseConfig = config.base
buildConfig = config.build
env = config.env
try {
GIT_INFO = {
SHA: execSync('git rev-parse --short HEAD').toString().trim(), // SHA
COMMIT_DATE: execSync('git log -1 --format=%cI').toString().trim(), // 提交时间
HASH: execSync('git rev-parse HEAD').toString().trim(), //HASH
LAST_COMMIT_MESSAGE: execSync('git show -s --format=%s').toString().trim(), //最后提交 message
BRANCH_LOCAL: execSync('git branch').toString().trim(), //本地分支
BRANCH: execSync('git rev-parse --abbrev-ref HEAD').toString().trim() // 当前分支
}
} catch (e) {
console.log('error------>>>>>>', e)
}
},
closeBundle: {
sequential: true,
order: 'post',
async handler() {
if (!/^http/i.test(baseConfig)) {
throw Error('[vite-plugin-ali-oss] base must be a url')
}
const outDirPath = normalizePath(path.resolve(normalizePath(buildConfig.outDir)))
const { pathname: ossBasePath, origin: ossOrigin } = new URL(baseConfig)
const createOssOption = Object.assign({}, options)
delete createOssOption.overwrite
delete createOssOption.ignore
delete createOssOption.headers
delete createOssOption.test
delete createOssOption.enabled
const client = new OSS(createOssOption)
const ssrClient = buildConfig.ssrManifest
const ssrServer = buildConfig.ssr
let { rmdir: prefix, rmdirCount = 1, mode, branch } = createOssOption
let reg = /https://fst1.fuxiaoli.com//g
let isFxl = reg.test(baseConfig)
// 检测当前环境下分支是否需要上传,防止本地build后覆盖线上产生重大问题
let envList = ['production'], branchList = [], { BRANCH } = GIT_INFO
Array.isArray(mode) && (envList = [...envList, ...mode])
Array.isArray(branch) && (branchList = [...branchList, ...branch])
if (isFxl) {
envList = ['production', 'uat1', 'test']
branchList = ['HEAD']
}
console.log('envList------>>>>>>', envList)
console.log('BRANCH-branchList------>>>>>>', BRANCH, branchList)
if (envList.includes(env.MODE)) {
if (isFxl && branchList.includes(BRANCH)){
await delDir()
return upload()
}
if (!isFxl) {
if (branchList.length) {
if (branchList.includes(BRANCH)) {
await delDir()
return upload()
}
return
}
await delDir()
return upload()
}
}
// 删除对应文件
async function delDir() {
// 如果您需要删除所有前缀为src的文件,则prefix设置为src。设置为src后,所有前缀为src的非目录文件、src目录以及目录下的所有文件均会被删除。
// 如果您仅需要删除src目录及目录下的所有文件,则prefix设置为src/。
if (!prefix && isFxl) {
prefix = baseConfig.replace(reg, '')
}
if (prefix) {
for (let i = 0; i < rmdirCount; i++) {
await deletePrefix(prefix)
}
// 删除多个文件。
async function deletePrefix(prefix) {
const list = await client.list({
prefix: prefix,
'max-keys': 1000
})
list.objects = list.objects || []
const result = await Promise.all(list.objects.map((v) => handleDel(v.name)))
}
// 处理请求失败的情况,防止promise.all中断,并返回失败原因和失败文件名。
async function handleDel(name) {
try {
await client.delete(name)
} catch (error) {
error.failObjectName = name
return error
}
}
}
}
// 上传方法
async function upload() {
const files = globSync(outDirPath + '/**/*', {
nodir: true,
dot: true,
ignore:
// custom ignore
options.ignore !== undefined
? options.ignore
: // ssr client ignore
ssrClient
? ['**/ssr-manifest.json', '**/*.html']
: // ssr server ignore
ssrServer
? ['**']
: // default ignore
'**/*.html'
})
console.log(
'ali oss upload start' + (ssrClient ? ' (ssr client)' : ssrServer ? ' (ssr server)' : '')
)
const startTime = new Date().getTime()
for (const fileFullPath of files) {
const filePath = normalizePath(fileFullPath).split(outDirPath)[1] // eg: '/assets/vendor.bfb92b77.js'
const ossFilePath = ossBasePath.replace(//$/, '') + filePath // eg: '/base/assets/vendor.bfb92b77.js'
const completePath = ossOrigin + ossFilePath // eg: 'https://foo.com/base/assets/vendor.bfb92b77.js'
const output = `${buildConfig.outDir + filePath} => ${color.green(completePath)}`
if (options.test) {
console.log(`test upload path: ${output}`)
continue
}
if (options.overwrite) {
await client.put(ossFilePath, fileFullPath, {
headers: options.headers || {}
})
console.log(`upload complete: ${output}`)
} else {
try {
await client.head(ossFilePath)
console.log(`${color.gray('files exists')}: ${output}`)
} catch (error) {
if (error.code === 'NoSuchKey') {
await client.put(ossFilePath, fileFullPath, {
headers: Object.assign(options.headers || {}, { 'x-oss-forbid-overwrite': true })
})
console.log(`upload complete: ${output}`)
} else {
throw new Error(error)
}
}
}
}
const duration = (new Date().getTime() - startTime) / 1000
console.log(`ali oss upload complete ^_^, cost ${duration.toFixed(2)}s`)
}
}
}
}
}
2、工程项目中下载vite-plugin-oss-cdn插件,并在配置中导入即可
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vitePluginOssCdn from 'vite-plugin-oss-cdn'
const options = {
region: '<Your Region>'
accessKeyId: '<Your Access Key ID>',
accessKeySecret: '<Your Access Key Secret>',
bucket: '<Your Bucket>'
}
// https://vitejs.dev/config/
export default defineConfig({
base: 'https://foo.com/', // 必须是 URL
plugins: [vue(), vitePluginOssCdn(options)]
})