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 的測試及評估。