vite plugin 学习并写出一些小的demo

164 阅读3分钟

vite 插件钩子

  1. config 类型:async (config: Config, env: { command: string, mode: string }) => Promise<void | Config> 用途:修改或扩展 Vite 的配置。这个钩子在解析配置文件后、其他插件之前运行。
  2. configResolved 类型:async (config: ResolvedConfig) => Promise<void | ResolvedConfig> 用途:在配置完全解析和验证之后调用。你可以在这个钩子中访问所有解析后的配置选项。
  3. transform 类型:async (code: string, id: string) => Promise<string | { code: string, map: RawSourceMap } | null> 用途:转换文件内容。例如,你可以使用此钩子来编译 Vue 或其他框架的组件,或者应用 Babel 转换。
  4. transformIndexHtml 类型:async (html: string, url: URL) => Promise<string | null> 用途:转换 index.html 文件的内容。例如,你可以插入 script 标签或修改页面结构。
  5. resolveId 类型:async (source: string, importer?: string) => Promise<string | null | false> 用途:解析模块 ID。你可以在这里实现自定义的模块解析逻辑,例如支持特定的模块导入路径或后缀。
//业务代码
import A from "@A"
console.log(A())// 1

// ./add.ts
export default ()=>{
    return 1
}

async resolveId(source: string, importer: any, options: any) {  
    console.log('source', source)
    // 检查 rawId 是否匹配自定义别名  
    if (source === '@A') {  
      // 返回实际的文件路径  
      return resolve(__dirname, "./src/components/add.ts");  
    }  
    // 如果 rawId 不匹配,则返回 null 或 undefined,让其他插件继续处理  
    return null;  
  },  

  1. load 类型:async (id: string) => Promise<string | null | void> 用途:加载模块内容。如果 Vite 无法直接加载某个模块,你可以在这里实现自定义的加载逻辑。
// 能拿到文件的路径,然后通过fs拿到内容
if (id.endsWith('.ts')) {
    // 假设我们想要在每个 .ts 文件的开头添加一行 console.log  
    const originalCode = fs.readFileSync(id).toString()/* 获取原始代码的代码 */; // 注意:这里需要实际获取源代码的逻辑  
    const modifiedCode = `console.log('This file is loaded by Vite!');\n${originalCode}`;
    return modifiedCode;
}
// 结果如下
console.log("This file is loaded by Vite!");
export default () => {
  return 1;
};

  1. configureServer 类型:(server: ViteDevServer) => void 用途:配置开发服务器。你可以在这里添加中间件、自定义 HMR 行为等。

  2. transformRequest 类型:async (url: URL, options: TransformRequestOptions) => Promise<void | URL | { url: URL, headers?: HeadersInit }> 用途:在请求资源之前进行转换。你可以在这里修改请求的 URL、添加请求头或返回自定义的响应。

  3. handleHotUpdate 类型:(ctx: HotUpdateContext) => void 用途:处理热模块替换(HMR)的更新。你可以在这里定义如何应用模块更新或自定义 HMR 的行为。

handleHotUpdate({ file, server }: any) {
    console.log("handleHotUpdate",file )
    // 检查是否是 JSON 文件更新  
    if (file.endsWith('a.json')) {
        // 发送自定义事件通知客户端  
        server.ws.send({
            type: 'custom',
            event: 'json-file-updated',
            path: file,
        });
    }
},

// 页面上
// 在需要监听 JSON 文件更新的组件或文件中  
if (import.meta.hot) {  
  import.meta.hot.on('json-file-updated', (data) => {  
    // 处理 JSON 文件更新逻辑,比如重新加载数据、更新 UI 等  
    console.log('JSON 文件已更新:', data);  
    // ... 其他处理逻辑 ...  
  });  
}
  1. buildStart 类型:async (options: BuildOptions) => Promise 用途:在构建过程开始时调用。你可以在这里进行一些初始化工作,例如设置构建目录或加载额外的资源。

  2. buildEnd 类型:async () => Promise 用途:在构建过程结束时调用。你可以在这里进行一些清理工作或生成额外的输出文件。

  3. closeBundle 类型:async (outputOptions: RollupOutputOptions, bundle: RollupBundle, isWrite: boolean) => Promise 用途:在 Rollup 打包结束后,但在输出文件之前调用。你可以在这里对打包结果进行后处理或自定义输出格式。

备注: vite 通过预构建node_modules 放到.vite 缓存起来,而本地的业务代码vue、css等会放到vite内部实现的 ModuleGraph 的 内存中,里面有很多Map、Set 去实现缓存。

import fs from 'fs';
import { resolve } from 'path';

export default () => {
   return {
       name: "vite-my-plugin",

       async config(config: any, env: any) {
           console.error('config')
           await clear()
           await writeLog('config:' + JSON.stringify(config))

           await writeLog('config:' + JSON.stringify(env))

           return config
       },
       async configResolved(config: any) {
           console.error('configResolved')
           await writeLog('configResolved:' + JSON.stringify(config))
           return config
       },
       async resolveId(source: string, importer: any, options: any) {
           console.error('resolveId')
           // 检查 rawId 是否匹配自定义别名  
           if (source === '@A') {
               // 返回实际的文件路径  
               return resolve(__dirname, "./src/components/add.ts");
           }
           // 如果 rawId 不匹配,则返回 null 或 undefined,让其他插件继续处理  
           return null;
       },

       async load(id: string) {
           console.error('load')
           // if (id.endsWith('.ts')) {
           //     // 假设我们想要在每个 .js 文件的开头添加一行 console.log  
           //     const originalCode = fs.readFileSync(id).toString()/* 获取原始代码的代码 */; // 注意:这里需要实际获取源代码的逻辑  
           //     const modifiedCode = `console.log('This file is loaded by Vite!');\n${originalCode}`;
           //     return modifiedCode;
           // }
       },
       transformRequest(url, options) {
           console.log("transformRequest:", url, options)
           // 假设我们想要给所有 .js 文件的请求添加一个自定义的查询参数  
           // if (url.endsWith('.js')) {  
           //   const modifiedUrl = `${url}?customParam=true`;  
           //   return { url: modifiedUrl };  
           // }  
           // 对于其他类型的文件,直接返回原 URL  
           return { url };
       },
       handleHotUpdate({ file, server }: any) {
           console.log("handleHotUpdate",file )
           // 检查是否是 JSON 文件更新  
           if (file.endsWith('a.json')) {
               // 发送自定义事件通知客户端  
               server.ws.send({
                   type: 'custom',
                   event: 'json-file-updated',
                   path: file,
               });
           }
       },
       transform(source: any, id: any) {
           console.error('transform')
           if (process.env.NODE_ENV === 'development' && process.versions.node) {
               const arr = id?.split("/")
               try {
                   fs.writeFile(`./test/${arr[arr?.length - 1]}`, source, { encoding: 'utf8', flag: 'w' }, function (err) {
                       if (err) throw err;
                   });
               } catch (error) {
                   // console.log(error)
               }
           }
           return source
       },
       async transformIndexHtml(html: string, url: any) {
           console.error('transformIndexHtml')
           const scriptContent = `  
           console.log('Hello from dynamically inserted script啊啊啊!');  
           // 这里可以添加更多的JavaScript代码  
         `;
           await writeLog('config:' + JSON.stringify(html))
           // 使用模板字符串插入<script>标签  
           return html.replace('</body>', `  
           <script>  
             ${scriptContent}  
           </script>  
         </body>  
         `);
       },
       buildStart({ command, mode }: any){
           console.log('buildStart:', command, mode)
       },
       buildEnd(context){
           console.error('buildEnd completed')
       },

       closeBundle(bundle) {  
           // bundle 对象可能包含构建结果的相关信息,但具体结构取决于 Vite 的版本  
           // config 是 Vite 的配置对象  
       
           // 打印构建完成消息  
           console.log('Vite 构建完成,开始执行 closeBundle 钩子中的操作...' );  
       
           // 在这里,你可以根据 bundle 或 config 对象中的信息执行自定义操作  
           // 例如,你可以访问输出目录、构建时间等信息  
       
           // 假设我们要访问输出目录(outputDir)  
        
       
           // 注意:closeBundle 钩子通常是同步的,但如果你需要执行异步操作,可以将其定义为 async 函数  
         },  

   }
}

const logPath = "./log.log";

const clear = async () => {
   await fs.writeFileSync(logPath, "", { encoding: 'utf8', flag: 'w' });
}

const writeLog = async (log: string) => {
   let content = ""

   if (fs.existsSync(logPath)) {
       content = fs.readFileSync(logPath).toString()
   }
   content += '\r\n' + log
   await fs.writeFileSync(logPath, content, { encoding: 'utf8', flag: 'w' });
}

官方插件

@vitejs/plugin-vue 提供 Vue 3 单文件组件支持。

@vitejs/plugin-vue-jsx 提供 Vue 3 JSX 支持(通过 专用的 Babel 转换插件)。

@vitejs/plugin-vue2 提供对 Vue 2.7 的单文件组件支持。

@vitejs/plugin-vue2-jsx 提供对 Vue 2.7 JSX 对支持(通过 dedicated Babel transform)。

@vitejs/plugin-react 使用 esbuild 和 Babel,使用一个微小体积的包脚注可以实现极速的 HMR,同时提升灵活性,能够使用 Babel 的转换管线。在构建时没有使用额外的 Babel 插件,只使用了 esbuild。

@vitejs/plugin-react-swc 在开发时会将 Babel 替换为 SWC。在构建时,若使用了插件则会使用 SWC+esbuild,若没有使用插件则仅会用到 esbuild。对不需要非标准 React 扩展的大型项目,冷启动和模块热替换(HMR)将会有显著提升。

@vitejs/plugin-legacy 为打包后的文件提供传统浏览器兼容性支持。

vite-plugin-pwa 0 配置 用来生成 pwa 的插件