使用 Vue 3 + Vite.js 快速实现小型服务前端

2021年9月10日 · 3 years ago

使用 Vue 3 + Vite.js 快速实现小型服务前端

尤雨溪(Evan You)主创的前端框架 Vue.js 首发于 2014 年,距今已经 7 年过去了。我作为一个曾经的前端工程师,虽然代码写得不怎样,但经常还会折腾点小工具什么的。去年我用上 TypeScript 之后感觉给自己的小工具找到了“可维护”之路,于是写了《TypeScript + ExpressJS 快速搭建小工具服务》作为记录,今年我又学了下 Vue.js,从 Vue 2.x 开始试着上手,因为 2.x 流行多年,相关生态和第三方库最全。

但使用 Vue 2.x 的我配合的是臃肿的 webpack,虽然工具链成熟但开发过程总觉得笨重且慢。近来我改用 Vue 3 配合官方的 Vite.js,感受到了轻量级自动化工具带来的闪电般的开发体验,于是把我的播客枫言枫语官网的前端搬到 Vue 3 上来,效果还不错,遂写此文以记之。

一、选择 Vue 3 和 Vite.js

枫言枫语官网非常简单,只有一个 Episodes 列表和一个 About 页面,很适合用来练手新技术。早期我用的是简单的 Express.js 做后端,Pug(Jade)做 HTML 前端引擎,用户访问官网时直接吐一个后端渲染好的 HTML 静态页面。

我练习 Vue 2 的项目还用上了 BootstrapVue 这个库,基本上把所有的 Bootstrap 官方控件都包装成了 Vue Component,引入后直接用就行,非常方便。用上 Vue 3 之后我就自己 import bootstrap 自己写控件逻辑了,好在页面简单这点不成问题。对于这个项目来说,Vue 3 和 2 的区别并未造成大的影响,如果你的项目规模较大可以参考官方迁移文档

在 Vue 2 的项目中,我需要用 webpack 实现:

  1. ts → js
  2. scss → css
  3. html 自动打包
  4. dev server 实现 Hot reload

因为 webpack 的设计目标具有普适性,所以为了实现我这种比较特别的开发环境,我的 webpack 配置文件会比较庞大。如果 dev server 的逻辑涉及在前端用到的数据结构,我还得把 webpack.config.js 也用 ts 来写,才能引入相关的数据类型。

Vite.js 的出现直接解决所有问题,使用 npm init vite@latest 直接构建脚手架:

➜ npm init vite@latest
Need to install the following packages:
  create-vite@latest
Ok to proceed? (y)
✔ Project name: … vite-project
✔ Select a framework: › vue
✔ Select a variant: › vue-ts

如此就完成了使用 ts 开发 Vue 3 项目的脚手架,assets 目录里也支持 scss,非常方便。

Vite 项目的开发预览直接跑 vite 命令即可,在去年的文章中我还是比较多用 Makefile,但现在我更喜欢用 package.json 里的 scripts 了,因为可以直接用 local node modules,无需 npx 前缀。

npm run dev

Dev server 就跑起来了,Vue 文件的任何修改几乎都是秒刷新,非常赞。目前我唯一觉得慢的就是修改 scss 文件,要等十多秒的编译时间,不过 CSS 的修改我可以直接在浏览器里做,然后再复制过来,所以影响不大。

二、引入 Bootstrap 和 Fontwaesome

BootstrapFontawesome 都可以通过 npm 安装。

Bootstrap 的 scss 文件在 style.scss 里通过 @import 引入,Fontawesome 在 main.ts 通过 import 引入。

三、Vue Router History Mode

引入 Vue Router 如果使用 History Mode 需要 Server 端配合。比如我们配置两个路由:

const routes = [
    {
        path: '/',
        name: 'Home',
        component: Home
    },
    {
        path: '/about',
        name: 'About',
        component: About
    }
]

当我们使用 <router-link>直接跳进 /about 页面时, Vue Router 会使用History API 实现页面跳转和浏览器 URL 的修改,并不会真的发起一次 GET /about 的请求。

但是如果用户直接访问了 /about 这个 URL,我们希望他能直接见到 about 页面的 UI,所以我们需要在服务端做一次 fallback,当一个请求不 match 我们 server 端的路由时直接返回 index.html,这样 Vue Router 会自行处理页面渲染。

服务端我用的还是 express.js 所以直接在 Router 配置完之后,末尾加了一个 app.all("*", () => {}) 的处理,简单粗暴。

app.use('/episode', episodeRouter)

app.all("*", (_req, res) => {
    try {
        res.sendFile(path.join(g_publicFolder, 'index.html'));
    } catch (error) {
        res.json({ success: false, message: "Something went wrong" });
    }
});

四、Vite Dev Server 转发请求到 API 后端

Vite 默认用 `http://127.0.0.1:3000` 来 serve Vue 的前端页面,我们对前端文件的任何修改都会自动触发 Vite 的自动编译然后 reload。但是如果我们在前端需要使用 JS 发起 API 请求到后端呢?

一般如果用 axios 来发起一个 GET 请求我们这样写:

const response = await axios.get(`/episode/all`)

如果在浏览器中跑起来这个请求就会发到 Vite server 而不是真正的后端。所以我们还需要在 vite.config.ts 中配置 Dev Server Proxy:

export default defineConfig({
    plugins: [vue()],
    build: {
        rollupOptions: {
            input: resolve(__dirname, 'index.html')
        }
    },
    server: {
        host: '0.0.0.0', // 把服务暴露给内网其他设备,比如同个WiFi下的手机
        proxy: {
            '^/episode/.*': 'http://127.0.0.1:3001', // 所有 episode 的请求都会被转发到 3001 端口,我们在这个端口启动一个后端的 server 就可以实现前后端联调了
        }
    }
})

五、解决 ts 的 this.property does not exist 编译错误

在 Vue 2 里使用 js 的单独的 vue 文件大约是这样的:

Single-file component example (click for code as text)

但是 Vue 3 里使用 ts 的话需要加上 defineComponent() 不然 this 的类型推断就会出错,比如:

export default defineComponent({
    data() {
            return {
                foo: 0,
            }
    },
    methods: {
        bar() {
            console.log(this.foo); // 如果没有 `defineComponent()` 这里会报错
        }
    }
})

六、总结

目前用着感觉 Vue 3 + Vite.js 的组合非常地轻便好用,适合我这种不做大型项目但又需要方便可维护的特性的场景。

从一个 iOS 程序员的角度来看,单文件的 .vue 可以类比为 iOS 的 ViewController 或者 View。当我写一个 About.vue 这样的页面时,他就是一个 View Controller, <script> 是他的 View Model,<template> 包起来的部分就是他的 UI 布局代码。

Vue framework 的整体设计使得复杂的逻辑只能放在 <script> 部分编写,所有的用户行为操作通过事件或函数回调进 methods 里面,再修改自己的 this.data。通过 this.datatemplate 中元素的绑定关系更新 UI。

在 iOS 开发中,如果要实现这样比较纯粹的单项数据流动的写法,我们可以使用类似 RxSwift 的库实现 UI 绑定,然后从 View Controller 中分离 View Model 来达成。但约束就没有 Vue 来得单一和严格。

ts 带来了使用 JS 实现大型项目的可能,比如 Angular.js 和 VS Code,Vue 带来相比 Angular, React 更加轻量的响应式编程体验,Vite.js 也以轻量迅捷的优势用起来比 webpack 舒服得多,以后我的小工具如果需要 Web UI 应该都会用这套方案,简单又优雅。