@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
JavaScript
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 };