静态资源上传cdm

59 阅读3分钟

起因

因公司移动端项目想要提升访问速度,领导命令将打包后的资源放在阿里云服务器上,那牛马就只能干活了

思考方案

如果只是上传文件至阿里云其实很简单,官方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)]
})