Vite2 实战: React + TS + Mobx 旧项目迁移

3,294 阅读10分钟

前言

一个阳光明媚的午后,我跟老大站在楼梯间里一边抽烟一边看着楼下来来往往的靓男靓女们,惬意无比,忽然他开始跟我抱怨。
老大:我们的项目打包速度太慢了
我满不在乎的吐了一口烟:嫌慢,你用 vite 啊。
老大:嗯,你看了?
我:嗯,我觉得很厉害,速度确实很快,可以尝试一下(说完之后我就想抽自己,又话多)
老大:嗯,那你研究一下吧
我默默的吸了口烟,在心里狠狠的抽了自己一个耳光(没舍得真动手,我怕疼)。于是我十分开心的开始了我的 vite 爬坑之旅

339471617004691_.pic.jpg

Vite是什么

尤大是这样对Vite定义的:Vite,一个基于浏览器原生 ES imports 的开发服务器。利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。同时不仅有 Vue 文件支持,还搞定了热更新,而且热更新的速度不会随着模块增多而变慢。针对生产环境则可以把同一份代码用 rollup 打包。简而言之,vite 是一个基于浏览器 ES Modules 规范的开发服务器,无须打包,无关框架,生产环境利用 rollup 进行打包。

新建一个项目

尤大很了解我们这群懒人,因此贴心的为我们准备了模板,只需按照文档上的提示操作就可以了。Vite官网

# npm 6.x
npm init @vitejs/app my-app --template react-ts
# npm 7+, 需要额外的双横线:
npm init @vitejs/app my-app -- --template react-ts
# yarn
yarn create @vitejs/app my-app --template react-ts

初始化成功后,我们就会得到一个文件结构如下的项目

├── package-lock.json
├── package.json
├── src
│   ├── App.css
│   ├── App.tsx
│   ├── favicon.svg
│   ├── index.css
│   ├── index.tsx
│   └── logo.svg
├── tsconfig.json
└── vite.config.ts

接下里,我们安装上依赖,启动项目看一眼,这熟悉的感觉。

image.png 恭喜你,到这里为止,你已经成功的使用 vite 构建了一个 react+ts 的项目了,看起来一切顺利,是不是已经磨拳擦掌准备抄起键盘就开干了?年轻。

image.png

开始爬坑

mobx 版本依赖问题

我们一直使用 mobx 来做状态管理工具,在要迁移的旧项目中使用的是 mobx5 的语法,所以为了后期迁移方便,我安装了跟旧项目中同样版本号的 mobx 来做测试。有趣的是,mobx 5.0.4 这个版本在 vite 的编译过程中被 vite 检查出 mobx 的依赖源码中有一处语法错误。将 mobx 升级至 5.15.7 后,错误解决。

image.png 这让我有些疑惑,好像 vite 并不尊重我的 tsconfig 配置,理论上来说当我在 tsconfig 配置文件中 exclude 掉了 node_modules ,vite 在编译的时候就不会去检查 node_modules。我开始有点怀疑是 vite 在 dev 环境中,并没有去读 tsconfig.json 中的设置。在 webpack 中我们通常会使用 awesome-typescript-loader 来读取标准的 tsconfig.json 配置文件。vite 中没有怎么办呢?别担心我们可以使用 @rollup/plugin-typescript 这个插件来帮我们读取 tsconfig.json。
首先安装依赖

npm install @rollup/plugin-typescript 

npm install tslib

依赖安装完成后,对 vite.config 中的 plugins 模块进行如下配置。 image.png 再次运行项目后,发现依旧没有绕过对 mobx 依赖的检查(内心崩溃,直接注释了插件,怀疑尤大,我怎么敢的啊!),头发都快抓成鸟窝了,有懂的兄弟麻烦给解释下。

styled-components

当我在一个组件中,使用 styled—components 定义了一个根容器,并且使用 mobx @observer 了这个组件后,vite 给我报了一个错误:[plugin:vite:esbuild] Transform failed with 1 error: Unexpected "@"。

image.png image.png mobx 装饰器错误这很常见,一般的错误原因都是因为我们没有在 tsconfig 中设置 "experimentalDecorators": true 刚爬出 mobx 坑的我很确定我的 tsconfig 写了这条配置,所以这一定不是 mobx 的锅。于是我开始逐行注释,终于,当我将 styled—components 定义的根容器换成普通 div 标签后,错误消失了。内心复杂,一脸懵逼,总不能让我放弃 styled—components 吧,那我还怎么迁移旧项目。

image.png 忽然我又开始狗胆包天的怀疑起尤大,是不是 vite 真的没有完全的读到我的 tsconfig 配置,所以我又将 @rollup/plugin-typescript 的注释打开,重新运行项目。奇迹就这么发生了,报错消失了,我真是惊了个呆。为了帮尤大洗脱嫌疑,证明是我菜的原因,目前我已经在 GitHub 上给 vite 提 issue 了,坐等回复。

image.png

关于 CSS 预处理器

由于Vite 提供了对 .scss, .sass, .less, .styl 和 .stylus 文件的内置支持。没有必要为他们安装特定的 vite 插件,只要npm i 预处理器就可以开箱即用,直呼真香!

image.png

image.png

image.png

image.png 与此同时,vite还为我们提供了 PostCss,CSS Modules 等一系列功能的开箱即用,想要了解的各位看官可以移步Vite官方文档

引入 antd

antd 目前并没有出对于在 vite 中如何配置的相关文档,所以咱们就自己瞎琢磨一下吧,所幸有掘金上各位大佬的文章在前,这个过程并不麻烦。

基本引用

啥也别说,npm 先

npm i antd --save
npm i less@3.13.1 --save
less 文件中引用 theme.less

在新建的 less 文件中,我们要引入 antd的theme.less。这里有一个额外的小注意点。在 less 文件中引入 antd 的 less 文件会有一个 ~ 前置符,这种写法对于 ESM 构建工具是不兼容的,vite 恰好就是一个ESM 构建工具。为了绕过这个问题,我们可以在 vite.config.ts 中添加如下配置

···
resolve: {
        alias: [
            { find: /^~/, replacement: "" },
        ],
    },
···

这样我们就可以在新建的 less 文件中引入 theme.less 了。

less 依赖版本

也许你们看到这里会有疑问,为什么 less 已经升级到了 4.X 版本了,我还在 npm less 的时候特意指定 less 为 3.X 版本。那是因为在最新的 4.x 版本中有一个新的特性(移步 GitHub 查看),我推测就是这个新特性导致了我们在引用antd theme.less 的时候 ts 的类型检查会出错,让 vite 无法编译通过。这让我不得不再一次提上文说的问题,为啥类型检查会检查到 node_modules 里面去,vite 到底有没有读到 tsconfig.json 里面的配置项

image.png 按照这个推测,将 less 降级到 3.X 版本后,问题果然就解决了。

less 按需引入

按照基本使用方法,antd 完全可以正常工作。但是直接引用 antd 的 antd.less 文件,体量过大,对于项目性能优化十分的不友好。antd 提供了在 webpack 中的按需使用方法是要用 babel-plugin-import 来帮助我们完成这个功能,只不过这个过程比较复杂。在 vite 中,我们只需要两步就可以完成这一功能。

第一步 安装所需插件
npm i vite-plugin-imp
第二步 在 vite.config.ts 中添加配置
import vitePluginImp from "vite-plugin-imp";
...
    plugins: [
        // 使用此方法来引入antd的样式文件,可以帮我们做到样式按需加载
        vitePluginImp({
            libList: [
                {
                    libName: "antd",
                    style: (name) => `antd/es/${name}/style`,
                },
            ],
        }),
    ],
...

这里一定要找 es 文件夹下的 style 文件中的 js,lib 文件下的 style 文件中的 js 文件不支持 ESM。你们又要问为什么不用 antd/es/${name}/style/index.less? 因为 antd 中不是所有组件的样式都是以 less 导出的,所以直接找 js 更保险
就这么两步,我们就已经可以对 antd 的样式做到按需加载了,真香!

全局主题定制

antd 的主题定制,在 webpack 中我们可以使用 less-loader 来完成一系列的配置,这个过程相对来说有些麻烦,而在 vite 中则要简单的多。

...
    css: {
        preprocessorOptions: {
            less: {
                // 支持内联 JavaScript
                javascriptEnabled: true,
                // 重写 less 变量,定制样式
                modifyVars: {
                    ...{
                        "primary-color": "red",
                        "border-radius-base": " 4px",
                        "link-color": "red",
                    },
                },
            },
            sass: {
                // 支持内联 JavaScript
                javascriptEnabled: true,
            },
            scss: {
                // 支持内联 JavaScript
            },
        },
    },
...

情不自禁,再次真香。

image.png 当然,如果你想直接使用官方提供的主题,比如暗黑模式,你可以这样做。

import { getThemeVariables } from "antd/dist/theme";
...
    css: {
        preprocessorOptions: {
            less: {
                // 支持内联 JavaScript
                javascriptEnabled: true,
                // 重写 less 变量,定制样式
                modifyVars: {
                    ...getThemeVariables({
                        dark: true,
                   }),
                },
            },
            sass: {
                // 支持内联 JavaScript
                javascriptEnabled: true,
            },
            scss: {
                // 支持内联 JavaScript
            },
        },
    },
...

环境变量

vite 自身是没有 NODE_ENV 的,所以如果我们想区分开发环境跟生产环境的话,就需要我们自己去手写一下。

老样子,先装依赖

npm install --save-dev cross-env

依赖安装完成后,在 package.json 中手写注入

script:{
    "force": "cross-env NODE_ENV=development vite --force",
    "dev": "cross-env NODE_ENV=development vite",
    "build": "tsc && vite build",
    "serve": "vite preview"
}

现在我们就可以区分环境了

多入口配置

vite 为我们提供了多页面应用模式,在开发中,简单地导航或链接到 /nested/ - 将会按预期工作,就如同一个正常的静态文件服务器。在构建中,要做的只有指定多个 .html 文件作为入口点。当然,前提是我们的项目根目录下有一个叫 nested 的文件夹叫啥名字你随意,开心就好,在里面添加新的 html 文件作为入口点。然后在 vite.config.ts 中做如下配置即可

···
 build: {
        ···
        rollupOptions: {
            input: {
                main: path.resolve(__dirname, "index.html"),
                nested: path.resolve(__dirname, "nested/index.html"),
            },
        },
        ···
    },
···

alias别名配置

vite 的别名配置在 resolve 模块中,它提供了两种写法。一种是直接写入一个对象,一种是传入一个 {find,replacement} 的对象数组。
对象写法

···
resolve: {
        alias:{
            "fonts": "./static/fonts"
        },
    },
···

对象数组写法

···
resolve: {
        alias: [
            { find: /^~/, replacement: "" },
            { find: "fonts", replacement: "./static/fonts" },
        ],
    },
···

无痛迁移

吭哧吭哧的从坑里面爬出来,现在就该对老项目开始下手了。我们的老项目是使用 poi 来作构建工具的,使用的是 poi.config.js 来对项目进行配置。因为结构相似,所以迁移到 vite 的过程无比丝滑顺畅,丝毫没有痛感。

安装依赖

npm i vite vite-plugin-imp @vitejs/plugin-react-refresh @rollup/plugin-typescript --save
npm i cross-env --save-dev

指定新入口模板

在根目录下,新建 html 文件,作为项目的入口点

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta http-equiv="Cache-Control" content="max-age=0">
    <meta http-equiv="Cache-Control" content="no-cache">
    <meta http-equiv="Expires" content="0">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta name="viewport"
        content="width=device-width,height=device-height,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1,user-scalable=no">
    <meta name="renderer" content="webkit">
    <title>搬家啦</title>
    <!-- <link rel="stylesheet" href="fonts/iconfont.css"> -->
</head>
<body>
    <div id="app"></div>
    <script type="module" src="/src/index.tsx"></script>
</body>
</html>

tsconfig.json 配置

{
    "include": [
        "./src/**/*"
    ],
    "exclude": [
        "node_modules"
    ],
    "compilerOptions": {
        "types": [
            "vite/client"
        ],
        "noImplicitAny": false,
        "noImplicitThis": true,
        "alwaysStrict": false,
        "strictNullChecks": false,
        "target": "es5",
        "sourceMap": false,
        "jsx": "react",
        "experimentalDecorators": true,
        "allowSyntheticDefaultImports": true,
        "lib": [
            "dom",
            "es6"
        ],
        "baseUrl": "./",
        "module": "es2015",
        "moduleResolution": "node",
        "skipLibCheck": true,
        "noEmit": true
    }
}

package.json 写入脚本

"scripts": {
        "force": "set NODE_ENV=development && vite --force",
        "force-mac": "export NODE_ENV=development && vite --force",
        "dev": "set NODE_ENV=development && vite",
        "dev-mac": "export NODE_ENV=development && vite",
        "build": "tsc && vite build",
        "serve": "vite preview"
    },

新建 vite.config.ts 文件并编写 vite 配置

import typescript from "@rollup/plugin-typescript";
import reactRefresh from "@vitejs/plugin-react-refresh";
import { defineConfig } from "vite";
import vitePluginImp from "vite-plugin-imp";
// https://vitejs.dev/config/
console.log(process.env.NODE_ENV);
let isDebug = process.env.NODE_ENV === "development";
export default defineConfig({
    // 将要用到的插件数组。 https://vite.xilinglaoshi.com/config/#plugins
    plugins: [
        reactRefresh(),
        typescript({
            tsconfig: "./tsconfig.json",
        }),
        // 使用此方法来引入antd的样式文件,可以帮我们做到样式按需加载
        vitePluginImp({
            libList: [
                {
                    libName: "antd",
                    style: (name) => `antd/es/${name}/style`,
                },
            ],
        }),
    ],
    // 作为静态资源服务的文件夹。这个目录中的文件会再开发中被服务于 /,在构建时,会被拷贝到 outDir 根目录,并没有转换,永远只是复制到这里。该值可以是文件系统的绝对路径,也可以是相对于项目根的路径。
    publicDir: "static",
    // 公共基础路径 https://vite.xilinglaoshi.com/config/#base
    base: "./",
    define: {
        __IS_DEBUG__: isDebug,
    },
    // 打包编译配置 https://vite.xilinglaoshi.com/config/#build-options
    build: {
        assetsDir: "assets",
    },
    // alias别名设置
    resolve: {
        alias: [
            { find: /^~/, replacement: "" },
            { find: "fonts", replacement: "./static/fonts" },
        ],
    },
    //  css loader
    css: {
        preprocessorOptions: {
            less: {
                // 支持内联 JavaScript
                javascriptEnabled: true,
                // 重写 less 变量,定制样式
                modifyVars: {
                },
            },
            sass: {
                // 支持内联 JavaScript
                javascriptEnabled: true,
            },
            scss: {
                // 支持内联 JavaScript
                javascriptEnabled: true,
            },
        },
    },
    // proxy代理配置 https://vite.xilinglaoshi.com/config/#server-options
    server: {
        hmr: {
            overlay: false,
        }, // 禁止服务器错误遮罩
        port: 4001,
        proxy: {
            "/apis": {
                target: "http://xx.xx.xx.xxx:xx",
                secure: false,
                changeOrigin: true,
            }
        },
    },
});

至此,我的老项目迁移 vite 工作完成。

结语

首先感谢@清秋大佬在我爬坑过程中提供的帮助,也感谢掘金上那些珠玉在前的 vite 文章帮我对 vite 进行了基础扫盲。vite 绝对是一种新的体验,它的速度无愧于它的名字。至于是否能稳如老狗的投入生产,那就仁者见仁智者见智了,总之我已经付诸实践了,胆子肥的兄弟可以试一下。我的老项目是基于 poi 的,所以迁移的过程并不一定会遇到跟你们一样的问题,如果你们在迁移过程中遇到问题,可以提出来大家一起讨论。
最后,给大家推荐几篇关于 vite 的文章。
Vite 2 + React 实践
【译】下一代前端构建工具 ViteJS 中英双语字幕|技术点评
Vite 2.0 + React + Ant Design 4.0 搭建开发环境

完结·撒花