Vue3 项目脚手架搭建和运行指北

Vue

This article was last updated on <span id="expire-date"></span> days ago, the information described in the article may be outdated.

Vue3 项目脚手架搭建和运行指北

本指南带你从 0 到 有 的编写一个 Vue3 项目的脚手架,解剖完整的 Vue3 项目结构和各种配置文件,并手把手教你配置 VSCode 达到最高效率,运用到以下技术:

Install NodeJS、TypeScript、pnpm、vite

安装 NodeJS(默认),安装 TypeScript,换成 pnpm 包管理工具,安装 Vite 打包工具。

# 将 ts 添加到 dev dependencies
npm install -D typescript ts-node

# 全局安装 pnpm 包管理工具
npm install -g pnpm

Vscode IDE

安装以下 extension:

  1. Vue Language Features (Volar) Volar – Vue3 支持
  2. TypeScript Vue Plugin (Volar) Volar – TS 支持
  3. Auto Close Tag – 标签自动合并
  4. Auto Rename Tag – 标签自动修改
  5. EditorConfig for VSCode – editorconfig code rule
  6. Prettier for VSCode – prettier .prettierrc code rule
  7. ESLint for VSCode – ESLint VSC 支持,需要安装 ESLint package

Create Vite Framework

选择 ts vue3 技术栈

pnpm create vite@latest

pnpm install
pnpm install -D typescript ts-node
pnpm install -D @types/node

Set vite.config.ts/tsconfig.json

Path alias 路径别名

vite.config.ts:

reference: javascript - `Vue3 - Vite` project alias src to @ not working - Stack Overflow

import path from "path";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
const pathSrc = path.resolve(__dirname, "src");

export default defineConfig({
  // …
  resolve: {
    alias: {
      "@": pathSrc, // 源码根目录
    },
  },
  // …
});

tsconfig.json TS 配置

{
  "compilerOptions": {
    // …
    // path alias
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    }
    // …
  }
  // …
}

Code rule 代码规范

规范先行:【vue3-element-admin】ESLint+Prettier+Stylelint+EditorConfig 约束和统一前端代码规范_有来技术的博客-CSDN 博客

.editorconfig

.editorconfig:这个文件的作用是强制编辑器以该配置来进行编码,比如缩进统一为空格而不是 Tab ,每次缩进都是 2 个空格而不是 4 个等等。

VSCode 需要安装  EditorConfig for VSCode 扩展 。

# http://editorconfig.org
root = true
# 表示所有文件适用
[*]
charset = utf-8 # 设置文件字符集为 utf-8
end_of_line = lf # 控制换行类型(lf | cr | crlf)
indent_size = 2 # 缩进空格数
indent_style = space # 缩进风格(tab | space)
insert_final_newline = true # 始终在文件末尾插入一个新行
max_line_length = 60 # 一行最大数

# 表示仅 md 文件适用以下规则
[*.md]
max_line_length = off # 关闭最大行长度限制
trim_trailing_whitespace = false # 关闭末尾空格修剪

Prettier [英 /‘prɪtɪ/ 漂亮]

.prettierrc

配合 VSCode 的  VSCode Prettier  扩展,可以在编辑器里使用该规则格式化文件。

{
  "semi": false,
  "singleQuote": true,
  "printWidth": 50,
  "tabWidth": 2
}

ESLint (MAIN)

ESLint  是一个查找 JavaScript / TypeScript 代码问题并提供修复建议的工具,换句话说就是可以约束的代码不会写出一堆 BUG ,它是代码健壮性的重要保障。

这里以一个 Vite + TypeScript + Prettier  的 Vue 3 项目为例,在项目根目录下创建一个名为  .eslintrc.js  文件,写入以下内容:

module.exports = {
  root: true,
  env: {
    node: true,
    browser: true,
  },
  extends: ["plugin:vue/vue3-essential", "eslint:recommended", "prettier"],
  parser: "vue-eslint-parser",
  parserOptions: {
    parser: "@typescript-eslint/parser",
    ecmaVersion: 2020,
    sourceType: "module",
  },
  plugins: ["@typescript-eslint", "prettier"],
  rules: {
    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
    "prettier/prettier": "warn",
    "vue/multi-word-component-names": "off",
  },
  globals: {
    defineProps: "readonly",
    defineEmits: "readonly",
    defineExpose: "readonly",
    withDefaults: "readonly",
  },
};

pnpm 安装对应的依赖 dependents:

pnpm install -D eslint eslint-config-prettier eslint-plugin-prettier eslint-plugin-vue @typescript-eslint/eslint-plugin @typescript-eslint/parser prettier

排除检查:.eslintignore

dist/*

Auto import 自动导入

Install Module

pnpm install -D unplugin-auto-import unplugin-vue-components

Modify config file

vite.config.ts

import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";

plugins: [
  AutoImport({
    // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
    imports: ["vue"],
    eslintrc: {
      enabled: true, // 是否自动生成 eslint 规则,建议生成之后设置 false
      filepath: "./.eslintrc-auto-import.json", // 指定自动导入函数 eslint 规则的文件
    },
    dts: path.resolve(pathSrc, "types", "auto-imports.d.ts"), // 指定自动导入函数TS类型声明文件路径
  }),
  Components({
    dts: path.resolve(pathSrc, "types", "components.d.ts"), // 指定自动导入组件TS类型声明文件路径
  }),
];

.eslintrc.cjs

"extends": [
    "./.eslintrc-auto-import.json"
],

tsconfig.json

{
  "include": ["src/**/*.d.ts"]
}

SCSS

一款 CSS 预处理语言,SCSS 是 Sass 3 引入新的语法,其语法完全兼容 CSS3,并且继承了 Sass 的强大功能。

pnpm i -D sass

Environment variate

项目根目录新建  .env.development 、.env.production

  • 开发环境变量配置:.env.development
# 变量必须以 VITE_ 为前缀才能暴露给外部读取 VITE_APP_TITLE = 'vue3-element-admin'
VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/dev-api'
  • 生产环境变量配置:.env.production
VITE_APP_TITLE = 'vue3-element-admin'
VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/prod-api'

环境变量智能提示

新建 src/types/env.d.ts 文件存放环境变量 TS 类型声明。

// src/types/env.d.ts
interface ImportMetaEnv {
  /**
   * 应用标题
   */
  VITE_APP_TITLE: string;
  /**
   * 应用端口
   */
  VITE_APP_PORT: number;
  /**
   * API基础路径(反向代理)
   */
  VITE_APP_BASE_API: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

Vue3-router 路由

reference: 安装 | Vue Router

install:

pnpm install vue-router

修改 main.ts 文件:

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
const app = createApp(App);

app.use(router).mount("#app");

Import 导入

建立 src/router 目录,放 index.ts(入口文件) routes.ts (路由文件)。

src/router/index.ts

// 路由导入

import { createRouter, createWebHistory } from 'vue-router'
import routes from './routes'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
})

// 路由守卫/钩子 hook
// 全局前置守卫 / 路由拦截
router.beforeEach((to, from) => {
  const { title } = to.meta
  document.title = (title as string) || '默认标题'

  // 判断登陆
  // const { isNoLogin } = to.meta
  // if (!isNoLogin) return '/login'
})

export default router

src/router/routes.ts

// src/router/routes.ts 路由定义

import type { RouteRecordRaw } from "vue-router";

// define routes
const routes: Array<RouteRecordRaw> = [
  {
    path: "/",
    name: "home",
    component: () => import("@/App.vue"),
    meta: {
      title: "main page",
    },
  },
  {
    path: "/:pathMatch(.*)*",
    name: "404",
    component: () => import("@/views/404.vue"),
  },
];

export default routes;

Pinia 全局状态管理

Install

# 需要 cd 到的项目目录下
pnpm install pinia

src/main.ts

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { createPinia } from "pinia";

createApp(App).use(router).use(createPinia()).mount("#app");

Stores Folder

src/stores/index.ts 使用 storeToRefs 将 store 变为响应式,只需要导出文件的 config 即可。

// src/stores/index.ts

import { defineStore } from 'pinia'
import { storeToRefs } from 'pinia'

export const userStore = defineStore('main', {
  state: () => {
    return {
      message: 'Hello World Pinia Store',
      randomMessages: <string[]>[],
    }
  },
})

export const useConfig = defineStore('config', {
  state: () => {
    return {
      debug: false,
      autoRefresh: false,
      autoRefreshInterval: 2000,
    }
  },
})

export const config = storeToRefs(useConfig())

Element UI

Install

pnpm install element-plus

按需要引入,安装在开发环境中:

pnpm install -D unplugin-vue-components unplugin-auto-import unplugin-icons

修改 vite.config.js

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

import path from "path";
const pathSrc = path.resolve(__dirname, "src");

// Auto import
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";

// Element UI
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import IconsResolver from "unplugin-icons/resolver";
import Icons from "unplugin-icons/vite";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
      imports: ["vue"],
      eslintrc: {
        enabled: true, // 是否自动生成 eslint 规则,建议生成之后设置 false
        filepath: "./.eslintrc-auto-import.json", // 指定自动导入函数 eslint 规则的文件
      },
      // 指定自动导入函数TS类型声明文件路径
      dts: path.resolve(pathSrc, "types", "auto-imports.d.ts"),
      resolvers: [
        ElementPlusResolver(), // element func
        IconsResolver({}), // element Icon
      ],
      vueTemplate: true, // 在 vue 模板中自动导入
    }),
    Components({
      dts: path.resolve(pathSrc, "types", "components.d.ts"), // 指定自动导入组件TS类型声明文件路径
      resolvers: [
        ElementPlusResolver(),
        IconsResolver({
          enabledCollections: ["ep"],
        }),
        // 自动安装图标库
      ],
    }),
    Icons({ autoInstall: true }),
  ],

  resolve: {
    alias: {
      "@": pathSrc,
    },
  },
});

Import

src/main.ts

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { createPinia } from "pinia";
import "element-plus/dist/index.css";

createApp(App).use(router).use(createPinia()).mount("#app");

封装一个 Element UI 的 notice

src/utils/notice.ts

// src/plugin/notice.ts 系统级提醒

import { ElNotification } from 'element-plus'
import {
  NotificationParams,
  MessageParams,
} from 'element-plus'
import { ElMessage } from 'element-plus'

export enum NoticeType {
  SUCCESS = 'success',
  WARNING = 'warning',
  INFO = 'info',
  ERROR = 'error',
}

export const useNotice = (
  title: string,
  content: string,
  type: NoticeType | string = NoticeType.INFO,
) => {
  ElNotification({
    title: title,
    message: content,
    type: type,
    duration: 1000,
  } as NotificationParams)
}

export const useMsg = (
  content: string,
  type: NoticeType | string = NoticeType.INFO,
) => {
  ElMessage({
    showClose: true,
    message: content,
    type: type,
    duration: 1000,
  } as MessageParams)
}

Axois 请求库

Axios 基于 promise 可以用于浏览器和 node.js 的网络请求库

Install

pnpm install axios

Axios 工具类封装

src/utils/request.js

初始化 axios instance 实例,包含异常处理等:

import axios from "axios";
import { AxiosResponse } from "axios";
import { NoticeType, useNotice } from "./notice";
import { config } from "@/stores";
import { InternalAxiosRequestConfig, AxiosError } from "axios";

export const request = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  timeout: 5000,
});

// 添加请求拦截器
request.interceptors.request.use(
  (axiosConfig: InternalAxiosRequestConfig) => {
    // 在发送请求之前做些什么
    if (config.debug.value) {
      useNotice("Response", `url:${axiosConfig.url}`, NoticeType.INFO);
    }

    return axiosConfig;
  },
  (error) => {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 添加响应拦截器
request.interceptors.response.use(
  (response: AxiosResponse) => {
    // 对响应数据做点什么
    // 成功响应数据
    if (config.debug.value) {
      useNotice("Response", `url:${response.config.url}`, NoticeType.SUCCESS);
    }
    return response;
  },
  (error: AxiosError) => {
    console.log(error);
    if (error.response) {
      // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
      useNotice(
        `Response ERROR ${error.code}`,
        `${error.config?.url}\n${error.response.data}`,
        NoticeType.WARNING
      );
    } else if (error.request) {
      // 请求已经成功发起,但没有收到响应
      // `error.request` 在浏览器中是 XMLHttpRequest 的实例,
      // 而在node.js中是 http.ClientRequest 的实例
      useNotice(
        `Response ERROR`,
        `${error.config?.url}\n${error}`,
        NoticeType.ERROR
      );
    } else {
      useNotice(
        `Response ERROR`,
        `${error.config?.url}\nunknow http request error:${error}`,
        NoticeType.ERROR
      );
    }
    return Promise.reject(error);
  }
);

高级封装:

//  src/utils/request.ts
import axios, { InternalAxiosRequestConfig, AxiosResponse } from "axios";
import { useUserStoreHook } from "@/store/modules/user";

// 创建 axios 实例
const service = axios.create({
  baseURL: import.meta.env.VITE_APP_BASE_API,
  timeout: 50000,
  headers: { "Content-Type": "application/json;charset=utf-8" },
});

// 请求拦截器
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    // 使用 Token
    const userStore = useUserStoreHook();
    if (userStore.token) {
      config.headers.Authorization = userStore.token;
    }
    return config;
  },
  (error: any) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  (response: AxiosResponse) => {
    const { code, msg } = response.data;
    // 登录成功
    if (code === "00000") {
      return response.data;
    }

    ElMessage.error(msg || "系统出错");
    return Promise.reject(new Error(msg || "Error"));
  },
  (error: AxiosError) => {
    if (error.response.data) {
      const { code, msg } = error.response.data;
      // token 过期,跳转登录页
      if (code === "A0230") {
        ElMessageBox.confirm("当前页面已失效,请重新登录", "提示", {
          confirmButtonText: "确定",
          type: "warning",
        }).then(() => {
          localStorage.clear(); // @vueuse/core 自动导入
          window.location.href = "/";
        });
      } else {
        ElMessage.error(msg || "系统出错");
      }
    }
    return Promise.reject(error.message);
  }
);

// 导出 axios 实例
export default service;

src/plugins/api.ts 具体请求:

// src/plugins/api.ts
import { request } from './useRequest'
import { ChatInfo } from '@/schema'

// get chat infomation /chat/all/

export async function getChats(): Promise<Array<ChatInfo> | void> {
  const resp = await request.get('/chat/all/')
  if (resp.data) {
    return resp.data as Array<ChatInfo>
  }
}

Vueuse

起步 | VueUse 中文文档

Install

pnpm install @vueuse/core

Reference

  1. CSDN 博文:Vue3.3 + Vite4.3 + TypeScript5+ Element-Plus:从零到一构建企业级后台管理系统(前后端开源)

Author: WhaleFall

Permalink: https://www.whaleluo.top/javascript/vue-project-start-tutorial/

文章默认使用 CC BY-NC-SA 4.0 协议进行许可,使用时请注意遵守协议。

Comments