说明
本文记录一下 Vue3 的一个基本的开发框架的搭建。主要在公司开发业务,主体框架不用每次都搭建。时间长了就容易忘记了,当初的框架如何搭建的。文章记录的框架搭建的主要实现功能如题,然后研究一些快捷的操作。例如,ts 定义的空间自动导入等等。
工具介绍
- Vite 是一种新型前端构建工具,能够显著提升前端开发体验。细致的介绍看官网
- Vue3 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。
- TypeScript 是一门静态类型、弱类型的语言。 TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性。 TypeScript 可以编译为 JavaScript,然后运行在浏览器、Node.js 等任何能运行 JavaScript 的环境中。
- Axios 是一个基于 promise 网络请求库,作用于 node.js 和浏览器中。
- Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。详细的介绍看官网
- Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。
Vue Router 和 Vue 的版本有一个范围支持的。前面使用了 Vue3.x 所以 Vue Router 使用 4.x
使用 Vite
Vite 需要 Node.js 版本 14.18+,16+。所以安装之前检查你的 node 版本
1 2 3 4 5 6 7 8 9 10 11
| # npm 6.x npm create vite@latest my-vue-app --template vue
# npm 7+, extra double-dash is needed: npm create vite@latest my-vue-app -- --template vue
# yarn yarn create vite my-vue-app --template vue
# pnpm pnpm create vite my-vue-app --template vue
|
–template 后面的参数有 vanilla
,vanilla-ts
,vue
,vue-ts
,react
,react-ts
,preact
,preact-ts
,lit
,lit-ts
,svelte
,svelte-ts
。查看create-vite以获取每个模板的更多细节。
这里就使用命令
1 2
| npm create vite@latest my-vue-app --template vue-ts # 执行之后安装它给出的提示安装模块和启动
|
到此 Vue3 + Vite + Ts 就很简单的完成了,但是这还只是一部分内容
安装 Axios
安装完成之后,创建一个如下的文件src/assets/js/my-axios.ts
,这里要做一个自己的项目接口配置,可以参考我的,根据个人需求来改动。本文件完成了以下几个需求。
- 在具体页面中通过
api.get
或者api.post
这种方式来调用函数,创建对应的方法。
- 通过请求前拦截和请求后拦截来做一个身份的验证和一个数据加载动画。
- 有时候需要手动上传图片数据,可以对接口请求参数类型是否是
[object FormData]
头部修改字段。
还需要安装@types/node
包,因为文件中使用到了 Timeout 函数,来处理加载动画到操作。
以及安装 element-plus,使用其中的信息组件
@types 是什么?
1 2 3 4
| > 有些包并不是 TypeScript 编写的,自然也不会导出 TypeScript 声明文件。所以在TS项目中使用会报错,TS官方给了2个解决方法。
1. 安装 @types 2. 自己 declare module
|
1 2 3 4
| # 安装@types/node npm install @types/node # 安装element-plus npm install element-plus --save
|
自定义的 axios 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
| var root = "/api";
import axios, { AxiosRequestConfig, AxiosRequestHeaders } from "axios"; import { ElMessage as Message } from "element-plus";
function toType(obj: any): string { return {}.toString .call(obj) .match(/\s([a-zA-Z]+)/)![1] .toLowerCase(); }
function filterNull(o: any) { for (var key in o) { if (o[key] === null) { delete o[key]; } if (toType(o[key]) === "string") { o[key] = o[key].trim(); } else if (toType(o[key]) === "object") { o[key] = filterNull(o[key]); } else if (toType(o[key]) === "array") { o[key] = filterNull(o[key]); } } return o; }
function apiAxios( method: string, url: string, params: null | string | object, success: any, failure: any ) { let contentTypeIsJson = false; if (params && typeof params != "string") { params = filterNull(params); } else contentTypeIsJson = true;
if (params && (method === "GET" || method === "DELETE")) { const arr: Array<string> = []; Object.entries(params).forEach((item) => { arr.push(`${item[0]}=${encodeURIComponent(item[1])}`); }); url = `${url}?${arr.join("&")}`; } axios({ method: method, url: url, data: method === "POST" || method === "PUT" ? params : null, params: method === "GET" || method === "DELETE" ? "" : null, baseURL: root, withCredentials: true, crossDomain: true, transformRequest: [ function (data) { if (contentTypeIsJson) return data; let ret = ""; for (let it in data) { ret += encodeURIComponent(it) + "=" + encodeURIComponent(data[it]) + "&"; } return ret; }, ], headers: { "Content-Type": contentTypeIsJson ? "application/json" : "application/x-www-form-urlencoded", }, } as AxiosRequestConfig<any>) .then(function (res) { let response = res.data; if (response.code == 302) { window.location.href = response.urlToRedirectTo; return; } else if (response.code == 0) { if (success) { success(response); } } else { if (failure) { failure(response); } else { if (response.data == 2) { Message.error(response.msg); setTimeout(() => { location.reload(); }, 1000); } else { Message.info(response.msg); } } } }) .catch(function (err) { let res = err.response; console.error(res || err); if (res) { Message.closeAll(); clearTimeout(timeObj); if (res.data.msg) { Message.error(res.data.msg); } else { Message.error("网络请求出错"); } return; } }); }
function strToHexCharCode(_str: string) { if (_str === "") return ""; var hexCharCode: Array<string> = []; for (var i = 0; i < _str.length; i++) { var str = _str.charCodeAt(i).toString(16); if (_str.length == 1) str += "0" + _str; hexCharCode.push(str); } return hexCharCode.join(""); }
let requestCount = 0; let timeObj: NodeJS.Timeout;
axios.interceptors.request.use((config) => { requestCount++; if (requestCount == 1) { timeObj = setTimeout(() => { Message.info({ message: "加载中...", duration: 0 }); }, 800); }
if ( config.data && Object.prototype.toString.call(config.data) == "[object FormData]" ) { config.headers!!["Content-Type"] = "multipart/form-data;charset=utf-8"; config.transformRequest = [ function (data) { return data; }, ]; }
if (localStorage.getItem("currentRole")) { (config.headers as AxiosRequestHeaders)["currentRole"] = localStorage.getItem("currentRole"); } return config; });
axios.interceptors.response.use((response) => { requestCount--; if (requestCount === 0) { setTimeout(() => { Message.closeAll(); }, 1500); clearTimeout(timeObj); } return response; });
export default { get: function ( url: string, params: string | object | null, success: any, failure: any ) { return apiAxios("GET", url, params, success, failure); }, post: function ( url: string, params: string | object, success: any, failure: any ) { return apiAxios("POST", url, params, success, failure); }, put: function ( url: string, params: string | object, success: any, failure: any ) { return apiAxios("PUT", url, params, success, failure); }, delete: function ( url: string, params: string | object, success: any, failure: any ) { return apiAxios("DELETE", url, params, success, failure); }, };
|
同时新建一个src/api/index.ts
,主要用来抛出定义的接口名称,算是按需加载吧
另外,有很多时候前端项目需要部署到很多服务器上,接口的域名会改变,所以做一个动态改变配置
1 2 3 4
| import conf from "@/assets/config/host.config"; let BaseUrl = conf.serviceHost + "/api"; export const GetUserInfo = `${BaseUrl}/user/GetUserInfo`;
|
动态配置文件加载
新建src/config/host.config.ts
,然后在main.ts
中引入它。最后需要在 index.html 中引入一个<script src="./config/index.js"></script>
,在第一次部署的时候加一个这个文件。
1 2 3 4 5 6
| let config = { serviceHost: "http://xx.com", }; if (typeof (window as any).conf !== "undefined") config = (window as any).conf; export default config;
|
1 2 3 4
| window.conf = { serviceHost: "http://xx.com", };
|
这样以后如果域名什么的修改了,就不用手动打包再上传了。也可以定义自己的配置,动态修改。
如何使用
写好的my-axios.ts
配置文件在需要的文件中引入就可以了
1 2 3 4 5 6
| <script setup lang="ts"> import api from '@/assets/js/myaxios' import { GetXXXX, } from '@/api/index.js' </script>
|
补充另一个大佬提供的方案
文件中可能需要安装一些插件或者引入一些文件,看个人需求修改或者自定义一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
| import NProgress from "nprogress"; import qs from "qs"; import axios, { AxiosError, AxiosInstance, AxiosPromise, AxiosRequestConfig, AxiosResponse, } from "axios"; import axiosRetry from "axios-retry"; import { useUserInfo } from "@/store/userInfo"; import { BASE_TIMEOUT, ENV_URL } from "../static/service";
class HttpService { private http!: AxiosInstance;
constructor() { this.http = axios.create({ baseURL: ENV_URL, timeout: BASE_TIMEOUT, }); this.addInterceptors(this.http); }
get<T>(url: string, param?: unknown, config?: AxiosRequestConfig) { return this.handleErrorWrapper<T>( this.http.get(`${url}${param ? "?" : ""}${qs.stringify(param)}`, config) ); }
getDownload<T>(url: string, param?: unknown) { return this.handleErrorWrapper<T>( this.http.get(`${url}?${qs.stringify(param)}`, { responseType: "arraybuffer", }) ); }
post<T>(url: string, param: unknown, config?: AxiosRequestConfig) { return this.handleErrorWrapper<T>(this.http.post(url, param, config)); }
postDownload<T>(url: string, param?: unknown) { return this.handleErrorWrapper<T>( this.http.post(url, param, { responseType: "arraybuffer" }) ); }
put<T>(url: string, param?: unknown, config?: AxiosRequestConfig) { return this.handleErrorWrapper<T>(this.http.put(url, param, config)); }
delete<T>(url: string, param?: unknown, config?: AxiosRequestConfig) { return this.handleErrorWrapper<T>( this.http.delete(url, { data: param, ...config }) ); }
private addInterceptors(http: AxiosInstance) { http.interceptors.request.use((config: AxiosRequestConfig) => { NProgress.start(); const { token } = useUserInfo(); if (token) config.headers!.Authorization = token; config.validateStatus = (status: number): boolean => { switch (status) { case 200: break; case 401: window.localStorage.clear(); window.sessionStorage.clear(); ElMessageBox.alert("用户信息过期,请重新登录!", "提示", { showClose: false, showConfirmButton: false, }); setTimeout(() => { ElMessageBox.close(); window.localStorage.clear(); window.location.reload(); }, 1000); break; case 404: ElNotification.error("接口错误!"); break; case 503: ElNotification.error("服务不可用!"); break; default: ElNotification.error("服务器错误"); console.warn(`status = ${status}`); break; } return status === 200; }; return config; });
http.interceptors.response.use( (response: AxiosResponse) => { NProgress.done(); return response; }, (error) => { NProgress.done(); return Promise.reject(error); } ); }
private async handleErrorWrapper<T>( p: AxiosPromise ): Promise<DataRequest<T>> { return p .then((response) => { if (response.config.responseType === "arraybuffer") { return response.data; } const { data: { code, msg }, } = response; if (code === 200) { return response.data; } ElMessage.error(msg); return Promise.reject(msg); }) .catch((error: AxiosError) => { console.warn(error); if (error.code === "ERR_NETWORK") ElMessage.error("网络错误"); if (error.code === "ECONNABORTED") ElMessage.error("请求超时"); return error; }); } } export const httpService = new HttpService();
|
在上文中设计到一个部署到地址问题,对应目录下新建service.ts
文件
1 2 3 4 5
| const NODE_ENV = import.meta.env.MODE as string; const VITE_APP_URL: string = import.meta.env.VITE_APP_URL as string;
export const ENV_URL: string = NODE_ENV === "config" ? (window as any).g.baseUrl : VITE_APP_URL;
|
在项目根目录新建.env.development
、.env.production
、.env.test
这种类似的文件,可以写上import.meta.env.MODE
、import.meta.env.VITE_APP_URL
的值
这些具体可以在vite 文档中查看
在 public 目录下新建一个 config.js 文件,其内容如下
1 2 3
| window.g = { baseUrl: "ENV_BASE_URL", };
|
在 index.html 中引入这个文件
1 2 3 4 5 6 7 8 9 10
| ....... <script> document.write( '<script src="/config.js?time="' + new Date().getTime() + ' type="module" charset="utf-8"><\/script>' ); </script>
.......
|
对应的可以部署在 Docker 上,案例给的 Dockerfile
1 2 3
| FROM nginx:1.17.6 COPY dist/ /usr/share/nginx/html/ CMD ["/bin/bash", "-c", "sed -i \"s@ENV_BASE_URL@\"${ENV_BASE_URL}\"@\" /usr/share/nginx/html/confing.js; nginx -g \"daemon off;\" "]
|
打包使用方法
1
| docker build -t app:1.0 --build-arg ENV_BASE_URL=http://xxx.com .
|
安装 Pinia
同时为了能够数据持久化安装**pinia-plugin-persistedstate
**
1
| npm i pinia-plugin-persistedstate
|
接下来我们要做一些自己的个性化设置,让代码更加有规则吧
在src/store/index.ts
写入如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { createPinia } from "pinia"; import { createPersistedState } from "pinia-plugin-persistedstate";
const store = createPinia(); store.use( createPersistedState({ serializer: { serialize: JSON.stringify, deserialize: JSON.parse, }, }) ); export default store;
|
在 main.ts 中注册 store 以及上面编写的动态配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { createApp } from "vue"; import App from "./App.vue"; const app = createApp(App);
import config from "@/assets/config/host.config.js"; app.config.globalProperties.$config = config;
import store from "@/store/index";
app.use(store);
app.mount("#app");
|
提示找不到文件的错误
这里会看到一些报错提示,找不到模块“./App.vue”或其相应的类型声明。
找不到模块“@/store/index”或其相应的类型声明。
第一个解决的方法是src/env.d.ts
中加入
1 2 3 4 5 6 7 8
|
declare module "*.vue" { import { DefineComponent } from "vue"; const component: DefineComponent<{}, {}, any>; export default component; }
|
第二个找不到文件的问题主要是@没有被解析,ts 文件不能被正确识别。
解析@在根目录下的 vite.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13
| import path from 'path' import { defineConfig } from 'vite' ....... const pathSrc = path.resolve(__dirname, 'src') ...... export default defineConfig({ ...... resolve: { alias: { "@/": `${pathSrc}/` } }, })
|
对于 ts 文件的识别就需要在项目根目录下新建一个tsconfig.json
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| { "compilerOptions": { "baseUrl": ".", "target": "esnext", "useDefineForClassFields": true, "module": "esnext", "moduleResolution": "node", "strict": false, "jsx": "preserve", "sourceMap": true, "resolveJsonModule": true, "esModuleInterop": true, "lib": ["esnext", "dom"], "paths": { "@/*": ["src/*"] }, "skipLibCheck": true }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] }
|
有了上面的这个作为基础,我们就能定义自己想要的数据了,例如我定义一个存储用户信息的 store
在src/store/user.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import { defineStore } from "pinia";
export const useUserStore = defineStore("user", { persist: { storage: sessionStorage }, state: () => ({ name: "", age: -1, }), getters: { getUser() { return { name: this.name, age: this.age }; }, }, actions: { setUser(user) { for (let i in user) { this[i] = user[i]; } }, }, });
|
这样就可以非常简单的在需要使用的文件种导入,例如。
1 2 3 4 5 6 7 8 9 10 11
| <template> <div>{{ userStore.name }}--{{ userStore.age }}</div> </template> <script setup lang="ts"> import { useUserStore } from '@/store/user' let userStore = useUserStore()
userStore.setUser({ name: '张三', age: 22 }) </script>
<style scoped></style>
|
安装 VueRouter
vue3.x 的对应的 VueRouter 版本在 v4.x,如果你是 vue2.x 使用的路由版本在 4.x 会提示一些很奇怪的错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| 1. 安装
```shell npm install vue-router@4 ```
2. 路由注册文件
在`/src/router/index.ts`文件中编写自定的路由。这里主要是引入注册页面,并且希望它能够动态导入路由。目前这项功能我没使用上,不过记录一下。
```typescript import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' function getRoutes() { const routes = [ { path: '/home', name: '我的APP', meta: { title: '主页', keepAlive: true, }, component: () => import('../views/teacher/home.vue'), }, ]
return routes } const router = createRouter({ history: createWebHashHistory(), routes: getRoutes(), }) export default router
function loadRouters() { const context = import.meta.globEager('../views/**/*.vue') const routes: RouteRecordRaw[] = [] let modules = import.meta.glob('../views/**/*.vue') Object.keys(context).forEach((key: any) => { if (key === './index.ts') return let name = key.replace(/(\.\.\/views\/|\.vue)/g, '') let path = '/' + name.toLowerCase() if (name === 'Index') path = '/' routes.push({ path: path, name: name, component: modules[`../views/${name}.vue`], }) }) return { context, routes } } ```
3. 将写好的文件导入注册到`main.ts`中
```typescript ....... import router from '@/router/index' app.use(router) ...... ```
4. 那么我们就只要在APP.vue中加入`<router-view></router-view>`就能够跳转路由渲染页面了。
```typescript import { useRouter } from 'vue-router' let router = useRouter()
const toMain = () => { router.push({ path: '/home' }) } ```
这里就完成了路由的跳转配置
但是平常可能会遇到这样一个这样的场景A->B,返回到A页面的时候希望保存之前的状态,A->C返回的时候就重新调用函数。那么就需要一个灵活的缓存操作。而如果你使用`activated`这种方法,你就要在URL或者什么地方记录本次是否重新调用接口,这样就显得太麻烦了。我们都了解使用`keep-alive`能缓存组件,同时`include`和`exclude`能让那些组件缓存和不缓存,而且`exclude`优先级比`include`高。那么我是否可以通过操作这2个数组来控制是否缓存页面。在`/src/router/keepAlive.ts`文件中写入以下代码
```typescript import { ComponentInternalInstance, ref } from 'vue' export const excludes = ref<string[]>([]) export function removeKeepAliveCache(instance: ComponentInternalInstance) { excludes.value.push(instance.type.name!) console.log(excludes.value, 'remove') } export function resetKeepAliveCache(instance: ComponentInternalInstance) { excludes.value = excludes.value.filter((item) => item !== instance.type.name) console.log(excludes.value, 'reset') } export const SimpleEvents = { map: new Map(), registerEvent(key: string, cb: (data?: any) => void) { this.map.set(key, cb) }, emit(key: string, params?: any) { if (this.map.has(key)) { this.map.get(key)(params) } }, } export const GlobalData = { animationMode: ref('slide'), }
|
一个简单高效的通过控制exclude
数组来改变是否缓存页面的方法就出现了。在 APP.vue 页面引入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <router-view v-slot="{ Component, route }"> <keep-alive :include="include" :exclude="excludes"> <component :is="Component"></component> </keep-alive> </router-view> </template>
<script lang="ts"> export default { name: "Home", }; </script> <script lang="ts" setup> import { excludes } from "@/router/keepAlive"; const include = ["A", "B", "C"]; </script>
|
当我们从 A 跳入 C 的时候我们希望 C 返回到 A 不缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> A页面 </template>
<script lang="ts"> export default { name: "A", }; </script> <script lang="ts" setup> import { useRouter, onBeforeRouteLeave } from "vue-router"; import { removeKeepAliveCache, resetKeepAliveCache, } from "@/router/keepAlive"; const instance = getCurrentInstance()!; onBeforeRouteLeave((to, from) => { if (to.path === "/C") { removeKeepAliveCache(instance); } else { resetKeepAliveCache(instance); } }); </script>
|
1 2 3 4 5 6 7 8 9
| <template> C页面 </template>
<script lang="ts"> export default { name: "C", }; </script> <script lang="ts" setup></script>
|
通过removeKeepAliveCache, resetKeepAliveCache
暴露的方法来控制 exclude 数组,从而达到是否缓存这个页面,同时还能做一些动画。这里如果有时间就去琢磨实现一下。
优化
上面的步骤基本完成了一个项目的搭建,和一些开发时候的优化。这里做的优化是一些代码编写时候的便捷优化。Vue3 比较不同的是它可以采用组合式编程,一段一段的。
自动导入
由于经常要引入 vue 的方法,而我又不想自己手动写,想让它自动导入,这怎么弄呢
安装unplugin-auto-import
1
| npm i -D unplugin-auto-import
|
vite.config.ts 配置
1 2 3 4 5 6 7 8 9 10 11 12 13
| ...... import AutoComplete from 'unplugin-auto-import/vite' ...... const pathSrc = path.resolve(__dirname, 'src') export default defineConfig({ ..... plugins:[ AutoComplete({ imports: ['vue', 'vue-router'], dts: path.resolve(pathSrc, 'auto-imports.d.ts'), }), ] })
|
打包优化
这个只要在 vite.config.ts 中配置一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| export default defineConfig({ base: "./" , build: { rollupOptions: { output: { manualChunks: (id) => { if (id.includes("node_modules")) { return id .toString() .split("node_modules/")[1] .split("/")[0] .toString(); } }, entryFileNames: "js/[name].[hash].js", chunkFileNames: "js/[name].[hash].js", }, }, }, });
|
最后可能有其他的优化我暂时记不起来了,把整个配置文件分享出来,以后想起来了,再接着编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
| import path from "path"; import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; import Components from "unplugin-vue-components/vite"; import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import Icons from "unplugin-icons/vite"; import IconsResolver from "unplugin-icons/resolver"; import AutoImport from "unplugin-auto-import/vite"; import { splitVendorChunkPlugin } from "vite"; import Unocss from "unocss/vite"; import { presetAttributify, presetIcons, presetUno, transformerDirectives, transformerVariantGroup, } from "unocss"; const pathSrc = path.resolve(__dirname, "src");
export default defineConfig({ base: "./" , build: { rollupOptions: { output: { manualChunks: (id) => { if (id.includes("node_modules")) { return id .toString() .split("node_modules/")[1] .split("/")[0] .toString(); } }, entryFileNames: "js/[name].[hash].js", chunkFileNames: "js/[name].[hash].js", }, }, }, resolve: { alias: { "@/": `${pathSrc}/`, }, }, css: { preprocessorOptions: { scss: { additionalData: `@use "@/styles/element/index.scss" as *;`, }, }, }, plugins: [ vue(), AutoImport({ imports: ["vue"], resolvers: [ ElementPlusResolver(), IconsResolver({ prefix: "Icon", }), ], dts: path.resolve(pathSrc, "auto-imports.d.ts"), }), Components({ extensions: ["vue", "md"], include: [/\.vue$/, /\.vue\?vue/, /\.md$/], resolvers: [ ElementPlusResolver({ importStyle: "sass", }), IconsResolver({ enabledCollections: ["ep"], }), ], dts: path.resolve(pathSrc, "components.d.ts"), }), Unocss({ presets: [ presetUno(), presetAttributify(), presetIcons({ scale: 1.2, warn: true, }), ], transformers: [transformerDirectives(), transformerVariantGroup()], }), splitVendorChunkPlugin(), Icons({ autoInstall: true, }), ], server: { proxy: { "/api": { target: "http://xx.com", changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ""), }, }, }, });
|
模版编写优化 tsx
这个问题是在写element-plus
的Virtualized Table 虚拟化表格
中遇到的,由于它的示例代码里面有用到 tsx,所以需要用到@vitejs/plugin-vue-jsx
这个插件。
注意:
这个插件和 vite 之间有版本对应的关系。我的 vite 是 2.9.9 所以我安装了版本1.3.10
。
1
| npm install @vitejs/plugin-vue-jsx@^1.3.10 -D
|
安装之后要在 vite.config.ts 中引入这个插件
1 2 3 4
| ...... import VueJsx from '@vitejs/plugin-vue-jsx' .... plugins:[VueJsx(),]
|
然后就可以将script上
的lang="ts"
,改成lang="tsx"
。