vue3 + ts + vite项目搭建

429 阅读5分钟

使用脚手架创建项目

yarn create vite '项目名' --template vue-ts
npm init vite@latest '项目名'

初始化项目

yarn 或者 npm i

安装eslint代码规范

输入命令 npx eslint --init 会生成 .eslintrc.js 配置文件
解决vue文件报 Parsing error: '>' expected

Snipaste_2022-08-16_17-02-57.png

在 .eslintrc.js 文件添加 'parser': 'vue-eslint-parser',

Snipaste_2022-08-16_17-05-21.png

解决 ’defineProps’ is not defined

在 .eslintrc.js 文件添加 'vue/setup-compiler-macros':true,

Snipaste_2022-08-16_17-16-07.png

配置eslint规范

// rules配置文档http://eslint.cn/docs/rules/  
'rules': {
    'vue/multi-word-component-names': 'off',
    // --以下是Possible Errors JS代码中的逻辑错误相关
    'no-extra-parens': 'error', // 禁止不必要的括号
    // "no-console": "error" // 不允许打印console.log
    'no-template-curly-in-string': 'error', // 禁止在常规字符串中出现模板字符串语法${xxx}
    // --以下是Best Practices 最佳实践
    'default-case': 'error', // 强制switch要有default分支
    'dot-location': ['error', 'property'], // 要求对象的点要跟属性同一行
    'eqeqeq': 'error', // 要求使用 === 和 !==
    'no-else-return': 'error', // 禁止在else前有return,return和else不能同时存在
    'no-empty-function': 'error', // 禁止出现空函数,有意而为之的可以在函数内部加条注释
    'no-multi-spaces': 'error', // 禁止出现多个空格,如===前后可以有一个空格,但是不能有多个空格
    'no-multi-str': 'error', // 禁止出现多行字符串,可以使用模板字符串换行
    'no-self-compare': 'error', // 禁止自身比较
    'no-unmodified-loop-condition': 'error', // 禁止一成不变的循环条件,如while条件,防止死循环
    'no-useless-concat': 'error', // 禁止没有必要的字符串拼接,如'a'+'b'应该写成'ab'
    'require-await': 'error', // 禁止使用不带await的async表达式
    // --以下是Stylistic Issues 主观的代码风格
    'array-element-newline': ['error', 'consistent'], // 数组元素要一致的换行或者不换行
    'block-spacing': 'error', // 强制函数/循环等块级作用域中的花括号内前后有一个空格(对象除外)
    'brace-style': ['error', '1tbs', { 'allowSingleLine': true }], // if/elseif/else左花括号要跟if..同行,右花括号要换行;或者全部同一行
    'comma-dangle': ['error', 'only-multiline'], // 允许在对象或数组的最后一项(不与结束括号同行)加个逗号
    'comma-spacing': 'error', // 要求在逗号后面加个空格,禁止在逗号前面加一个空格
    'comma-style': 'error', // 要求逗号放在数组元素、对象属性或变量声明之后,且在同一行
    'computed-property-spacing': 'error', // 禁止在计算属性中出现空格,如obj[ 'a' ]是错的,obj['a']是对的
    'eol-last': 'error', // 强制文件的末尾有一个空行
    'func-call-spacing': 'error', // 禁止函数名和括号之间有个空格
    'function-paren-newline': 'error', // 强制函数括号内的参数一致换行或一致不换行
    'implicit-arrow-linebreak': 'error', // 禁止箭头函数的隐式返回 在箭头函数体之前出现换行
    'indent': ['error', 2], // 使用一致的缩进,2个空格
    'jsx-quotes': 'error', // 强制在jsx中使用双引号
    'key-spacing': 'error', // 强制对象键值冒号后面有一个空格
    'lines-around-comment': 'error', // 要求在块级注释/**/之前有一个空行
    // 'multiline-comment-style': 'error', // 多行注释同一个风格,每一行前面都要有*
    'new-cap': 'error', // 要求构造函数首字母大写
    'newline-per-chained-call': ['error', { 'ignoreChainWithDepth': 2 }], // 链式调用长度超过2时,强制要求换行
    'no-lonely-if': 'error', // 禁止else中出现单独的if
    'no-multiple-empty-lines': 'error', // 限制最多出现两个空行
    'no-trailing-spaces': 'error', // 禁止在空行使用空白字符
    'no-unneeded-ternary': 'error', // 禁止多余的三元表达式,如a === 1 ? true : false应缩写为a === 1
    'no-whitespace-before-property': 'error', // 禁止属性前有空白,如console. log(obj['a']),log前面的空白有问题
    'nonblock-statement-body-position': 'error', // 强制单行语句不换行
    'object-curly-newline': ['error', { 'multiline': true }], // 对象数属性要有一致的换行,都换行或都不换行
    'object-curly-spacing': ['error', 'always'], // 强制对象/解构赋值/import等花括号前后有空格
    'object-property-newline': ['error', { 'allowAllPropertiesOnSameLine': true }], // 强制对象的属性在同一行或全换行
    'one-var-declaration-per-line': 'error', // 强制变量初始化语句换行
    'operator-assignment': 'error', // 尽可能的简化赋值操作,如x=x+1 应简化为x+=1
    'quotes': ['error', 'single'], // 要求字符串尽可能的使用单引号
    'semi': ['error', 'never'], // 不要分号
    'semi-spacing': 'error', // 强制分号后面有空格,如for (let i=0; i<20; i++)
    'semi-style': 'error', // 强制分号出现在句末
    'space-before-blocks': 'error', // 强制块(for循环/if/函数等)前面有一个空格,如for(...){}是错的,花括号前面要空格:for(...) {}
    'space-infix-ops': 'error', // 强制操作符(+-/*)前后有一个空格
    'spaced-comment': 'error', // 强制注释(//或/*)后面要有一个空格
    // --以下是ECMAScript 6 ES6相关的
    'arrow-body-style': 'error', // 当前头函数体的花括号可以省略时,不允许出现花括号
    'arrow-parens': ['error', 'as-needed'], // 箭头函数参数只有一个时,不允许写圆括号
    'arrow-spacing': 'error', // 要求箭头函数的=>前后有空格
    'no-confusing-arrow': 'error', // 禁止在可能与比较操作符混淆的地方使用箭头函数
    'no-duplicate-imports': 'error', // 禁止重复导入
    'no-useless-computed-key': 'error', // 禁止不必要的计算属性,如obj3={['a']: 1},其中['a']是不必要的,直接写'a'
    'no-var': 'error', // 要求使用let或const,而不是var
    'object-shorthand': 'error', // 要求对象字面量使用简写
    'prefer-const': 'error', // 要求使用const声明不会被修改的变量
    /*
     * 'prefer-destructuring': ['error', {
     *   'array': false,
     *   'object': true
     * }, { 'enforceForRenamedProperties': true }], // 要求优先使用结构赋值,enforceForRenamedProperties为true将规则应用于重命名的变量
     */
    'prefer-template': 'error', // 使用模板字符串,而不是字符串拼接
    'rest-spread-spacing': 'error', // 扩展运算符...和表达式之间不允许有空格,如... re1错误,应该是...re1
    'template-curly-spacing': 'error', // 禁止模板字符串${}内前后有空格
  }

配置环境(开发环境/生产环境)

根目录新建.env.development .env.production 文件
在其他文件可以通过import.meta.env拿到环境变量

# .env.development
VITE_APP_NAME='development'
# base api 配置数据请求 基础地址
#解决跨域问题 (接口地址改成本地) vite.config.js
VITE_BASE_URL='/api'
# 配置项目端口号
VITE_PORT=8000
# .env.production
# just a flag
VITE_APP_NAME = 'production'
# base api
VITE_BASE_URL = '/prod-api'

配置代理

vite.config.ts文件

/* eslint-disable object-curly-newline */
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'url'

// https://vitejs.dev/config/

// config
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default defineConfig(({ command, mode }): any => {
  const proces = loadEnv(mode, process.cwd())
  // eslint-disable-next-line prefer-destructuring
  const port = proces.VITE_PORT

  /**
   * command - 命令模式
   * mode - 生产、开发模式
   */
  /*
   * const BASE_URl = loadEnv(mode, process.cwd()).VITE_GLOB_BASE_URl;
   * const PUBLIC_DIR = loadEnv(mode, process.cwd()).VITE_GLOB_STATIC_SPACE;
   * console.log(command, mode, loadEnv(mode, process.cwd()))
   */

  console.log('\x1B[34m', '当前命令:', command)
  console.log('\x1B[34m', '当前环境:', mode)
  console.log('\x1B[37m', '\n环境变量:', proces)

  return {

    
    // 项目根目录,index.html 所在的目录
    // root:'/',
    // 生产或开发环境下的基础路径
    // base:'/hboot/',
      
    // 需要用到的插件数组
    plugins: [vue()],
    // 静态资源服务目录地址
    publicDir: '',
    // 存储缓存文件的目录地址
    cacheDir: '',
    // 打包解释项目中的@路径
    resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } },
    css: {
      // postcss-modules 行为配置
      modules: {
        // ...
      },
      // 传递给css预处理器的配置项
      preprocessorOptions: {
        // 指定less预处理的配置项
        less: {
          // ...
        }
      }
    },
    // esbuild 选项转换配置
    esbuild: {

      /*
       * ...
       * 在react组件中无需导入react
       * jsxInject: `import React from 'react'`,
       * vue 使用jsx
       */
      jsxFactory: 'h',
      jsxFragment: 'Fragment'
    },
    // 静态资源处理
    assetsInclude: '',
    // 开发服务器选项
    server: {
      // ...
      host: 'localhost',
      port,
      open: true, // 帮我们打开浏览器
      cors: true, // 允许开发时 ajax 跨域

      // 配置跨域请求
      proxy: {
          //匹配开发环境
        '/api': {
          // 这里是配置要代理的后端接口
          target: 'https://baidu.com',
          changeOrigin: true,
          rewrite: path => path.replace(/^\/api/, '')
        },
          //匹配生产环境
        '/prod-api': {
          // 这里是配置要代理的后端接口
          target: 'https://baidu.com',
          changeOrigin: true,
          rewrite: path => path.replace(/^\/prod-api/, '')
        }
      }
    },
    base: './', // 设置打包路径,部署需要
    // 依赖优化配置项
    optimizeDeps: {
      // 依赖构建入口
      entries: '',
      // 排除不需要构建的依赖项
      exclude: [],
    }
  }
})

配置@路径提示

tsconfig.json文件

{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["ESNext", "DOM"],
    "skipLibCheck": true,
    // 配置@路径提示
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    },
    //配置elementUI类型提示
    "types": ["element-plus/global","vite/client"],
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }],
  "type":["node"],
  "exclude": ["node_modules", "dist"]
}

配置git代码提交规范

官网 github.com/leoforfree/…

yarn add -D commitizen cz-customizable @commitlint/cli @commitlint/config-conventional

npm install commitizen cz-customizable @commitlint/cli @commitlint/config-conventional -D

在根目录新建 .commitlintrc.js.cz-config.js 文件

参考:

// .commitlintrc.js
module.exports = {
  extends: ["@commitlint/config-conventional"],
  rules: {
    "type-enum": [2, "always", ["init", "feat", "fix", "ui", "bug", "del", "docs", "style", "revert", "test", "perf", "chore", "refactor"]],
    "type-case": [0],
    "type-empty": [0],
    "scope-empty": [0],
    "scope-case": [0],
    "subject-full-stop": [0, "never"],
    "subject-case": [0, "never"],
    "header-max-length": [0, "always", 72],
  },
};
// .cz-config.js
module.exports = {
  types: [
    { value: "🎉init", name: "init: 初始化" },
    { value: "✨feat", name: "feat: 新功能" },
    { value: "🔨fix", name: "fix: 修复缺陷" },
    { value: "🐛bug", name: "bug: 修复问题" },
    { value: "🗑del", name: "del: 删除相关" },
    { value: "📝docs", name: "docs: 文档/注释相关" },
    { value: "💄style", name: "style: 代码格式/样式相关" },
    { value: "⏪revert", name: "revert: 代码回退" },
    { value: "✅test", name: "test: 测试相关" },
    { value: "⚡perf", name: "perf: 性能优化相关" },
    { value: "📦build", name: "build: 代码构建/编译相关" },
    { value: "🚀chore", name: "chore: 构建/工程依赖/工具" },
    { value: "♻refactor", name: "refactor: 代码重构" },
    { value: "👷ci", name: "ci: 持续集成" },
  ],

  scopes: [{ name: "all" }, { name: "doc" }, { name: "pc" }],

  messages: {
    type: "选择一种你的提交类型(必填):",
    scope: "选择一个影响区域 (可选):",
    subject: "短说明(必填):\n",
    body: '长说明,使用"|"换行(可选):\n',
    footer: "关联关闭的issue,例如:#31, #34(可选):\n",
    breaking: "非兼容性说明 (可选):\n",
    confirmCommit: "确定提交说明?",
  },

  allowCustomScopes: true,
  allowBreakingChanges: ["feat", "fix"],

  subjectLimit: 100,
};

然后在package.json文件中配置

"scripts": {
    "commit": "git-cz"
}
"config": {
    "commitizen": {
      "path": "./node_modules/cz-customizable"
    }
  },
"husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }

然后就可以通过命令 npm run commit yarn commit 提交代码啦, 效果是这样的:

Snipaste_2022-08-17_11-45-40.png

Snipaste_2022-08-17_11-46-29.png

注意一点的是vite脚手架创建的项目使用这种规范可能会报这个错误:

Snipaste_2022-08-17_13-37-49.png

配置路由

官网: router.vuejs.org/zh/

yarn add vue-router@4

npm i vue-router@4

在src目录下创建router/index.ts文件

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'

// 指定 RouteRecordRaw[] 类型后,书写的时候就有 TS 的类型提示和检查了
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    component: () => import('@/layout/index.vue'),
    children: [
      { path: '/', component: () => import('@/views/home/index.vue') },
      { path: '/my', component: () => import('@/views/my/index.vue') },
    ],
  },
  {
    path: '/login',
    component: () => import('@/views/login/index.vue')
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes,
  scrollBehavior: () => ({ top: 0 })
})

// 路由拦截
router.beforeEach((to, from, next) => {
  /*
   * if (to.hash === '/login') {
   *   return '/'
   * }
   */
  console.log(to, from, next())
  // return true或者next()就是放行
  return true
})

export default router

在main.ts文件引入注册一下

import router from '@/router'
createApp(App).use(router).mount('#app')

使用状态管理pinia

官网: pinia.vuejs.org/

yarn add pinia 安装数据存储插件 yarn add piniaPluginPersistedstate

npm i pinia

在main.ts文件引入注册

import { createPinia } from 'pinia'
// piniaPluginPersistedstate数据存储插件
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

createApp(App).use(router).use(pinia).mount('#app')

在src目录新建store/index.ts文件

import useTodoStore from './module/todo'

export default function () {
  return { todos: useTodoStore() }
}

store/module/todo.ts文件

import { Todo } from '@/type/todo'
import { defineStore } from 'pinia'

const useTodoStore = defineStore({
  id: 'todos',
  // 数据持久化(当数据发生改变时,保存到本地)
  persist: {
    key: 'todoList',
    paths: ['list'],
  },
  // 状态
  state: () => ({
    list: [
      {
        id: 1,
        name: '吃饭',
        done: true,
      },
    ] as Todo[]
  }),
 // 计算属性
  getters: {
    lists(): Todo[] {
      return this.list
    }
  },
  // 异步方法
  actions: {
    setList(obj: Todo) {
      console.log('触发了')
      this.list.push({ ...obj })
    }
  }
})
export default useTodoStore

在页面中使用

import useStore from '@/store'
// storeToRefs响应式
import { storeToRefs } from 'pinia'

const { todos } = useStore()
const { list } = storeToRefs(todos)

console.log(list)
const handleAdd = () => {
  // 直接修改
  // list.value.push({ id: Date.now(), name: '玩', done: false })

  // 调用pinia action函数修改
  todos.setList({ id: Date.now(), name: '玩', done: false })
}

使用axios

官网: www.kancloud.cn/yunye/axios…

yarn add axios

npm i axios

在src目录下创建utils/request.ts文件

/* eslint-disable implicit-arrow-linebreak */
import axios from 'axios'

const service = axios.create({
  // 请求的地址
  baseURL: import.meta.env.VITE_BASE_URL,
  // 请求超时时间
  timeout: 3 * 60 * 1000,
  // 设置请求头
  headers: { 'Content-Type': 'application/json' }
})

// 请求拦截器
service.interceptors.request.use(config => config, error =>
  // 对请求错误做些什么
  Promise.reject(error))

// 响应拦截器
service.interceptors.response.use(response => response, error => Promise.reject(error))

export default service

封装api, 在src目录下创建api/user.ts文件

import request from '@/utils/request'

export function getUserApi(params: any = {}) {
  return request({
    url: '/home/new',
    method: 'get',
    params
  })
}

在页面使用

import { getUserApi } from '@/api/user'
getUserApi({}).then(res => {
  console.log(res)
})