兩週前(202.02.17),vite2.0 發佈了,做爲使用了瀏覽器原生 ESM 爲下一代前端工具,vite 2.0 相較於 1.0 更加成熟。在此以前筆者就開始關注這類「新型」的前端工具。此次趁着 vite 2.0 發佈,也成功將一個基於 vue-cli(-service) + vue2 的已有項目進行了遷移。javascript
遷移工做比較順利,花了不到半天時間。但整個遷移過程當中也遇到了一些小問題,這裏彙總一下,也方便遇到相似問題的朋友一塊兒交流和參考。html
在介紹具體遷移工做前,先簡單介紹下項目狀況。目前該項目上線不到一年,不太有構建相關的歷史遺留債務。項目包含 1897 個模塊文件(包括 node_modules 中模塊),使用了 vue2 + vuex + typescript 的技術棧,構建工具使用的是 vue-cli(webpack)。算是一套比較標準的 vue 技術棧。因爲是內部系統,項目對兼容性的要求較低,用戶基本都使用較新的 Chrome 瀏覽器(少部分使用 Safari)。前端
下面具體來講下遷移中都作了哪些處理。vue
首先須要安裝 vite 並建立 vite 的配置文件。java
npm i -D vite
複製代碼
vue-cli-service 中使用 vue.config.js
做爲配置文件;而 vite 則默認會須要建立一個 vite.config.ts
來做爲配置文件。基礎的配置文件很簡單:node
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
// ...
],
})
複製代碼
建立該配置文件,以前的 vue.config.js 就再也不使用了。webpack
在 vite 中也須要指定入口文件。但和 webpack 不一樣,在 vite 中不是指定 js/ts 做爲入口,而是指定實際的 HTML 文件做爲入口。git
在 webpack 中,用戶經過將 entry 設置爲入口 js(例如 src/app.js
)來指定 js 打包的入口文件,輔以 HtmlWebpackPlugin 將生成的 js 文件路徑注入到 HTML 中。而 vite 直接使用 HTML 文件,它會解析 HTML 中的 script 標籤來找到入口的 js 文件。github
所以,咱們在入口 HTML 中加入對 js/ts 文件的 script 標籤引用:web
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
</noscript>
<div id="app"></div>
+ <script type="module" src="/src/main.ts"></script>
</body>
</html>
複製代碼
注意上面 <script type="module" src="/src/main.ts"></script>
這一行,它使用瀏覽器原生的 ESM 來加載該腳本,/src/main.ts
就是入口 js 的源碼位置。在 vite dev 模式啓動時,它其實啓動了一個相似靜態服務器的 server,將 serve 源碼目錄,所以不須要像 webpack 那樣的複雜模塊打包過程。模塊的依賴加載將徹底依託於瀏覽器中對 import
語法的處理,所以能夠看到很長一串的腳本加載瀑布流:
這裏還須要注意 project root 的設置。在默認是 process.cwd()
,而 index.html 也會在 project root 下進行尋找。爲了方便我將 ./public/index.html
移到了 ./index.html
。
vite 2.0 提供了對 vue 項目的良好支持,但其自己並不和 vue 進行較強耦合,所以經過插件的形式來支持對 vue 技術棧的項目進行構建。vite 2.0 官網目前(2021.2.28)推薦的 vue 插件會和 vue3 的 SFC 一塊兒使用更好。所以這裏使用了一個專門用來支持 vue2 的插件 vite-plugin-vue2,支持 JSX,同時目前最新版本也是支持 vite2 的。
使用上也很簡單:
import { defineConfig } from 'vite';
+ import { createVuePlugin } from 'vite-plugin-vue2';
export default defineConfig({
plugins: [
+ createVuePlugin(),
],
});
複製代碼
使用 vite 構建 ts 項目時,若是使用了 typescript 路徑映射的功能,就須要進行特殊處理,不然會出現模塊沒法解析(找不到)的錯誤:
這裏須要使用 vite-tsconfig-paths 這個插件來作路徑映射的解析替換。其原理較爲簡單,大體就是 vite 插件的 resolveId 鉤子階段,利用 tsconfig-paths 這個庫來將路徑映射解析爲實際映射返回。有興趣的能夠看下該插件的實現,比較簡短。
具體使用方式以下:
import { defineConfig } from 'vite';
import { createVuePlugin } from 'vite-plugin-vue2';
+ import tsconfigPaths from 'vite-tsconfig-paths';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
createVuePlugin(),
+ tsconfigPaths(),
],
});
複製代碼
vite 使用 ESM 做爲模塊化方案,所以不支持使用 require
方式來導入模塊。不然在運行時會報 Uncaught ReferenceError: require is not defined
的錯誤(瀏覽器並不支持 CJS,天然沒有 require 方法注入)。
此外,也可能會遇到 ESM 和 CJS 的兼容問題。固然這並非 vite 構建所致使的問題,但須要注意這一點。簡單來講就是 ESM 有 default 這個概念,而 CJS 沒有。任何導出的變量在 CJS 看來都是 module.exports 這個對象上的屬性,ESM 的 default 導出也只是 cjs 上的 module.exports.default 屬性而已。例如在 typescript 中咱們會經過 esModuleInterop 配置來讓 tsc 添加一些兼容代碼幫助解析導入的模塊,webpack 中也有相似操做。
例如以前的代碼:
module.exports = {
SSO_LOGIN_URL: 'https://xxx.yyy.com',
SSO_LOGOUT_URL: 'https://xxx.yyy.com/cas/logout',
}
複製代碼
const config = require('./config');
複製代碼
在導出和導入上都須要修改成 ESM,例如:
export default {
SSO_LOGIN_URL: 'https://xxx.yyy.com',
SSO_LOGOUT_URL: 'https://xxx.yyy.com/cas/logout',
}
複製代碼
import config from './config';
複製代碼
使用 vue-cli(webpack)時咱們常常會利用環境變量來作運行時的代碼判斷,例如:
const REPORTER_HOST = process.env.REPORTER_TYPE === 'mock'
? 'http://mock-report.xxx.com'
: 'http://report.xxx.com';
複製代碼
vite 仍然支持環境變量的使用,但再也不提供 process.env
這樣的訪問方式。而是須要經過 import.meta.env
來訪問環境變量:
-const REPORTER_HOST = process.env.REPORTER_TYPE === 'mock'
+const REPORTER_HOST = import.meta.env.REPORTER_TYPE === 'mock'
? 'http://mock-report.xxx.com'
: 'http://report.xxx.com';
複製代碼
與 webpack 相似,vite 也內置了一些環境變量,能夠直接使用。
import.meta.env
types補充:vite 提供了它所須要的 types 定義,能夠直接應用 vite/client 來引入,能夠不須要經過如下方式來本身添加。
若是在 typescript 中經過 import.meta.env
來訪問環境變量,可能會有一個 ts 錯誤提示:類型「ImportMeta」上不存在屬性「env」
。
這是由於在目前版本下(v4.2.2)import.meta
的定義仍是一個空的 interface:
interface ImportMeta {
}
複製代碼
但咱們能夠經過 interface 的 merge 能力,在項目中進一步定義 ImportMeta 的類型來拓展對 import.meta.env
的類型支持。例如以前經過 vue-cli 生成的 ts 項目在 src 目錄下會生成 vue-shims.d.ts
文件,能夠在這裏拓展 env 類型的支持:
declare global {
interface ImportMeta {
env: Record<string, unknown>;
}
}
複製代碼
這樣就不會報錯了。
在 webpack 中咱們能夠經過 require.context
方法「動態」解析模塊。比較經常使用的一個作法就是指定某個目錄,經過正則匹配等方式加載某些模塊,這樣在後續增長新的模塊後,能夠起到「動態自動導入」的效果。
例如在項目中,咱們動態匹配 modules 文件夾下的 route.ts 文件,在全局的 vue-router 中設置 router 配置:
const routes = require.context('./modules', true, /([\w\d-]+)\/routes\.ts/)
.keys()
.map(id => context(id))
.map(mod => mod.__esModule ? mod.default : mod)
.reduce((pre, list) => [...pre, ...list], []);
export default new VueRouter({ routes });
複製代碼
文件結構以下:
src/modules
├── admin
│ ├── pages
│ └── routes.ts
├── alert
│ ├── components
│ ├── pages
│ ├── routes.ts
│ ├── store.ts
│ └── utils
├── environment
│ ├── store
│ ├── types
│ └── utils
└── service
├── assets
├── pages
├── routes.ts
├── store
└── types
複製代碼
require context 是 webpack 提供的特有的模塊方法,並非語言標準,因此在 vite 中再也不能使用 require context。但若是徹底改成開發者手動 import 模塊,一來是對已有代碼改動容易產生模塊導入的遺漏;二來是放棄了這種「靈活」的機制,對後續的開發模式也會有必定改變。但好在 vite2.0 提供了 glob 模式的模塊導入。該功能能夠實現上述目標。固然,會須要作必定的代碼改動:
const routesModules = import.meta.globEager<{default: unknown[]}>('./modules/**/routes.ts');
const routes = Object
.keys(routesModules)
.reduce<any[]>((pre, k) => [...pre, ...routesMod[k].default], []);
export default new VueRouter({ routes });
複製代碼
主要就是將 require.context
改成 import.meta.globEager
,同時適配返回值類型。固然,爲了支持 types,能夠爲 ImportMeta 接口添加一些類型:
declare global {
interface ImportMeta {
env: Record<string, unknown>;
+ globEager<T = unknown>(globPath: string): Record<string, T>;
}
}
複製代碼
此外再提一下,import.meta.globEager
會在構建時作靜態分析將代碼替換爲靜態 import 語句。若是但願能支持 dynamic import,請使用 import.meta.glob
方法。
vite2.0 本地開發時(DEV 模式)仍然提供了一個 HTTP server,同時也支持經過 proxy 項設置代理。其背後和 webpack 同樣也是使用了 http-proxy,所以針對 vue-cli 的 proxy 設置能夠遷移到 vite 中:
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import { createVuePlugin } from 'vite-plugin-vue2';
+ import proxy from './src/tangram/proxy-table';
export default defineConfig({
plugins: [
tsconfigPaths(),
createVuePlugin(),
],
+ server: {
+ proxy,
+ }
});
複製代碼
在基於 vue-cli 中咱們能夠利用 webpack 的 HtmlWebpackPlugin 來實現 HTML 中值的替換,例如 <%= htmlWebpackPlugin.options.title %>
這種形式來將該處模板變量在編譯時,替換爲實際的 title 值。要實現這樣的功能也很是簡單,例如 vite-plugin-html 。這個插件基於 ejs 來實現模板變量注入,經過 transformIndexHtml
鉤子,接收原始 HTML 字符串,而後經過 ejs 渲染注入的變量後返回。
下面是遷移後,使用 vite-plugin-html 的配置方式:
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import { createVuePlugin } from 'vite-plugin-vue2';
+ import { injectHtml } from 'vite-plugin-html';
export default defineConfig({
plugins: [
tsconfigPaths(),
createVuePlugin(),
+ injectHtml({
+ injectData: {
+ title: '用戶管理系統',
+ },
}),
],
server: {
proxy,
},
});
複製代碼
對應的需求修改一下 HTML 的模板變量寫法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
- <title><%= htmlWebpackPlugin.options.title %></title>
+ <title><%= title %></title>
</head>
<body>
<noscript>
- We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
+ We're sorry but <%= title %> doesn't work properly without JavaScript enabled.
</noscript>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
複製代碼
在項目背景介紹上有提到該項目對兼容性要求很低,因此這塊在遷移中實際並未涉及。
固然,若是對兼容性有要求的項目,可使用 @vitejs/plugin-legacy 插件。該插件會打包出兩套代碼,一套面向新式瀏覽器,另外一套則包含各種 polyfill 和語法兼容來面向老式瀏覽器。同時在 HTML 中使用 module/nomodule 技術來實現新/老瀏覽器中的「條件加載」。
該項目包含 1897 個模塊文件(包括 node_modules 中模塊),遷移先後的構建(無緩存)耗時以下:
vue-cli | vite 2 | |
---|---|---|
dev 模式 | ~8s | ~400ms |
prod 模式 | ~42s | ~36s |
能夠看到,在 DEV 模式下 vite2 構建效率的提高很是明顯,這也是由於其在 DEV 模式下只作一些輕量級模塊文件處理,不會作較重的打包工做,而在生產模式下,因爲仍然須要使用 esbuild 和 rollup 作構建,因此在該項目中效率提高並不明顯。
以上就是筆者在作 vue-cli 遷移 vite 2.0 時,遇到的一些問題。都是一些比較小的點,總體遷移上並未遇到太大的阻礙,用了不到半天時間就遷移了。固然,這也有賴於近年來 JavaScript、HTML 等標準化工做使得咱們寫的主流代碼也可以具有必定的統一性。這也是這些前端工具讓咱們「面向將來」編程帶來的一大優勢。但願這篇文章可以給,準備嘗試遷移到 vite 2.0 的各位朋友一些參考。