Vue CLI 無痛轉換 Vite 紀錄
- November 26, 2022
 
 由於前陣子我們團隊遇到一個 @vue/cli-plugin-babel 的 cache 命中問題,也因此檢視了一下我們目前專案使用 vue cli + Webpack 4 似乎已經有點老舊了,因此協助團隊測試及評估升級 Vite。
由於我們團隊有上千個專案,因此希望盡量維持與原本的架構一致,一方面是萬一我們想要抽換回 Webpack 5 不必更動過多,另一方面是我們在升級舊專案時可以不更改專案程式碼直接移植,減少不必要的問題。
經過整理思考後有以下主要問題需要解決:
- 環境變數
 - val-loader
 - Index.html 變數注入
 
環境變數
注入環境變數
我們團隊在 Webpack 時期已有自行封裝環境變數的 function ,且封裝的功能性非常多,但 Vite 使用 import.meta.env 來取得環境變數,因此首要解決問題是維持 process.env.XXX 在專案中的寫法。
Vite 有提供 define 讓我們自行注入環境變數,因此可以借助 define 來實現這個功能。
// public env
import envObject from "XXXX";
export default defineConfig(() => {
  const define = {
    "process.env": { ...envObject },
  };
  return {
    define,
  };
});
撰寫 rollup plugin
雖然此舉已經可以在專案中取得 process.env ,但卻沒有 TypeScript 的自動提示,因此我們透過撰寫 rollup plugin 來自動產生型別定義檔案。
// public env
import envObject from "XXXX";
import path from "path";
import { rm, existsSync } from "fs";
import { readFile, writeFile } from "fs/promises";
function autoGenerateProcessEnv() {
  const filepath = path.resolve("./src/process.env.d.ts");
  const getDeclaration = () => {
    const typeCodeLists = extendEnv.env.map((envKey) => {
      return `${envKey}: ${typeof process.env[envKey]};`;
    });
    if (typeCodeLists.length === 0) return "";
    const code = `declare namespace NodeJS {
  export interface ProcessEnv {
    ${typeCodeLists.join("\n    ")}
  }
}
`;
    return code;
  };
  return {
    name: "auto-generate-process-env",
    configResolved: async () => {
      const originalContent = existsSync(filepath)
        ? await readFile(filepath, "utf-8")
        : "";
      const code = getDeclaration();
      if (!code) return;
      if (code === originalContent) return;
      await writeFile(filepath, code, "utf-8");
    },
  };
}
export default defineConfig(() => {
  return {
    plugins: [autoGenerateProcessEnv()],
  };
});
如此一來在開發時也能獲得自動提示,增加開發體驗及品質。
val-loader
許多開發者常常使用 Webpack Modules + val-loader 來打包一些資源,在 Vite 與 Rollup 中可以使用 Virtual Modules 來達成。
以下 plugin 範例有簡化過,可參考概念,值得注意的是路徑方面 Virtual Modules 是以 vite.config.ts 的所在目錄(project root)為主,而不是你的 plugin 檔案所在目錄。
const fetchData = () => {
  //  fetch data and return
  return data;
};
export default function myPlugin() {
  const virtualModuleId = "virtual:fetch-data";
  const resolvedVirtualModuleId = "\0" + virtualModuleId;
  return {
    name: "fetch-data", // required, will show up in warnings and errors
    resolveId(id: string) {
      if (id === virtualModuleId) {
        return resolvedVirtualModuleId;
      }
    },
    async load(id: string) {
      if (id !== resolvedVirtualModuleId) return null;
      // development return local data
      if (process.env.NODE_ENV !== "production") {
        return `
          import localData from './src/...';
          export const data = localData;
        `;
      }
      // production
      const data = await fetchData();
      return `
        export const data = ${JSON.stringify(data)};
      `;
    },
  };
}
接著將 plugin 檔案引入 vite.config.ts 並於 plugins 呼叫即可
import fetchPlugin from './src/plugin/fetch';
export default defineConfig(() => {
  return {
    plugins: [
      fetchPlugin()
    ],
  }
}
並於 src/vite-env.d.ts 進行型別的宣告
// src/vite-env.d.ts
declare module "virtual:fetch-data" {
  const data: {
    // some type
  };
  export const messages;
}
最後就可以在需要的地方引入了
import { data } from "virtual:fetch-data";
Index.html 變數注入
為了避免更改 index.html,我們將注入的規則寫成跟原先 webpack 一樣
/**
 * Replace env variables in index.html
 * @see https://github.com/vitejs/vite/issues/3105#issuecomment-939703781
 * @see https://vitejs.dev/guide/api-plugin.html#transformindexhtml
 */
function htmlPlugin() {
  return {
    name: "html-transform",
    transformIndexHtml: {
      enforce: "pre" as const,
      transform: (html: string): string => {
        return html.replace(/<%= (.*?) %>/g, (match, envKey) => {
          const htmlEnv = {
            // some env
          };
          return htmlEnv[envKey] ?? "";
        });
      },
    },
  };
}
export default defineConfig(() => {
  return {
    plugins: [htmlPlugin()],
  };
});
結論
經過以上更動,我們幾乎可以在不更動專案程式碼時完成舊專案移植,尤其我們團隊因為 Webpack Modules 轉換上的一些困難所以一直沒有升級,也希望此文章可以幫助一些團隊更容易進行遷移升級 Vite 的測試及評估。