ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • vite 플러그인 만들기 (with 간단한 예제 & vite-plugin-pages 분석) 😊
    웹 개발 2025. 3. 18. 21:17

    vite는 rollup을 기반으로 만들어졌고, 빌드시 rollup이 사용된다.

    또한 vite의 플러그인은 rollup을 기반으로 vite의 특정 옵션이 추가된 형태로 개발되었기에,

    vite에서 제작한 플러그인은 기본적으로 개발 환경과 빌드 환경에서 모두 호환이 된다. (물론, 각 환경에서만 실행되는 특화된 기능을 제공하는 플러그인도 있다)

     

    실제로 vite 플러그인을 만들어보는 것도 재밌는 경험이 될 것 같아서, 어떻게 만들 수 있는지 간단한 예제를 만들어 알아보기로 했다 😙

     

    🌟 일단 가장 간단한 예제로 일단 플러그인을 만들어보자.

    // vite-plugin-hello.ts
    import { Plugin } from 'vite';
    
    export default function vitePluginHello(): Plugin {
      return {
        name: "vite-plugin-hello", // 플러그인 이름
        buildStart() {
          console.log("Hello from Vite Plugin!");
        },
      };
    }

    이렇게 플러그인 함수를 만든뒤, 연결한다.

    // vite.config.ts
    import { defineConfig } from "vite";
    import vitePluginHello from "./vite-plugin-hello";
    
    export default defineConfig({
      plugins: [vitePluginHello()],
    });

     

    이렇게 단순하게 로그를 남기는 플러그인을 만들 수 있지만, 좀 더 vite의 기능을 활용한 플러그인을 만들 수도 있다.

     

    🌠 vite는 transform, resolveId, load 같은 여러 훅(hook)을 제공하여 빌드 과정에서 다양한 동작을 수행할 수 있다.

    import { Plugin } from 'vite';
    
    export default function vitePluginVirtualModule(): Plugin {
      return {
        name: "vite-plugin-virtual-module",
        resolveId(id: string) {
          if (id === "virtual:hello") {
            return id; // 가상 모듈로 처리
          }
          // return이 null 이나 void면 가상모듈 X
        },
        load(id: string) {
          if (id === "virtual:hello") {
            return `export default 'Hello from virtual module!';`;
          }
        },
      };
    }

     

    🥖  load와 resolveId 훅을 사용하면 특정 모듈의 경로를 변경할 수 있다.

    🥖  load 훅은 resolveId에서 처리한 가상의 모듈에 대해 실제 내용을 제공한다.

    위 예제에서 load virtual:hello 모듈이 요청될 때, export default 'Hello from virtual module!';을 반환하도록 하였다.

     

    이렇게 하면 Vite는 해당 모듈이 실제 파일이 아니라도, 우리가 정의한 내용을 사용하여 코드를 빌드할 수 있다. 

    예를 들면, API 호출을 가상으로 처리하거나, 동적 코드 생성을 할 수도 있다.

     

    위의 예제 플러그인을 사용하면 virtual:hello를 import 할때, 실제 파일이 없어도 해당 코드가 반환된다 🫢

     

    import message from 'virtual:hello';
    console.log(message); // "Hello from virtual module!"

     

     

    + 근데, 타입스크립트라면 해당 모듈에 대한 타이핑이 필요하다

    // global.d.ts
    declare module 'virtual:hello' {
      const value: string;
      export default value;
    }

     

    // tsconfig.app.json
    
    {
      "compilerOptions": {
      	...
        "types": ["./global.d.ts"],
     }

     

    🍠 transform 훅을 사용하면 빌드 과정에서 특정 파일의 내용을 변경할 수 있다.

     

    예를 들어, .env 변수를 자동으로 주입하는 기능을 플러그인으로 제공할 수 있다.

     

    import { Plugin } from 'vite';
    
    export default function vitePluginTransform(): Plugin {
      return {
        name: "vite-plugin-transform",
        transform(code: string, id: string) {
          if (id.endsWith(".ts")) {
            return code.replace("process.env.HELLO", "'Hello from transform' ");
          }
        },
      };
    }

     

    위의 플러그인 예제를 직접 사용하면,

    console.log(process.env.HELLO); // "Hello from transform"

     

    플러그인을 통해서 env.HELLO를 주입해줄 수 있다.

     

    + 기본적으로 transform의 인자로 code가 주어지기에, 코드가 처리되기 전에 `process.env.HELLO`라는 텍스트를 다른 문자열로 바꿔치기를 함으로써 마치 env를 주입하는 것 처럼 동작할 수 있게 한다.

     

     

    + 추가로 작성해본 vite plugins들 ( vite-plugin-trolling  & vite-plugin-code-stats.ts ) 🌺

     

    https://github.com/citron03/practice-trpc/commit/4c2570f987d0b91eca47030ac5e799abf4dd381c

     

    feat(vite): add custom vite-plugins · citron03/practice-trpc@4c2570f

    + plugins: [react(), vitePluginTrolling(), vitePluginCodeStats()],

    github.com

     

     

    vite 플러그인은 다양한 시점에서 코드를 변환할 수 있도록 훅을 제공하므로, 이를 참고하여 원하는 시점에서 원하는대로 코드를 변형하거나 추가로 기능을 제공하도록 할 수 있다 🫰

    티스토리 표가 자꾸 깨지네요

     

    vite가 export하는 Plugin 타입을 확인하면, 더 많은 vite 플러그인의 기능을 확인할 수 있다. (물론, 타입도 함께)

    + 옵션이 있는 경우, ssr을 설정하는 boolean 값이 들어갈 수 있는데 vite에서 ssr에 대한 설정을 플러그인을 통해서도 제공할 수 있는 듯 하다 🌺

    + build 관련 훅 (buildStart, buildEnd 등)은 개발 서버에서 실행되지 않는다.

     

     

    실제 사용하는 vite-plugin 간단 분석 (✨ vite-plugin-pages)

    간단하게 vite 플러그인에 대해서 알아보고 예제도 작성해봤지만, 그래도 실제로 많이 사용하는 vite 플러그인을 분석해보면 해본 내용들이 와닿지 않을까, 싶어서 플러그인 하나의 코드를 열어보기로 했다.

     

    열어볼 코드는 vite-plugin-pages로, 간단히 말하자면 next의 page router같은 page 폴더 기반 라우팅을 자동으로 vite 환경에서 구현해주는 착한 플러그인이다 ✈️

     

    https://github.com/hannoeru/vite-plugin-pages

     

    GitHub - hannoeru/vite-plugin-pages: File system based route generator for ⚡️Vite

    File system based route generator for ⚡️Vite. Contribute to hannoeru/vite-plugin-pages development by creating an account on GitHub.

    github.com

     

    깃헙 레포지토리를 찾아 들어가서, 플러그인이 어떻게 구성되어 있는지 코드를 살펴보도록 했다.

     

    packages.json의 main을 보고 index.ts를 찾아가면 익숙한 구조를 만날 수 있다.

     

    간단하게 주석을 남긴 코드인데, 구조는 그렇게 어렵지 않다 😃

    import type { Plugin } from 'vite'
    import type { UserOptions } from './types'
    import { MODULE_ID_VIRTUAL, ROUTE_BLOCK_ID_VIRTUAL, routeBlockQueryRE } from './constants'
    
    import { PageContext } from './context'
    import { parsePageRequest } from './utils'
    
    function pagesPlugin(userOptions: UserOptions = {}): Plugin {
      let ctx: PageContext
    
      return {
        name: 'vite-plugin-pages',
        enforce: 'pre', // 플러그인이 다른 transform 훅보다 먼저 실행되도록 설정
        async configResolved(config) {
          // Vite 설정이 확정된 후 실행됨
          // 프로젝트가 React 또는 Solid인지 자동으로 감지하여 resolver 설정    
          // auto set resolver for react project
          if (
            !userOptions.resolver
            && config.plugins.find(i => i.name.includes('vite:react'))
          ) {
            userOptions.resolver = 'react'
          }
    
          // auto set resolver for solid project
          if (
            !userOptions.resolver
            && config.plugins.find(i => i.name.includes('solid'))
          ) {
            userOptions.resolver = 'solid'
          }
    
          ctx = new PageContext(userOptions, config.root)
          ctx.setLogger(config.logger)
          await ctx.searchGlob() // 페이지 파일을 검색하여 라우트 구성 ?
        },
        api: {
          getResolvedRoutes() {
            // 플러그인이 해석한 라우트 데이터를 제공하는 API ?      
            return ctx.options.resolver.getComputedRoutes(ctx)
          },
        },
        configureServer(server) {
          // 개발 서버 설정을 위한 훅    
          ctx.setupViteServer(server) // 핫 리로드 및 파일 감지 설정
        },
        resolveId(id) {
          // 특정 가상 모듈 ID를 처리하여 변환    
          if (ctx.options.moduleIds.includes(id))
            return `${MODULE_ID_VIRTUAL}?id=${id}`
    
          if (routeBlockQueryRE.test(id))
            return ROUTE_BLOCK_ID_VIRTUAL
    
          return null
        },
        async load(id) {
          // resolveId에서 처리한 가상 모듈의 실제 내용을 반환    
          const {
            moduleId,
            pageId,
          } = parsePageRequest(id)
    
          if (moduleId === MODULE_ID_VIRTUAL && pageId && ctx.options.moduleIds.includes(pageId))
            return ctx.resolveRoutes() // 라우트 정보를 변환하여 제공 ?
    
          if (id === ROUTE_BLOCK_ID_VIRTUAL) {
            return {
              code: 'export default {};',
              map: null,
            }
          }
    
          return null
        },
      }
    }
    
    export { syncIndexResolver } from './options'
    export type {
      ReactRoute,
      SolidRoute,
      VueRoute,
    } from './resolvers'
    
    export {
      reactResolver,
      solidResolver,
      vueResolver,
    } from './resolvers'
    export * from './types'
    export { PageContext }
    export default pagesPlugin

     

    물론, 훌륭한 라이브러리인만큼  기능이 분리되어 있고, 더 자세한 동작을 알기 위해서는 더 깊이 파고들어야 하겠지만.

    일단, 우리가 vite 플러그인에 대해서 작성해보고 공부한 내용이 vite-plugin-pages에 포함되어 있음을 알 수 있다.

     

    load, resolveId, configResolved(vite 전용 훅), configureServer(vite 전용 훅) 등이 vite에서 사용할 수 있는 훅임을 알고 이들이 어느때에 어떻게 동작하는지 파악할 수 있다. (이를 알기에, vite 공식문서를 참고할 수 있다)

     

    그렇기 때문에, 어느 순서대로 이 플러그인을 분석해야될지 접근할 수 있으며, 크게 어떤 구조로 움직이는지 대강 상상할 수 있다 😎

     

    😊 참고 자료 

    https://ko.vite.dev/guide/api-plugin.html#authoring-a-plugin

     

    Vite

    Vite, 프런트엔드 개발의 새로운 기준

    ko.vite.dev

    https://www.npmjs.com/package/vite-plugin-pages

     

    vite-plugin-pages

    File system base vue-router plugin for Vite. Latest version: 0.32.5, last published: a month ago. Start using vite-plugin-pages in your project by running `npm i vite-plugin-pages`. There are 38 other projects in the npm registry using vite-plugin-pages.

    www.npmjs.com

     

Designed by Tistory.