vue-cli 迁移 vite2

vue-cli 迁移 vite2 小结

最近研究新的前端脚手架,基于vue3的vite工程,先上一张尤大的图镇楼~

204851c7c93a4ab2930ae2fd228676df_tplv-k3u1fbpfcp-zoom-1

创建vite-vue工程

使用 NPM 命令:

 $ npm init @vitejs/app

或者使用 Yarn 命令:

 $ yarn create @vitejs/app

命令执行后,会让我们选择构建哪一种框架的项目。

如果你不想在命令行中做选择,可以指定具体的模板。

$ npm init @vitejs/app my-vue-app --template vue

1.配置文件

vue-cli-service 中使用 vue.config.js 作为配置文件;而 vite 则默认会需要创建一个 vite.config.ts 来作为配置文件。基础的配置文件很简单:

import { defineConfig } from 'vite';
 
export default defineConfig({
  plugins: [
    // 插件...
  ],
})

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.js"></script>
</body>
 
</html>

注意上面 <script type="module" src="/src/main.ts"></script> 这一行,它使用浏览器原生的 ESM 来加载该脚本,/src/main.ts 就是入口 js 的源码位置。在 vite dev 模式启动时,它其实启动了一个类似静态服务器的 server,将 serve 源码目录,因此不需要像 webpack 那样的复杂模块打包过程。模块的依赖加载将完全依托于浏览器中对 import 语法的处理,因此可以看到很长一串的脚本加载瀑布流:

fd2c743556dadc1f1a73f849ea63f29b

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';
+ import vue from "@vitejs/plugin-vue";
 
export default defineConfig({
  plugins: [
+   vue(),    
+   createVuePlugin(),
  ],
});

4.替换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';

5.环境变量的使用方式

使用 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 也内置了一些环境变量,可以直接使用。

6.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 方法。

7.API 代理

vite2.0 本地开发时(DEV 模式)仍然提供了一个 HTTP server,同时也支持通过 proxy 项设置代理。其背后和 webpack 一样也是使用了 http-proxy,因此针对 vue-cli 的 proxy 设置可以迁移到 vite 中:

import { defineConfig } from 'vite';
import { createVuePlugin } from 'vite-plugin-vue2';
+ import proxy from './src/tangram/proxy-table';
 
export default defineConfig({
  plugins: [
    createVuePlugin(),
  ],
+ server: {
+   proxy,
+ }
});

8.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.js"></script>
</body>
 
</html>

9.兼容性处理

在项目背景介绍上有提到该项目对兼容性要求很低,所以这块在迁移中实际并未涉及。

当然,如果对兼容性有要求的项目,可以使用 @vitejs/plugin-legacy 插件。该插件会打包出两套代码,一套面向新式浏览器,另一套则包含各类 polyfill 和语法兼容来面向老式浏览器。同时在 HTML 中使用 module/nomodule 技术来实现新/老浏览器中的「条件加载」。

总结

该项目包含 1897 个模块文件(包括 node_modules 中模块),迁移前后的构建(无缓存)耗时如下:

Vue-cliVite2
dev 模式~8s~400ms
prod 模式~42s~36s

可以看到,在 DEV 模式下 vite2 构建效率的提升非常明显,这也是因为其在 DEV 模式下只做一些轻量级模块文件处理,不会做较重的打包工作,而在生产模式下,由于仍然需要使用 esbuild 和 rollup 做构建,所以在该项目中效率提升并不明显。

参考:

Vite2 + Vue3项目最佳实践

vite官网

vue3官网

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值