いがにんのぼやき

WEBエンジニアのブログ。IT、WEB、バンド、アニメ。

簡単!Viteのプラグインを書いて、実行するソースコードを置き換える

この記事は 一休.comのカレンダー | Advent Calendar 2023 - Qiita20日目の記事です。
この記事ではViteのプラグインを書いて、実行するソースコードを置き換える方法を書いていきます。

自分はメインでNuxt3を触っていて、その開発ビルド、本番ビルドにはViteを用いています。
Viteではプラグインを書くことができるのですが、実はとても簡単に書くことができます。

Viteのプラグインのドキュメントはこちら
https://ja.vitejs.dev/guide/api-plugin.html

Viteのプラグインを書くのは実質的にはRollupのプラグインを書くことになるのでRollupのドキュメントも読むと理解が進みます。
https://rollupjs.org/plugin-development/

では実際に書いていきましょう。

まずはViteのアプリケーションを作成します。
templateはなんでもよいですが、自分はVueを書くことが多いのでVueにしました。

pnpm create vite transform-vite-example --template vue-ts
pnpm install

後述しますが、ソースマップの更新のために必要なライブラリもインストールしておきます。 https://github.com/rich-harris/magic-string

pnpm add -D magic-string

今回は簡単に以下の要件を実装してみます。

console.logが記載されている場合、console.logが出力される値をすべて [masked] に変更する

実際に以下のコードを書き換えるViteプラグインを実装していきます。

<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)

function add() {
  count.value++
  console.log(`console.log:${count.value}`)
}
</script>

<template>
  <button type="button" @click="add">count is {{ count }}</button>
</template>

上記のコードの、

console.log(`console.log:${count.value}`)

という箇所を

console.log('[masked]')

というコードに置き換えます。

コードの変換にはRollupのtranformフックを設定することで実現できます。
https://rollupjs.org/plugin-development/#transform

実際のtransformを使ったコードがこちら。

import MagicString from 'magic-string'
import type { Plugin } from 'vite'

export const examplePlugin = <Plugin>{
  name: 'transform-vite-example',
  transform(code, id) {
    if (id.includes('node_modules') || id.endsWith('.spec.ts')) {
      return
    }

    const newCode = new MagicString(code)

    newCode.replaceAll(
      /console.log\(.+?\)/g,
      `console.log('[masked]')`
    )

    return {
      code: newCode.toString(), 
      map: newCode.generateMap({ hires: true }),
    }
  }
}

このファイルを vite.config.ts 内で読み込めば上記要件が実装できます。

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

import { examplePlugin } from './plugins/example'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    examplePlugin, // 追加
  ],
})

そのまま出力されていたものが

[masked] に置き換わっています。

コードをそれぞれ解説していきます。

まずは前述したRollupのtransformフックです。コードを変換するにはこのフックを用います。

  transform(code, id) {

コードの変換対象に node_modules やテストファイルを含めないようにしています。

    if (id.includes('node_modules') || id.endsWith('.spec.ts')) {
      return
    }

ここからが重要で、magic-stringを用いてコードの変換を行います。
なぜmagic-stringというライブラリを使う必要があるのかというと、ソースマップを正しく更新するためです。
Viteでは開発したコードをビルド時に変換して実行用のコードを生成しています。
そのため、エラーが出たときなどにそのままstacktraceを出力すると実行用のコード位置、関数名で出力されてしまい理解することが難しくなります。
開発コードと実行用のコードを紐づけるソースマップをビルド時に出力することで、stacktraceが元のコードの位置、関数名で出力されるようになります。

Viteのプラグインではそれを考慮して書き換えをする必要があります。
そのまま直接文字列置換を行ってしまうとソースマップが正しく更新されず意図したstacktraceが出力されないようになってしまいます。
それを防ぐのがmagic-stringです。

ということで実際に使ったコードがこちら。console.log(~~~) という部分を console.log('[masked]') に変換します。
(改行対応とかあんまり細かいことはしていません)

    const newCode = new MagicString(code)

    newCode.replaceAll(
      /console.log\(.+?\)/g,
      `console.log('[masked]')`
    )

これをmagic-stringのメソッドを使って返してあげることで正しい書き換えを行うことができます。

    return {
      code: newCode.toString(), 
      map: newCode.generateMap({ hires: true }),
    }

こんな形でViteの書き換え処理はさっと実装できるので、ぜひ活用していきたいですね