好久没写文章了,今天终于有时间可以安安静静写文章了。

前言

Vite相信大家都用过,它是一种新型前端开发与构建工具,能够显著提升前端开发体验。我们在搭建Vite项目,选择Vue模板之后,默认会下载Vue3模板。如果你的公司现在还没有准备使用Vue3,而在使用Vue2,那么这篇文章值得你继续看下去。下面,我将带大家如何搭建一个 ​Vite+Vue2+Composition-api+​​<script setup>​​+TypeScript​ 搭配使用的项目。这篇文章很干,请大家点点赞哦!

安装所需依赖

又到了实战环节,下面可以一步步跟着我哦! 我这里使用的是​​yarn​​ 依赖管理工具。

初始化项目

这里使用快捷初始化命令:

yarn init -y

创建完​​package.json​​​文件之后,我们可以手动修改下项目名称字段​​name​​:​vitevue2p​。

初始化Vite

安装Vite。

yarn add vite -D

初始化Vue2

我们需要安装Vue2,所以直接这样安装。

yarn add vue

目前,我安装的版本是​​^2.6.14​​。

另外,我们还需要安装​​vue-template-compiler​​这个依赖,此包可用于将Vue 2.0模板预编译为渲染函数,以避免运行时编译开销和CSP限制。在编写具有非常特定需求的构建工具时,才需要单独使用它。所以,我们这里单独安装。

yarn add vue-template-compiler -D

最后,如果想让Vite支持Vue2,就必须安装这个依赖​​vite-plugin-vue2​​。

yarn add vite-plugin-vue2 -D

支持Composition-api

​Composition-api​​​字面意思是组合API,它是为了实现基于函数的逻辑复用机制而产生的。这也是Vue3亮点之一,那么我们如何才能够在Vue2项目中使用呢?这需要安装​​@vue/composition-api​​依赖。

yarn add @vue/composition-api

支持​​<script setup>​​语法

​<script setup>​​是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖,是Vue3.2新加入的语法。那么,我们也可以在Vue2项目中使用它。

你需要安装​​unplugin-vue2-script-setup​​依赖。

yarn add unplugin-vue2-script-setup -D

了解更多,可以查看​​https://github.com/antfu/unplugin-vue2-script-setup​​。

在Vue2项目中使用Volar

以下是官方的解释:


我们建议将 VS Code 与 Volar 结合使用以获得最佳体验(如果您拥有 Vetur,您可能希望禁用它)。 使用 Volar 时,您需要安装 @vue/runtime-dom 作为 devDependencies 以使其在 Vue 2 上工作。


yarn add @vue/runtime-dom -D

支持TypeScript语法

随着应用的增长,静态类型系统可以帮助防止许多潜在的运行时错误,所以我们推荐使用TypeScript。

yarn add typescript -D

最后,我把安装的所有依赖列出来,可以参照有没有漏的。

"dependencies": {
"@vue/composition-api": "^1.1.5",
"vue": "^2.6.14"
},
"devDependencies": {
"@vue/runtime-dom": "^3.2.11",
"typescript": "^4.4.3",
"unplugin-vue2-script-setup": "^0.6.4",
"vite": "^2.5.7",
"vite-plugin-vue2": "^1.8.1",
"vue-template-compiler": "^2.6.14"
}

搭建项目架构

首先,我先列出我自己搭建的项目文件目录,我是参照Vite默认模板而创建的文件目录。

- public
-- favicon.ico
- src
-- assets
--- logo.png
-- components
--- Async.vue
--- Bar.vue
--- Foo.vue
--- HelloWorld.vue
-- App.vue
-- main.ts
-- shims-vue.d.ts
- index.html
- package.json
- ref-macros.d.ts
- tsconfig.json
- vite.config.ts

下面,我们按排列顺序分别看下文件中都放了什么东西?

​public​​​文件夹中放着一个ico图标文件,这个不再说明。​​src​​文件夹中文件有点多,我们放在最后讨论。

index.html

谈到​​index.html​​这个文件,我们需要引入Vite官网一段话:


你可能已经注意到,在一个 Vite 项目中,​​index.html​​​ 在项目最外层而不是在 ​​public​​​ 文件夹内。这是有意而为之的:在开发期间 Vite 是一个服务器,而 ​​index.html​​ 是该 Vite 项目的入口文件。

Vite 将 ​​index.html​​​ 视为源码和模块图的一部分。Vite 解析 ​​<script type="module" src="...">​​​ ,这个标签指向你的 JavaScript 源码。甚至内联引入 JavaScript 的 ​​<script type="module">​​​ 和引用 CSS 的 ​​<link href>​​​ 也能利用 Vite 特有的功能被解析。另外,​​index.html​​​ 中的 URL 将被自动转换,因此不再需要 ​​%PUBLIC_URL%​​ 占位符了。


<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>

<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>

</html>

package.json

这个文件定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。这里,需要注意的是我们自定义了​​"scripts"​​​字段,有三个命令:​​"vite --open"​​​、​​"vite preview"​​​、​​"vite build"​​。

{
"name": "vitevue2p",
"version": "0.1.1",
"description": "",
"keywords": [],
"license": "MIT",
"main": "dist/index.js",
"module": "dist/index.mjs",
"scripts": {
"dev": "vite --open",
"serve": "vite preview",
"build": "vite build"
},
"dependencies": {
"@vue/composition-api": "^1.1.5",
"vue": "^2.6.14"
},
"devDependencies": {
"@vue/runtime-dom": "3.2.11",
"typescript": "^4.4.3",
"unplugin-vue2-script-setup": "^0.6.4",
"vite": "^2.5.7",
"vite-plugin-vue2": "^1.8.1",
"vue-template-compiler": "^2.6.14"
}
}

ref-macros.d.ts

以​​d.ts​​​后缀结尾的是TypeScript中的类型定义文件。我们知道自从引入 Composition API 以来,一个主要未解决的问题是 ​​refs​​​ 与​​reactive​​​的使用,到处使用 ​​.value​​​可能很麻烦,如果不使用类型系统,很容易错过。 一些用户特别倾向于只使用​​reactive​​​,这样他们就不必处理​​refs​​。

为了优化,官方提出了一个​RFC​,大家可以打开下面这个网址 ​​https://github.com/vuejs/rfcs/discussions/369​​ 了解一下。

下面,可以看下一个简单的例子。

// declaring a reactive variable backed by an underlying ref
let count = $ref(1)

// no need for .value anymore!
console.log(count) // 1

function inc() {
// assignments are reactive
count++
}

另外,这是一项实验性功能。实验性功能可能会改变补丁版本之间的行为。建议将您的 vue 依赖项固定到确切的版本以避免损坏。

言归正传,我们来看下​​ref-macros.d.ts​​文件中的内容。

import type {
Ref,
UnwrapRef,
ComputedRef,
WritableComputedOptions,
WritableComputedRef,
ShallowUnwrapRef,
} from '@vue/composition-api'

declare const RefMarker: unique symbol
type RefValue<T> = T & { [RefMarker]?: any }

declare const ComputedRefMarker: unique symbol
type ComputedRefValue<T> = T & { [ComputedRefMarker]?: any }

declare const WritableComputedRefMarker: unique symbol
type WritableComputedRefValue<T> = T & { [WritableComputedRefMarker]?: any }

type ToRawRefs<T extends object> = {
[K in keyof T]: T[K] extends ComputedRefValue<infer V>
? ComputedRefValue<V>
: T[K] extends WritableComputedRefValue<infer V>
? WritableComputedRef<V>
: T[K] extends RefValue<infer V>
? Ref<V>
: T[K] extends object
? T[K] extends
| Function
| Map<any, any>
| Set<any>
| WeakMap<any, any>
| WeakSet<any>
? T[K]
: ToRawRefs<T[K]>
: T[K];
}

/**
* Vue ref transform macro for binding refs as reactive variables.
*/
declare function _$<T>(arg: ComputedRef<T>): ComputedRefValue<T>
declare function _$<T>(
arg: WritableComputedRef<T>
): WritableComputedRefValue<T>
declare function _$<T>(arg: Ref<T>): RefValue<T>
declare function _$<T extends object>(arg?: T): ShallowUnwrapRef<T>

/**
* Vue ref transform macro for accessing underlying refs of reactive varaibles.
*/
declare function _$$<T>(value: T): ComputedRef<T>
declare function _$$<T>(
value: WritableComputedRefValue<T>
): WritableComputedRef<T>
declare function _$$<T>(value: RefValue<T>): Ref<T>
declare function _$$<T extends object>(arg: T): ToRawRefs<T>

declare function _$ref<T>(arg?: T | Ref<T>): RefValue<UnwrapRef<T>>

declare function _$shallowRef<T>(arg?: T): RefValue<T>

declare function _$computed<T>(
getter: () => T,
// debuggerOptions?: DebuggerOptions
): ComputedRefValue<T>
declare function _$computed<T>(
options: WritableComputedOptions<T>,
// debuggerOptions?: DebuggerOptions
): WritableComputedRefValue<T>

declare global {
const $: typeof _$
const $$: typeof _$$
const $ref: typeof _$ref
const $shallowRef: typeof _$shallowRef
const $computed: typeof _$computed
}

tsconfig.json

​tsconfig.json​​文件中指定了用来编译这个项目的根文件和编译选项。

我们这里需要注意如果您的 IDE 缺少全局类型。

{
"compilerOptions": {
"types": [
"unplugin-vue2-script-setup/types"
]
}
}

Volar 优先支持 Vue 3。Vue 3 和 Vue 2 模板有些不同。您需要设置 ExperimentCompatMode 选项以支持 Vue 2 模板。

{
"compilerOptions": {
...
},
"vueCompilerOptions": {
"experimentalCompatMode": 2
},
}

最后,文件内容如下:

{
"compilerOptions": {
"target": "es2017",
"module": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"strictNullChecks": true,
"resolveJsonModule": true,
"types": [
"unplugin-vue2-script-setup/types"
]
},
"vueCompilerOptions": {
"experimentalCompatMode": 2
}
}

vite.config.ts

这个文件是Vite的配置文件。当以命令行方式运行 ​​vite​​​ 时,Vite 会自动解析项目根目录下名为 ​​vite.config.js​​​(或​​vite.config.ts​​) 的文件。

这里需要注意 ​​refTransform​​​ 现在是插件根级选项,需要手动定义为​​true​​​。(为什么配置​​refTransform​​​,可以看上面​​ref-macros.d.ts​​​文件中对refs处理,不使用​​.value​​的介绍)。

另外,如果想支持​​<script setup>​​语法,必须在这里以插件的形式配置。

import { defineConfig } from 'vite'
import { createVuePlugin as Vue2 } from 'vite-plugin-vue2'
import ScriptSetup from 'unplugin-vue2-script-setup/vite'

export default defineConfig({
plugins: [
Vue2(),
ScriptSetup({
refTransform: true,
}),
],
})

介绍完这些文件,剩下的就是​src​文件夹中的文件了,因为文件过多,我们把它单独放在​Src文件夹​栏目中。

Src文件夹

​assets​​​文件中只有​​logo.png​​一个图片,你可以把静态文件放在当中,这里不多过介绍。

main.ts

这是Vue2的入口文件,我们可以看到这里​​VueCompositionAPI​​​被当做插件引入。另外,我们引入的​​App.vue​​​以及其他​​*.vue​​​为后缀的文件,需要有专门的类型定义文件进行声明,在下面的​​shims-vue.d.ts​​文件中我们会讲到。

import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
import App from './App.vue'

Vue.use(VueCompositionAPI)

const app = new Vue({ render: h => h(App) })
app.$mount('#app')

shims-vue.d.ts

declare module '*.vue' {
import Vue from 'vue'
export default Vue
}

App.vue

这个文件是页面入口文件。我们来看下它是如何写的,这是Vue2项目,但是写法与Vue3项目无异,只不过在Vue2项目中需要​​'@vue/composition-api'​​​使用​​Composition-api​​​,而Vue3项目直接引入​​vue​​。

另外,这里看到我们直接使用​​<script setup>​​​语法,替换了之前​​setup()​​方法,使代码更简洁。还有我们可以直接引入组件,直接在模板中使用。

更多关于​​<script setup>​​​语法的内容可以看看​​https://v3.cn.vuejs.org/api/sfc-script-setup.html​​,了解更多使用方法。

<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<hello-world name="Vue 2 + TypeScript + Vite + Composition-api" @update="onUpdate" />
<async-component />
</div>
</template>

<script setup lang="ts">
import { defineAsyncComponent } from '@vue/composition-api'

import HelloWorld from './components/HelloWorld.vue'

const AsyncComponent = defineAsyncComponent(() => import('./components/Async.vue'))

function onUpdate(e: any) {
console.log(e)
}
</script>
<script lang="ts">
export default {
name: 'App',
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

HelloWorld.vue

然后,我们再看下这个文件中什么内容。这里需要注意的是​​$ref()​​​、​​$computed()​​​方法,这就是之前提到的​​refTransform​​​语法,不得不说,这比以前使用​​.value​​处理方便多了。

<template>
<div>
<h1>{{ msg }}, {{ name }}</h1>
<button @click="inc">
Inc
</button>
<div>{{ count }} x 2 = {{ doubled }}</div>
<button @click="dec()" v-html="decText" />
<component :is="count > 2 ? Foo : Bar" />
</div>
</template>

<script setup lang="ts">
import { watch } from '@vue/composition-api'
import Foo from './Foo.vue'
import Bar from './Bar.vue'

const props = withDefaults(defineProps<{ msg: string; name: string | number }>(), { msg: 'Hello' })
const emit = defineEmits(['update'])

let count = $ref(1)
// eslint-disable-next-line prefer-const
let doubled = $computed(() => count * 2)

function inc() {
count += 1
}
function dec() {
count -= 1
}

const decText = '<b>Dec</b>'

watch(()=>count, value => emit('update', value))
</script>
<style scoped>
button{
margin: 20px 0;
}
</style>

其他文件就不过多介绍了,就只是简单的模板文件。

Foo.vue

<template>
<div>Foo</div>
</template>

Bar.vue

<template>
<div>Bar</div>
</template>

Async.vue

<template>
<div>Async Component</div>
</template>

结语

最后,我们启动下项目。

yarn dev

Vite+Vue2+Composition-api+<script setup>+TypeScript搭配如何开发项目?_typescript

如上图所示,启动成功。

相信这样可以在一定程度上提升你 Vue 2 的开发体验,赶快来!

以下是本篇文章的源码地址:

https://github.com/maomincoding/viteVue2p

如果觉得这篇文章对你有帮助,感谢点赞哦~

另外,我有一个公众号:​前端历劫之路​,如果有缘,可以关注一下哦~