从头构建vue3.0+ts+vite框架
vue3.0+ts+vite 框架搭建 细节 详解
前言
框架搭建是独当一面的基础:
本文回顾基于vite+vue3.0+ts框架,我会尽量讲的更全更细节,同时方便以后复用
vite环境搭建,构建vite-vue-ts项目
- 安装vite环境
npm init @vitejs/app 或者 yarn create @vitejs/app
- 使用vite初始化vite+vue+ts的项目
npm init @vitejs/app vite-project 或者 yarn create @vitejs/app vite-project
- 选择项目类型
cd vite-project
需要进行安装包 npm install 安装成功后进行 yarn dev
至此项目完成了基础的初始化
项目配置
-
路由配置
安装vue-router npm install vue-router@4 或者 yarn add vue-router@4
在src下新建一个router文件夹,作为vue-router的配置目录。此目录下再新建index.ts文件,编辑内容如下:
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; const history = createWebHistory() const routes: Array<RouteRecordRaw> = [ { path: '/', redirect: '/home', }, { path: '/home', name: 'home', component: () => import('@/views/home/index.vue'), }, ]; const router = createRouter({ history, routes, }) export default router
新建一个views文件夹,作为项目界面开发目录。参考router中的配置可知,在views目录下新建home目录并新建index.vue,编辑文件如下:
<template> <h2>{{ msg }}</h2> <h2>{{ count }}</h2> </template> <script setup lang="ts"> import { ref } from 'vue' const msg = ref('Hello') const count = ref(710) </script> <style scoped> </style>
在main.ts中引入vue-router
import { createApp } from 'vue' import App from './App.vue'; import router from './router'; createApp(App) .use(router) .mount('#app')
在App.vue中使用vue-router
<template> <router-view /> </template> <script setup lang="ts"> </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } </style>
这个时候页面报错
无法解析@符号
安装一个path的插件:
npm install --save-dev @types/node
然后对vite.config.ts 进行修改
修改 vite.config.ts 文件:import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' /************************************* 路径配置 start ********************************/ import { resolve } from 'path' const pathResolve = (dir: string): any => { return resolve(__dirname, ".", dir) } const alias: Record<string, string> = { '@': pathResolve("src") } /************************************* 路径配置 end ********************************/ // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue()], resolve: { // ****************** 路径配置新增 alias // ****************** 路径配置新增 } // ****************** 路径配置新增 })
在tsconfig.node.json添加 compilerOptions 对象属性添加 “allowSyntheticDefaultImports”: true 即可重新启动
-
vuex 配置
安装vuexnpm install vuex@next --save 或者 yarn add vuex@next --save 同时安装 vuex-persist 持久化存储⽽⽣的⼀个插件 npm install --save vuex-persist or yarn add vuex-persist
在 src目录下创建一个store文件夹,在里面新建 index.ts、module
编辑index.ts如下:import { createStore } from 'vuex'; import VuexPersistence from 'vuex-persist'; const persistedState = new VuexPersistence({ // 数据持久化 storage: window.sessionStorage, modules: ['keyword'], }); const store = createStore({ plugins: [persistedState.plugin], modules: { }, }); export default store;
在main.ts中引入
import { createApp } from 'vue' import App from './App.vue'; import router from './router'; import store from '@/store'; createApp(App) .use(router) .use(store) .mount('#app')
出现错误
ts 配置需要读取项目根路径的 tsconfig.json,添加 paths 配置即可:{ "compilerOptions": { "target": "esnext", "useDefineForClassFields": true, "module": "esnext", "moduleResolution": "node", "strict": true, "jsx": "preserve", "sourceMap": true, "resolveJsonModule": true, "esModuleInterop": true, "lib": ["esnext", "dom"], "paths": { "@/*": [ "./src/*" ] } }, "include": [ "src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue" ] }
-
css语言 scss
npm install --save-dev sass-loader npm install --save-dev node-sass npm install --save-dev sass
成功
-
全局引入element-plus
安装: npm install element-plus
import { createApp } from 'vue' import App from './App.vue'; import router from '@/router'; import store from '@/store'; import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' createApp(App) .use(router) .use(store) .use(ElementPlus) .mount('#app')
-
按需导入element
自动导入:安装插件 npm install -D unplugin-vue-components unplugin-auto-import
// vite.config.ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import path from 'path' const { resolve } = require('path') // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ], // 注册插件 server: { open: true }, resolve: { alias: { '@': resolve(__dirname, './src'), }, } })
这时页面多了 两个文件:页面使用elment标签就会自动导入
-
集成Axios
安装:npm install axios
配置接口proxy代理: 代理我们可以使用npm 官方文档推荐的dotenv 用于动态获取.env 文件加载到 process.env 安装 npm install dotenv
根目录下创建两个 .env 文件:.env.development(开发环境) .env.production(生产环境)
// .env.development 开发环境的代理配置 VITE_PUBLIC_PATH='./' VITE_BASE_API='/asr' VITE_TARGET_HOST='http://192.168.0.222:58008'
// .env.production VITE_PUBLIC_PATH='/' VITE_BASE_API='/asr'
修改 vite.config.ts
import { defineConfig, UserConfig } from 'vite' import vue from '@vitejs/plugin-vue' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import path from 'path' // @ts-ignore import fs from 'fs' // @ts-ignore import dotenv from 'dotenv' const { resolve } = require('path') // https://vitejs.dev/config/ export default defineConfig(({ mode }: UserConfig): UserConfig => { // 根据环境变量加载环境变量文件 const ASR_ENV = dotenv.parse(fs.readFileSync(`.env.${mode}`)) return { base: ASR_ENV.VITE_PUBLIC_PATH, server: { host: '0.0.0.0', port: 3000, https: false, proxy: { [ASR_ENV.VITE_BASE_API]: { target: `${ASR_ENV.VITE_TARGET_HOST}`, changeOrigin: true, }, }, }, resolve: { alias: { '@': resolve(__dirname, './src'), }, }, plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ], css: { preprocessorOptions: { scss: { charset: false, }, }, }, build: { minify: 'terser', // 是否进行压缩,boolean | 'terser' | 'esbuild',默认使用esbuild // reportCompressedSize: true, manifest: false, // 是否产出maifest.json sourcemap: false, // 是否产出soucemap.json chunkSizeWarningLimit: 1500, }, } })
封装axios
用到的插件:npm i js-cookie
在src下新建utils文件夹:
// auth.ts // @ts-ignore import Cookies from 'js-cookie'; const TokenKey = 'asr-token'; export const getToken = () => Cookies.get(TokenKey); export const delToken = () => Cookies.remove(TokenKey);
// request.ts import axios from 'axios'; // 引入axios import Vrouter from '@/router' import { getToken } from '@/utils/auth'; const Router = Vrouter; console.log('import.meta.env.VITE_BASE_API', import.meta.env.VITE_BASE_API) const service = axios.create({ baseURL: import.meta.env.VITE_BASE_API as string, timeout: 99999, }); // http request 拦截器 service.interceptors.request.use( (config:any) => { // 全局添加 token if (getToken()) { config.headers['asr-token'] = getToken(); } return config; }, (error) => { console.error(error); return Promise.reject(error); }, ); // http response 拦截器 service.interceptors.response.use( (response) => { if (response.data.code === 9) { // Router.replace('/rejectUser'); return } return response.data }, (error) => { if (error.response && error.response.status && error.response.status === 403) { // logout().then(() => { // // removeToken() // }); } // 网络超时 if (error.message && error.message.includes('timeout')) { console.error('请求超时'); return error.message; } if (error.response && error.response.status && error.response.status === 500) { // 没有权限 console.error('接口异常'); return error; } return error; }, ); export default service;
src下新建types类型文件夹:用于声明类型
/src/types/service/index.tsinterface resModel { code:number msg:string data:any [propname:string]:any } export interface requestModel { <T>(data?: T): Promise<resModel> }
src/types/service/shims.d.ts
// eslint-disable-next-line @typescript-eslint/no-unused-vars import axios from 'axios'; declare module 'axios' { export interface AxiosInstance { <T = any>(config: AxiosRequestConfig): Promise<T>; request<T = any> (config: AxiosRequestConfig): Promise<T>; get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>; delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>; head<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>; post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>; put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>; patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>; } }
src下新建api文件夹用于接口API二次封装
使用示例
src/api/user.tsimport request from '@/utils/request'; import { requestModel } from '@/types/service'; // 获取用户信息 export const getUserInfo: requestModel = () => request({ url: '/info', method: 'get', }); // 退出登录 export const logout: requestModel = () => request({ url: '/quit', method: 'post', });
-
配置eslint
yarn add eslint@7.2.0 eslint-plugin-vue@7.20.0 vue-eslint-parser @typescript-eslint/parser @typescript-eslint/eslint-plugin@4.33.0 eslint-config-airbnb-base@15.0.0 eslint-plugin-import@2.25.4 -D
根目录设置.eslintrc.js
module.exports = { root: true, globals: { defineEmits: 'readonly', defineProps: 'readonly', }, extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:vue/vue3-recommended', 'airbnb-base', ], parserOptions: { parser: '@typescript-eslint/parser', ecmaVersion: 2020, }, rules: { 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 禁用 debugger 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 禁用 console 'no-bitwise': 'off', // 禁用按位运算符 'no-tabs': 'off', // 禁用 tab 'array-element-newline': ['error', 'consistent'], // 强制数组元素间出现换行 indent: [ 'error', 2, { MemberExpression: 0, SwitchCase: 1, ignoredNodes: ['TemplateLiteral'] }, ], // 强制使用一致的缩进 quotes: ['error', 'single'], // 强制使用一致的反勾号、双引号或单引号 'comma-dangle': ['error', 'always-multiline'], // 要求或禁止末尾逗号 'object-curly-spacing': ['error', 'always'], // 强制在大括号中使用一致的空格 'max-len': ['error', 120], // 强制一行的最大长度 'no-new': 'off', // 禁止使用 new 以避免产生副作用 'linebreak-style': 'off', // 强制使用一致的换行风格 'import/extensions': 'off', // 确保在导入路径中统一使用文件扩展名 'eol-last': 'off', // 要求或禁止文件末尾存在空行 'no-shadow': 'off', // 禁止变量声明与外层作用域的变量同名 'no-unused-vars': 'warn', // 禁止出现未使用过的变量 'import/no-cycle': 'off', // 禁止一个模块导入一个有依赖路径的模块回到自己身上 'arrow-parens': 'off', // 要求箭头函数的参数使用圆括号 semi: ['error', 'never'], // 要求或禁止使用分号代替 ASI // semi:0, // 取消对分号校验 eqeqeq: 'off', // 要求使用 === 和 !== 'no-param-reassign': 'off', // 禁止对 function 的参数进行重新赋值 'import/prefer-default-export': 'off', // 如果模块只输入一个名字,则倾向于默认输出 'no-use-before-define': 'off', // 禁止在变量定义之前使用它们,则倾向于默认输出 'no-continue': 'off', // 禁用 continue 语句 'prefer-destructuring': 'off', // 优先使用数组和对象解构 'no-plusplus': 'off', // 禁用一元操作符 ++ 和 -- 'prefer-const': 'warn', // 要求使用 const 声明那些声明后不再被修改的变量 'global-require': 'off', // 要求 require() 出现在顶层模块作用域中 'no-prototype-builtins': 'off', // 禁止直接调用 Object.prototypes 的内置属性 'consistent-return': 'off', // 要求 return 语句要么总是指定返回的值,要么不指定 'one-var-declaration-per-line': 'off', // 要求或禁止在变量声明周围换行 'one-var': 'off', // 强制函数中的变量要么一起声明要么分开声明 'import/named': 'off', // 确保命名导入与远程文件中的命名导出相对应 'object-curly-newline': 'off', // 强制大括号内换行符的一致性 'default-case': 'off', // 要求 switch 语句中有 default 分支 'no-trailing-spaces': 'off', // 禁用行尾空格 'func-names': 'off', // 要求或禁止使用命名的 function 表达式 radix: 'off', // 强制在 parseInt() 使用基数参数 'no-unused-expressions': 'off', // 禁止出现未使用过的表达式 'no-underscore-dangle': 'off', // 禁止标识符中有悬空下划线 'no-nested-ternary': 'off', // 禁用嵌套的三元表达式 'no-restricted-syntax': 'off', // 禁用特定的语法 'no-await-in-loop': 'off', // 禁止在循环中出现 await 'import/no-extraneous-dependencies': 'off', // 禁止使用外部包 'import/no-unresolved': 'off', // 确保导入指向一个可以解析的文件/模块 'template-curly-spacing': ['error', 'always'], // 要求或禁止模板字符串中的嵌入表达式周围空格的使用 '@typescript-eslint/no-var-requires': 'off', // 除import语句外,禁止使用require语句 '@typescript-eslint/no-empty-function': 'off', // 不允许空函数 '@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型 'guard-for-in': 'off', // 要求 for-in 循环中有一个 if 语句 'class-methods-use-this': 'off', // 强制类方法使用 this 'vue/html-indent': ['error', 2], // 在<template>中强制一致缩进 'vue/html-self-closing': 'off', // 执行自闭合的风格 'vue/max-attributes-per-line': [ // 强制每行属性的最大数量 'warn', { singleline: { max: 3, allowFirstLine: true, }, multiline: { max: 1, allowFirstLine: false, }, }, ], 'vue/singleline-html-element-content-newline': 'off', // 要求单行元素的内容前后有一个换行符 }, }
shift + command + p 组合键打开命令面板,输入 settings 回车打开配置文件。
js { "workbench.iconTheme": "vscode-icons", "workbench.colorTheme": "Default Dark+", "vsicons.dontShowNewVersionMessage": true, "eslint.validate": [ "javascript", "javascriptreact", "vue", "typescript" ], "eslint.format.enable": true, "eslint.alwaysShowStatus": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "files.associations": { "*.vue": "vue" }, }
直接 control + s 键保存,VSCode 会自动修复代码 ESLint 检测出的错误,帮助我们去除了 ; 逗号。
-
配置rem适配布局
1、根据页面屏幕大小计算每个页面的基准值,即一个rem为多少px
src目录下创建rem.ts
// 基准大小 const baseSize = 16 // 设置 rem 函数 function setRem() { // 当前页面宽度相对于 750 宽的缩放比例,可根据自己需要修改。 const scale = document.documentElement.clientWidth / 1920 // 设置页面根节点字体大小 document.documentElement.style.fontSize = `${baseSize * Math.min(scale, 2)}px` } // 初始化 setRem() // 改变窗口大小时重新设置 rem window.onresize = () => { setRem() } ```
注意:如果提示 “无法在 "--isolatedModules" 下编译“rem.ts”,因为它被视为全局脚 本文件。请添加导入、导出或空的 "export {}" 语句来使它成为模块 ”此时你应该在 tsconfig.json 文件中将 isolatedModules 字段设置为 false。
在main.ts中引入:
import './rem'
安装依赖
npm install postcss-pxtorem -D
对vite.config.ts文件添加配置
css: {
preprocessorOptions: {
scss: {
charset: false,
},
},
+ postcss: {
+ plugins: [
+ postCssPxToRem({
+ rootValue: 16, // 1rem的大小
+ propList: ['*'], // 需要转换的属性,这里选择全部都进行转换
+ }),
+ ],
+ },
},
结束
更多推荐
所有评论(0)