玛雅,我也会写vite插件了!

起因

最近学习如何编写vite插件,于是便想着写一个用于上传项目产物到oss的vite插件。支持自定义实现上传方法。

话不多说,干就干完了。

源码仓库

github

创建项目

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(),
  })],
})
相关推荐
二十雨辰24 分钟前
[uni-app]小兔鲜-04推荐+分类+详情
前端·javascript·uni-app
霸王蟹1 小时前
Vue3 项目中为啥不需要根标签了?
前端·javascript·vue.js·笔记·学习
儒雅的烤地瓜2 小时前
JS | 如何解决ajax无法后退的问题?
前端·javascript·ajax·pushstate·popstate事件·replacestate
觉醒法师2 小时前
Vue3+TS项目 - ref和useTemplateRef获取组件实例
开发语言·前端·javascript
老章学编程i2 小时前
Vue工程化开发
开发语言·前端·javascript·vue.js·前端框架
什么鬼昵称3 小时前
Pikachu-PHP反序列化
开发语言·javascript·php
果子切克now3 小时前
vue3导入本地图片2种实现方法
前端·javascript·vue.js
谢尔登4 小时前
【移动端】事件基础
前端·javascript·html
敲啊敲95274 小时前
uni-app之旅-day02-分类页面
前端·javascript·uni-app
汪子熙4 小时前
Angular 客户端渲染时,从 ng-state 里读取 SSR 状态的具体逻辑
前端·javascript·angular.js