起因
最近学习如何编写vite插件,于是便想着写一个用于上传项目产物到oss的vite插件。支持自定义实现上传方法。
话不多说,干就干完了。
源码仓库
创建项目
bash
mkdir vite-plugin-oss
cd vite-plugin-oss
yarn init -y
安装项目所需依赖
- 安装tsup(用于构建项目)
shell
yarn add tsup -D
运行时依赖
依赖 | 描述 |
---|---|
@baiducloud/sdk | 百度云sdk |
ali-oss | 阿里云oss-sdk |
glob | |
picocolors |
构建时依赖
json
"devDependencies": {
"@types/ali-oss": "^6.16.11",
"@types/glob": "^8.1.0",
"@types/node": "^20.11.27",
"tsup": "^8.0.2",
"typescript": "^5.4.2",
"vite": "^5.1.6"
},
最终package.json文件
json
{
"name": "@isfive/vite-plugin-oss",
"version": "1.0.2",
"main": "dist/index.mjs",
"module": "dist/index.mjs",
"types": "dist/index.d.mts",
"files": [
"dist/*"
],
"license": "MIT",
"scripts": {
"build": "tsup"
},
"devDependencies": {
"@types/ali-oss": "^6.16.11",
"@types/glob": "^8.1.0",
"@types/node": "^20.11.27",
"tsup": "^8.0.2",
"typescript": "^5.4.2",
"vite": "^5.1.6"
},
"dependencies": {
"@baiducloud/sdk": "^1.0.0-rc.42",
"ali-oss": "^6.20.0",
"glob": "^10.3.10",
"path": "^0.12.7",
"picocolors": "^1.0.0"
},
"peerDependencies": {
"vite": "^5.0.0"
}
}
tsup.config.ts(打包配置文件)
ts
import { defineConfig } from 'tsup'
// 导出默认配置
export default defineConfig({
// 生成d.ts文件
dts: true,
// 清理输出目录
clean: true,
// 最小化输出
minify: true,
// 将项目拆分为单个文件
splitting: true,
// 输出目录
outDir: 'dist',
// 格式
format: [ 'esm'],
// 入口文件
entry: ['src/index.ts'],
// 排除vite
external:['vite'],
// 保留注释
keepNames: true,
sourcemap: true,
})
编写插件代码
src/index.ts
typescript
// 导入 normalizePath 函数,用于处理路径
import { normalizePath } from "vite";
// 导入 picocolors 库,用于生成随机颜色
import color from "picocolors";
// 导入 globSync 函数,用于处理 glob 匹配
import { globSync } from "glob";
// 导入 path 库,用于处理文件路径
import path from "path";
// 导入 Options 类型,用于声明配置参数
import type { Options } from "./types";
import IClient from "./IClient";
// 导出 resolver 中的所有内容
export * from "./resolver"
// 导出 types 中的所有内容
export * from "./types"
export type CustomClient = IClient & {}
// 定义 handleIgnore 函数,用于处理忽略规则
const handleIgnore = (
ignore: string[],
ssrServer: string,
ssrClient: string
) => {
if (ignore === undefined) return "";
if (ignore) {
if (ssrClient) {
return ["**/ssr-manifest.json", "**/*.html", ...ignore];
}
if (ssrServer) {
return ["**"];
}
}
};
// 定义 VitePluginOSS 函数,用于处理上传OSS操作
/**
*
* @param config 插件入参
* @param {IClient} config.client - 上传客户端类
* @param {boolean} config.enabled - 是否开启上传
* @param {string[]} [config.ignore] - 忽略文件数组
* @param {boolean} [config.overwrite] - 是否覆盖原文件
* @param {string} [config.base] - 对象存储路径前缀
* @returns
*/
const VitePluginOSS = (config: Options) => {
// 获取配置参数
const { client, enabled, ignore = [], overwrite = true,base="/" } = config;
// 打印配置参数
console.log("进入插件", config);
// 设置基础路径
let baseConfig = base;
// 设置构建路径
let buildConfig = {} as any;
// 返回插件对象
return {
name: "vite-plugin-oss",
enforce: "post",
apply: "build",
configResolved(resolvedConfig: any) {
// 存储最终解析的配置
buildConfig = resolvedConfig.build;
},
async closeBundle() {
// console.log("VitePluginOSS", config);
// console.log("resolvedConfig", buildConfig);
// console.log("baseConfig", baseConfig);
// 获取输出目录路径
const outDirPath = normalizePath(
path.resolve(normalizePath(buildConfig.outDir))
);
// 获取 ssr 客户端路径
const ssrClient = buildConfig.ssrManifest;
// 获取 ssr 服务端路径
const ssrServer = buildConfig.ssr;
// 获取输出目录下的所有文件
const files = globSync(`${outDirPath}/**/*`, {
// 忽略 nodir
nodir: true,
// 忽略 dot
dot: true,
// 处理忽略规则
ignore: handleIgnore(ignore, ssrServer, ssrClient),
});
let statTime = new Date().getTime();
// 遍历所有文件
for (const fileFullPath of files) {
// 获取文件路径
const filePath = normalizePath(fileFullPath).split(`${outDirPath}/`)[1];
// 获取 oss 存储路径
const ossFilePath = baseConfig + filePath;
// 获取完成时路径
const completePath = ossFilePath;
// 打印输出路径
const output = `${buildConfig.outDir + filePath} => ${color.green(
completePath
)}`;
// 上传文件
await client.upload({
fileFullPath,
filePath,
ossFilePath,
completePath,
output,
overwrite,
});
console.log(fileFullPath);
}
// 计算耗时
const duration = (new Date().getTime() - statTime) / 1000;
console.log("");
console.log(
color.green(`文件已全部上传完毕 ^_^, 耗时 ${duration.toFixed(2)}秒`)
);
console.log("");
},
} as any;
}
// 导出 VitePluginOSS
export default VitePluginOSS;
src/types.ts
js
import OSS from "ali-oss";
import IClient from "./IClient";
// 阿里云oss配置类型
export type AliOSSConfig = OSS.Options;
//百度云bos配置
export type BOSConfig = {
endpoint: string; //传入Bucket所在区域域名
bucket: string;
credentials: {
ak: string; //您的AccessKey
sk: string; //您的SecretAccessKey
};
};
export type Config = AliOSSConfig | BOSConfig;
export type Options = {
base?:string; //对象存储路径前缀
overwrite?:boolean;//是否覆盖原文件
ignore?: string[]; //忽略文件数组
client: IClient;//上传客户端类
enabled: boolean;//是否开启上传
};
export type UploadParameter = {
// 是否覆盖
overwrite:boolean;
// 文件完整路径
fileFullPath:string;
// 文件相对路径
filePath:string;
// oss路径
ossFilePath:string;
// 完成时路径
completePath:string
// 输出路径
output:string
}
src/resolver.ts
js
// 导入IClient类型
import IClient from "./IClient";
// 导入AliOSSClient类型
import AliOSSClient from "./alioss";
// 导入BOSClint类型
import BOSClint from "./bos";
// 导入AliOSSConfig和BOSConfig类型
import type { AliOSSConfig,BOSConfig } from "./types";
// 导出AliOSSResolve函数,参数为AliOSSConfig类型,返回值为IClient类型
/**
*
* @param config
* @returns
*/
export const AliOSSResolve = (config: AliOSSConfig): IClient => {
// 返回一个新的AliOSSClient实例
return new AliOSSClient(config);
};
/**
*
* @param config
* @returns
*/
// 导出BaiduResolve函数,参数为BOSConfig类型,返回值为IClient类型
export const BaiduResolve = (config: BOSConfig): IClient => {
// 返回一个新的BOSClint实例
return new BOSClint(config);
};
src/alioss.ts(阿里云客户端实现类)
js
// 导入阿里OSS模块
import OSS from "ali-oss";
// 导入picocolors模块
import color from "picocolors";
// 导入IClient接口
import type IClient from "./IClient";
// 导入VitePluginOSS中的AliOSSConfig类型
import type { UploadParameter ,AliOSSConfig} from "./types";
// 实现IClient接口的AliOSSClient类
class AliOSSClient implements IClient {
// 定义OSS客户端
client: any;
constructor(createOssOption:AliOSSConfig) {
// 实例化OSS客户端
this.client = new OSS(createOssOption);
}
// 打印"AliOSSClient"
info() {
console.log("AliOSSClient");
}
// 上传文件
async upload({
overwrite,
filePath,
ossFilePath,
fileFullPath,
output ,
}:UploadParameter) {
// 如果options.overwrite为true,则直接上传
try {
await this.client.put(ossFilePath, fileFullPath, {
headers: {},
});
} catch (error) {
console.error("上传失败:",error)
}
}
}
export default AliOSSClient;
src/IClient.ts(上传客户端接口)
js
import { UploadParameter } from "./types";
interface IClient {
client: any;
upload: (uploadParameter:UploadParameter)=>Promise<unknown>;
info?: Function;
beforeUpload?: Function;
afterUpload?: Function;
}
export default IClient;
直接去仓库看代码吧
抄近路:github
vite-plugin-oss
vite插件:将打包后的文件上传到oss
安装
bash
npm install @isfive/vite-plugin-oss --save-dev
使用方法
- 百度云对象存储
ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VitePluginOSS,{BaiduResolve} from "@isfive/vite-plugin-oss"
import type {BOSConfig} from "@isfive/vite-plugin-oss"
const config: BOSConfig = {
endpoint: "https://xxx.bcebos.com", //传入Bucket所在区域域名
bucket: "stoneku",
credentials: {
ak: "xxxxxxxxxxxxxxxx", //您的AccessKey
sk: "xxxxxxxxxxxxxxxxxxxxx" //您的SecretAccessKey
}
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(),VitePluginOSS({
base:"/" //oss路径前缀
overwrite:true,
enabled:true,
client:BaiduResolve(config),
ignore:["xxx.xxx"] //忽略文件列表
})],
})
- 阿里云对象存储
ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VitePluginOSS,{AliOSSResolve} from "@isfive/vite-plugin-oss"
import type {AliOSSConfig} from "@isfive/vite-plugin-oss"
console.log(typeof VitePluginOSS)
const config: AliOSSConfig = {
accessKeyId: 'XXXXXXXXXXXXXXXX',
accessKeySecret: 'XXXXXXXXXXXXXXXXXXXX'
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(),VitePluginOSS({
overwrite:true,
enabled:true,
client:AliOSSResolve(config),
})],
})
- 自定义上传
ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VitePluginOSS from "@isfive/vite-plugin-oss"
import type {CustomClient, UploadParameter} from "@isfive/vite-plugin-oss"
console.log(typeof VitePluginOSS)
/**
* 自定义resolve 需要继承 CustomClient接口
*/
class MyResolve implements CustomClient {
client: any
constructor() {}
// 需要实现upload方法
async upload (uploadParameter: UploadParameter){
console.log(uploadParameter.completePath) //完成路径
console.log(uploadParameter.fileFullPath) //文件完整路径
console.log(uploadParameter.filePath) //文件相对路径
/**
* 在这里做自定义文件上传
*/
}
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(),VitePluginOSS({
overwrite:true,
enabled:true,
client:new MyResolve(),
})],
})