UNPKG

@stellaris/vite-plugin-tencent-oss

Version:

Upload the production files bundled in the project to Tencent CSS, except for html

252 lines (221 loc) 7.77 kB
import color from "picocolors"; import { glob } from "glob"; import { minimatch } from "minimatch"; import path from "path"; import fs from "fs"; import { URL } from "url"; import COS from "cos-nodejs-sdk-v5"; import { normalizePath } from "vite"; import { to } from "await-to-js"; export default function vitePluginTencentOss(options) { let baseConfig = "/"; let buildConfig = ""; if (options.enabled !== void 0 && !options.enabled) { return; } return { name: "vite-plugin-tencent-oss", enforce: "post", apply: "build", configResolved(config) { baseConfig = config.base; buildConfig = config.build; }, async closeBundle() { const outDirPath = normalizePath( path.resolve(normalizePath(buildConfig.outDir)) ); const { pathname: ossBasePath, origin: ossOrigin } = new URL(baseConfig); let { secretId: SecretId, secretKey: SecretKey, bucket: Bucket, region: Region, overwrite = false, test = false, lastCommitGlobs = [], } = options; if (!SecretId || !SecretKey || !Bucket || !Region) { const missingParams = []; if (!SecretId) missingParams.push("secretId"); if (!SecretKey) missingParams.push("secretKey"); if (!Bucket) missingParams.push("bucket"); if (!Region) missingParams.push("region"); throw new Error(`关键参数缺失: ${missingParams.join(", ")}`); } delete options.secretId; delete options.secretKey; delete options.overwrite; delete options.test; delete options.lastCommitGlobs; const client = new COS({ ...options, SecretId, SecretKey, }); const ssrClient = buildConfig.ssrManifest; const ssrServer = buildConfig.ssr; const files = await glob(outDirPath + "/**/*", { nodir: true, dot: true, ignore: // custom ignore options.ignore ? options.ignore : // ssr client ignore ssrClient ? ["**/ssr-manifest.json", "**/*.html"] : // ssr server ignore ssrServer ? ["**"] : // default ignore "**/*.html", }); // 分离需要最后提交的文件和普通文件 const { normalFiles, lastCommitFiles } = separateFilesByGlobs( files, lastCommitGlobs, outDirPath ); log("tencent oss upload start"); console.log(color.blue(`构建输出目录: ${outDirPath}`)); console.log(color.blue(`CDN 基础路径: ${baseConfig}`)); console.log(color.blue(`总文件数: ${files.length}`)); console.log(color.blue(`普通文件: ${normalFiles.length} 个`)); console.log(color.blue(`最后提交文件: ${lastCommitFiles.length} 个`)); if (test) { console.log(color.yellow("⚠️ 测试模式已启用,不会真实上传文件")); } console.time("tencent oss upload complete ^_^, cost"); // 先上传普通文件 console.log(color.cyan(`上传普通文件 (${normalFiles.length} 个):`)); for (const fileFullPath of normalFiles) { const filePath = 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 (test) { console.log(`test upload path: ${output}`); continue; } //是否覆盖上传文件 if (overwrite) { let [err, data] = await to( upDown(client, { Bucket, Region, ossFilePath, fileFullPath }) ); data && console.log(`upload complete: ${output}`); if (err) throw new Error(err); continue; } //不覆盖的话,先校验下文件是否存在 let [err, data] = await to( client.headObject({ Bucket, Region, Key: ossFilePath, }) ); data && console.log(`${color.gray("files exists")}: ${output}`); if (err) { if (err.code === "404") { let [err, data] = await to( upDown(client, { Bucket, Region, ossFilePath, fileFullPath }) ); data && console.log(`upload complete: ${output}`); if (err) throw new Error(err); } else { throw new Error(err); } } } // 最后上传指定的文件 if (lastCommitFiles.length > 0) { console.log( color.cyan(`\n上传最后提交文件 (${lastCommitFiles.length} 个):`) ); for (const fileFullPath of lastCommitFiles) { const filePath = 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 (test) { console.log(`test upload path: ${output}`); continue; } //是否覆盖上传文件 if (overwrite) { let [err, data] = await to( upDown(client, { Bucket, Region, ossFilePath, fileFullPath }) ); data && console.log(`upload complete: ${output}`); if (err) throw new Error(err); continue; } //不覆盖的话,先校验下文件是否存在 let [err, data] = await to( client.headObject({ Bucket, Region, Key: ossFilePath, }) ); data && console.log(`${color.gray("files exists")}: ${output}`); if (err) { if (err.code === "404") { let [err, data] = await to( upDown(client, { Bucket, Region, ossFilePath, fileFullPath }) ); data && console.log(`upload complete: ${output}`); if (err) throw new Error(err); } else { throw new Error(err); } } } } console.log(""); console.timeLog("tencent oss upload complete ^_^, cost"); }, }; } function upDown(client, option) { let { Bucket, Region, ossFilePath: Key, fileFullPath } = option; return client.putObject({ Bucket, Region, Key, Body: fs.createReadStream(fileFullPath), }); } function separateFilesByGlobs(files, lastCommitGlobs, outDirPath) { if (!lastCommitGlobs || lastCommitGlobs.length === 0) { return { normalFiles: files, lastCommitFiles: [] }; } const normalFiles = []; const lastCommitFiles = []; for (const fileFullPath of files) { const relativePath = path.relative(outDirPath, fileFullPath); const shouldBeLastCommit = lastCommitGlobs.some((pattern) => { return ( minimatch(relativePath, pattern) || minimatch(fileFullPath, pattern) ); }); if (shouldBeLastCommit) { lastCommitFiles.push(fileFullPath); } else { normalFiles.push(fileFullPath); } } return { normalFiles, lastCommitFiles }; } function log(logStr) { console.log(""); console.log(logStr); console.log(""); } // 导出函数以便测试 export { separateFilesByGlobs, upDown, log };