<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          vue-cli 遷移 vite2 實踐小結(jié)

          共 12348字,需瀏覽 25分鐘

           ·

          2021-03-27 03:03

          (給前端大學加星標,提升前端技能.

          作者:AlienZHOU

          https://juejin.cn/post/6934316962952544269

          vite2.0 發(fā)布了,作為使用了瀏覽器原生 ESM 為下一代前端工具,vite 2.0 相較于 1.0 更加成熟。在此之前筆者就開始關(guān)注這類「新型」的前端工具。這次趁著 vite 2.0 發(fā)布,也成功將一個基于 vue-cli(-service) + vue2 的已有項目進行了遷移。

          遷移工作比較順利,花了不到半天時間。但整個遷移過程中也遇到了一些小問題,這里匯總一下,也方便遇到類似問題的朋友一起交流和參考。

          項目背景

          在介紹具體遷移工作前,先簡單介紹下項目情況。目前該項目上線不到一年,不太有構(gòu)建相關(guān)的歷史遺留債務(wù)。項目包含 1897 個模塊文件(包括 node_modules 中模塊),使用了 vue2 + vuex + typescript 的技術(shù)棧,構(gòu)建工具使用的是 vue-cli(webpack)。算是一套比較標準的 vue 技術(shù)棧。由于是內(nèi)部系統(tǒng),項目對兼容性的要求較低,用戶基本都使用較新的 Chrome 瀏覽器(少部分使用 Safari)。

          遷移工作

          下面具體來說下遷移中都做了哪些處理。

          1、配置文件

          首先需要安裝 vite 并創(chuàng)建 vite 的配置文件。

          npm i -D vite

          vue-cli-service 中使用 vue.config.js 作為配置文件;而 vite 則默認會需要創(chuàng)建一個 vite.config.ts 來作為配置文件。基礎(chǔ)的配置文件很簡單:

          import { defineConfig } from 'vite';

          export default defineConfig({
            plugins: [
              // ...
            ],
          })

          創(chuàng)建該配置文件,之前的 vue.config.js 就不再使用了。

          2、入口與 HTML 文件

          在 vite 中也需要指定入口文件。但和 webpack 不同,在 vite 中不是指定 js/ts 作為入口,而是指定實際的 HTML 文件作為入口。

          在 webpack 中,用戶通過將 entry 設(shè)置為入口 js(例如 src/app.js)來指定 js 打包的入口文件,輔以 HtmlWebpackPlugin 將生成的 js 文件路徑注入到 HTML 中。而 vite 直接使用 HTML 文件,它會解析 HTML 中的 script 標簽來找到入口的 js 文件。

          因此,我們在入口 HTML 中加入對 js/ts 文件的 script 標簽引用:

          <!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 模式啟動時,它其實啟動了一個類似靜態(tài)服務(wù)器的 server,將 serve 源碼目錄,因此不需要像 webpack 那樣的復雜模塊打包過程。模塊的依賴加載將完全依托于瀏覽器中對 import 語法的處理,因此可以看到很長一串的腳本加載瀑布流:

          這里還需要注意 project root 的設(shè)置(https://vitejs.dev/config/#root)。在默認是 process.cwd(),而 index.html 也會在 project root 下進行尋找。為了方便我將 ./public/index.html 移到了 ./index.html

          3、使用 vue 插件

          vite 2.0 提供了對 vue 項目的良好支持,但其本身并不和 vue 進行較強耦合,因此通過插件的形式來支持對 vue 技術(shù)棧的項目進行構(gòu)建。vite 2.0 官網(wǎng)目前(2021.2.28)推薦的 vue 插件會和 vue3 的 SFC(https://vitejs.dev/plugins/#vitejs-plugin-vue) 一起使用更好。因此這里使用了一個專門用來支持 vue2 的插件 vite-plugin-vue2(https://www.npmjs.com/package/vite-plugin-vue2),支持 JSX,同時目前最新版本也是支持 vite2 的(https://github.com/underfin/vite-plugin-vue2/pull/13)。

          使用上也很簡單:

          import { defineConfig } from 'vite';
          + import { createVuePlugin } from 'vite-plugin-vue2';

          export default defineConfig({
            plugins: [
          +   createVuePlugin(),
            ],
          });

          4、處理 typescript 路徑映射

          使用 vite 構(gòu)建 ts 項目時,如果使用了 typescript 路徑映射(https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping)的功能,就需要進行特殊處理,否則會出現(xiàn)模塊無法解析(找不到)的錯誤:

          這里需要使用 vite-tsconfig-paths(https://github.com/aleclarson/vite-tsconfig-paths) 這個插件來做路徑映射的解析替換。其原理較為簡單,大致就是 vite 插件的 resolveId 鉤子(https://vitejs.dev/guide/api-plugin.html#universal-hooks)階段,利用 tsconfig-paths(https://www.npmjs.com/package/tsconfig-paths) 這個庫來將路徑映射解析為實際映射返回。有興趣的可以看下該插件的實現(xiàn),比較簡短。

          具體使用方式如下:

          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(),
            ],
          });

          5、替換 CommonJS

          vite 使用 ESM 作為模塊化方案,因此不支持使用 require 方式來導入模塊。否則在運行時會報 Uncaught ReferenceError: require is not defined 的錯誤(瀏覽器并不支持 CJS,自然沒有 require 方法注入)。

          此外,也可能會遇到 ESM 和 CJS 的兼容問題。當然這并不是 vite 構(gòu)建所導致的問題,但需要注意這一點。簡單來說就是 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';

          6、環(huán)境變量的使用方式

          使用 vue-cli(webpack)時我們經(jīng)常會利用環(huán)境變量來做運行時的代碼判斷,例如:

          const REPORTER_HOST = process.env.REPORTER_TYPE === 'mock'
            ? 'http://mock-report.xxx.com'
            : 'http://report.xxx.com';

          vite 仍然支持環(huán)境變量(https://vitejs.dev/guide/env-and-mode.html#env-variables)的使用,但不再提供 process.env 這樣的訪問方式。而是需要通過 import.meta.env(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import.meta) 來訪問環(huán)境變量:

          -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 也內(nèi)置了一些環(huán)境變量(https://vitejs.dev/guide/env-and-mode.html#env-variables),可以直接使用。

          7、import.meta.env types

          補充:vite 提供了它所需要的 types 定義,可以直接應(yīng)用 vite/client(https://github.com/vitejs/vite/blob/v2.0.3/packages/vite/client.d.ts) 來引入,可以不需要通過以下方式來自己添加。

          如果在 typescript 中通過 import.meta.env(https://github.com/microsoft/TypeScript/issues/22861) 來訪問環(huán)境變量,可能會有一個 ts 錯誤提示:類型“ImportMeta”上不存在屬性“env”

          這是因為在目前版本下(v4.2.2)import.meta 的定義還是一個空的 interface(https://github.com/microsoft/TypeScript/blob/v4.2.2/lib/lib.es5.d.ts#L608-L615):

          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>;
            }
          }

          這樣就不會報錯了。

          8、webpack require context

          在 webpack 中我們可以通過 require.context 方法「動態(tài)」解析模塊。比較常用的一個做法就是指定某個目錄,通過正則匹配等方式加載某些模塊,這樣在后續(xù)增加新的模塊后,可以起到「動態(tài)自動導入」的效果。

          例如在項目中,我們動態(tài)匹配 modules 文件夾下的 route.ts 文件,在全局的 vue-router 中設(shè)置 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 });

          文件結(jié)構(gòu)如下:

          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。但如果完全改為開發(fā)者手動 import 模塊,一來是對已有代碼改動容易產(chǎn)生模塊導入的遺漏;二來是放棄了這種「靈活」的機制,對后續(xù)的開發(fā)模式也會有一定改變。但好在 vite2.0 提供了 glob 模式的模塊導入(https://vitejs.dev/guide/features.html#glob-import)。該功能可以實現(xiàn)上述目標。當然,會需要做一定的代碼改動:

          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 會在構(gòu)建時做靜態(tài)分析將代碼替換為靜態(tài) import 語句。如果希望能支持 dynamic import,請使用 import.meta.glob 方法。

          9、API 代理

          vite2.0 本地開發(fā)時(DEV 模式)仍然提供了一個 HTTP server,同時也支持通過 proxy 項設(shè)置代理。其背后和 webpack 一樣也是使用了 http-proxy,因此針對 vue-cli 的 proxy 設(shè)置可以遷移到 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,
          + }
          });

          10、HTML 內(nèi)容插入

          在基于 vue-cli 中我們可以利用 webpack 的 HtmlWebpackPlugin 來實現(xiàn) HTML 中值的替換,例如 <%= htmlWebpackPlugin.options.title %> 這種形式來將該處模板變量在編譯時,替換為實際的 title 值。要實現(xiàn)這樣的功能也非常簡單,例如 vite-plugin-html 。這個插件基于 ejs 來實現(xiàn)模板變量注入,通過 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: '用戶管理系統(tǒng)',
          +     },
              }),
            ],
            server: {
              proxy,
            },
          });

          對應(yīng)的需求修改一下 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>

          11、兼容性處理

          在項目背景介紹上有提到該項目對兼容性要求很低,所以這塊在遷移中實際并未涉及。

          當然,如果對兼容性有要求的項目,可以使用 @vitejs/plugin-legacy(https://github.com/vitejs/vite/tree/main/packages/plugin-legacy) 插件。該插件會打包出兩套代碼,一套面向新式瀏覽器,另一套則包含各類 polyfill 和語法兼容來面向老式瀏覽器。同時在 HTML 中使用 module/nomodule 技術(shù)(https://philipwalton.com/articles/using-native-javascript-modules-in-production-today/)來實現(xiàn)新/老瀏覽器中的「條件加載」。

          總結(jié)

          該項目包含 1897 個模塊文件(包括 node_modules 中模塊),遷移前后的構(gòu)建(無緩存)耗時如下:


          vue-clivite 2
          dev 模式~8s~400ms
          prod 模式~42s~36s

          可以看到,在 DEV 模式下 vite2 構(gòu)建效率的提升非常明顯,這也是因為其在 DEV 模式下只做一些輕量級模塊文件處理,不會做較重的打包工作,而在生產(chǎn)模式下,由于仍然需要使用 esbuild 和 rollup 做構(gòu)建,所以在該項目中效率提升并不明顯。


          以上就是筆者在做 vue-cli 遷移 vite 2.0 時,遇到的一些問題。都是一些比較小的點,整體遷移上并未遇到太大的阻礙,用了不到半天時間就遷移了。當然,這也有賴于近年來 JavaScript、HTML 等標準化工作使得我們寫的主流代碼也能夠具備一定的統(tǒng)一性。這也是這些前端工具讓我們「面向未來」編程帶來的一大優(yōu)點。希望這篇文章能夠給準備嘗試遷移到 vite 2.0 的各位朋友一些參考。

          點贊和在看就是最大的支持??

          瀏覽 33
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久成人大香蕉 | 国产老妈操逼 | 色婷亚洲五月天 | 色播婷婷丁香五月 | 免费观看一级黄片 |