共计 7420 个字符,预计需要花费 19 分钟才能阅读完成。
两周前(202.02.17),vite2.0 公布了,作为应用了浏览器原生 ESM 为下一代前端工具,vite 2.0 相较于 1.0 更加成熟。在此之前笔者就开始关注这类「新型」的前端工具。这次趁着 vite 2.0 公布,也胜利将一个基于 vue-cli(-service) + vue2 的已有我的项目进行了迁徙。
迁徙工作比较顺利,花了不到半天工夫。但整个迁徙过程中也遇到了一些小问题,这里汇总一下,也不便遇到相似问题的敌人一起交换和参考。
我的项目背景
在介绍具体迁徙工作前,先简略介绍下我的项目状况。目前该我的项目上线不到一年,不太有构建相干的历史遗留债权。我的项目蕴含 1897 个模块文件(包含 node\_modules 中模块),应用了 vue2 + vuex + typescript 的技术栈,构建工具应用的是 vue-cli(webpack)。算是一套比拟规范的 vue 技术栈。因为是外部零碎,我的项目对兼容性的要求较低,用户根本都应用较新的 Chrome 浏览器(少部分应用 Safari)。
迁徙工作
上面具体来说下迁徙中都做了哪些解决。
1、配置文件
首先须要装置 vite 并创立 vite 的配置文件。
npm i -D vite
vue-cli-service 中应用 vue.config.js
作为配置文件;而 vite 则默认会须要创立一个 vite.config.ts
来作为配置文件。根底的配置文件很简略:
import {defineConfig} from 'vite';
export default defineConfig({
plugins: [// ...],
})
创立该配置文件,之前的 vue.config.js 就不再应用了。
2、入口与 HTML 文件
在 vite 中也须要指定入口文件。但和 webpack 不同,在 vite 中不是指定 js/ts 作为入口,而是指定理论的 HTML 文件作为入口。
在 webpack 中,用户通过将 entry 设置为入口 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 模式启动时,它其实启动了一个相似动态服务器的 server,将 serve 源码目录,因而不须要像 webpack 那样的简单模块打包过程。模块的依赖加载将齐全依靠于浏览器中对 import
语法的解决,因而能够看到很长一串的脚本加载瀑布流:
这里还须要留神 project root 的设置。在默认是 process.cwd()
,而 index.html 也会在 project root 下进行寻找。为了不便我将 ./public/index.html
移到了 ./index.html
。
3、应用 vue 插件
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(),
],
});
4、解决 typescript 门路映射
应用 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(),],
});
5、替换 CommonJS
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';
6、环境变量的应用形式
应用 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 也内置了一些环境变量,能够间接应用。
7、import.meta.env
types
如果在 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>;}
}
这样就不会报错了。
8、webpack require context
在 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
办法。
9、API 代理
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,
+ }
});
10、HTML 内容插入
在基于 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>
11、兼容性解决
在我的项目背景介绍上有提到该我的项目对兼容性要求很低,所以这块在迁徙中理论并未波及。
当然,如果对兼容性有要求的我的项目,能够应用 @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 的各位朋友一些参考。