Skip to content

Vite 从 0 到 1 搭建脚手架

注意点

搭建一个基础脚手架时,需要考虑的因素有很多,以下是一些可能需要注意的点:

项目类型:

需要确定项目是什么类型的项目,例如是 Web 应用、移动应用,还是桌面应用。不同类型的应用可能需要不同的工具和框架来支持。

构建工具:

需要选择一个合适的构建工具,例如 webpack、Rollup 等。构建工具能够将源代码转换成可执行的 JavaScript 代码,并且可以支持模块化、代码压缩、代码拆分等功能。

代码风格和规范:

需要定义一套代码风格和规范,以保证整个项目代码风格统一、易于维护。可以使用一些工具来自动化检测和修复代码风格,例如 ESLint、Prettier 等。

多环境配置:

需要根据不同的环境(例如开发环境、测试环境、生产环境等)来配置不同的参数,例如 API 地址、日志级别等。

代码分块:

需要使用代码分块来提高应用性能,将代码按照功能或页面进行分块,可以利用 webpack 的代码分割功能来实现。

异步请求:

需要使用一个合适的异步请求库来发送网络请求,例如 axios、fetch 等。需要对请求进行拦截和响应处理,以统一处理异常情况和成功返回数据。

目录结构:

需要定义一个合理的目录结构,以便组织代码、资源和配置文件。可以按照功能或者文件类型进行分类,例如按照页面、组件、样式、图片等进行分类。

路由定义:

需要定义一个合适的路由结构,以支持应用的页面导航和跳转。可以使用一个路由库(例如 vue-router、react-router 等)来实现路由功能。

状态管理:

如果应用的状态比较复杂,可以使用一个全局状态管理库来管理状态,例如 Vuex、Redux 等。需要将状态的定义、修改、订阅等功能进行封装,以便于应用其他地方使用。

测试:

需要编写测试用例来验证应用的功能和逻辑是否正确。可以使用一些测试工具来进行自动化测试,例如 Jest、Mocha 等。

以上是一些需要注意的点,当然在实际开发中还有很多细节需要关注。在搭建一个基础脚手架时,需要结合具体的需求和场景来进行选择和实现。

该基础架构

  • 项目类型:Web Vue3 管理后台
  • 构建工具: Vite
  • 代码风格和规范:Prettier
  • 多环境配置:开发、测试、生产
  • 代码分块:代码分割
  • 异步请求:axios
  • 目录结构:
apis 所有的api存放目录
components 可以复用的组件
layouts 目录结构页面
stores 状态管理
layouts 目录结构页面
utils 工具函数目录
  • 路由定义:VueRouter4.0
  • 状态管理:Pinia

Vite 官方脚手架

安装环境-初始化 vite-启动项目

需要安装环境

vscode

https://code.visualstudio.com/download

git

https://git-scm.com/downloads

nodejs

https://nodejs.org/zh-cn/

https://nodejs.org/dist/v18.14.1/node-v18.14.1.pkg

https://nodejs.org/dist/v18.14.1/node-v18.14.1-x64.msi

初始化 vite

https://cn.vitejs.dev/

bash
npm create vite@latest

custom

启动项目

bash
npm install --registry=https://registry.npmmirror.com

npm run dev

初始化 git-使用 prettier 自动格式化代码

初始化 git

bash
git init

提交代码使用 prettier 自动格式化

https://ffffee.com

https://prettier.io/docs/en/install.html

bash
npm install --save-dev --save-exact prettier

创建 .prettierrc.json 文件

给.prettierrc.json 填写配置

json
{
  "arrowParens": "always", // 要求箭头函数参数用括号括起来。例如,`(x) => x + 1`
  "bracketSameLine": true, // 将对象的左括号放在同一行,而不是另起一行。
  "bracketSpacing": false, // 对象字面量的大括号之间不加空格
  "embeddedLanguageFormatting": "auto", // 是否启用嵌入语言的格式化规则,如HTML、Markdown等
  "htmlWhitespaceSensitivity": "css", // HTML文件空格敏感度,可以是"css"或"strict"
  "jsxSingleQuote": false, // 在JSX中使用单引号而不是双引号
  "printWidth": 120, // 每行最大字符数
  "proseWrap": "preserve", // 换行格式,可以是"preserve"或"always"
  "quoteProps": "as-needed", // 只在必要时为对象属性添加引号
  "insertPragma": false, // 插入Prettier的特殊注释来表明文件已被格式化过
  "requirePragma": false, // 要求在文件开头加上Prettier特殊的注释才会格式化文件
  "semi": true, // 是否在语句末尾添加分号
  "singleQuote": true, // 是否使用单引号而不是双引号
  "tabWidth": 2, // 缩进宽度
  "trailingComma": "none", // 数组、对象、函数等结尾是否添加逗号,可选值:"none"、"es5"、"all"
  "useTabs": false, // 是否使用制表符进行缩进
  "vueIndentScriptAndStyle": false // 是否为Vue文件中的<script>和<style>标签缩进
}

使用 npx prettier --write . 自动修复代码使其符合规范

使用 npx prettier --check . 检查有哪些文件需要修复的

安装 husky 和 ​​lint-staged:

bash
npm install --save-dev husky lint-staged
npx husky install
npm pkg set scripts.prepare="husky install"
npx husky add .husky/pre-commit "npx lint-staged"

将以下内容添加到您的 package.json:

bash
{
  "lint-staged": {
    "**/*": "prettier --write --ignore-unknown"
  }
}

或者

bash
{
  "lint-staged": {
    "**/*": "prettier --check --ignore-unknown"
  }
}

目录结构调整-scss-CSSresetORnormalize

目录结构调整

apis 所有的api存放目录
components 可以复用的组件
stores 状态管理
layouts 目录结构页面
utils 工具函数目录

css 预处理器

https://cn.vitejs.dev/guide/features.html#css-pre-processors

bash
npm add -D sass

CSS reset OR normalize

bash
npm install normalize.css

https://ffffee.com/#/frontend/230216-reset.css-vs-normalize.css.md

多环境打包

"build": "run-p type-check build-only", 这个是什么意思

这段代码是一个 NPM script,它使用 npm-run-all 包中的 run-p 命令同时运行两个脚本命令:type-check 和 build-only。这里的 type-check 命令使用了 vue-tsc 工具来进行 TypeScript 类型检查,而 build-only 命令使用 vite build 命令来构建项目。

因此,运行 npm run build 命令将同时运行这两个命令。首先,type-check 命令将执行 TypeScript 类型检查,如果存在类型错误,则会抛出一个错误并停止构建进程。如果类型检查通过,则会继续运行 build-only 命令,这将构建你的项目并生成静态文件,以便在生产环境中使用。

使用这种方式可以在构建项目之前先进行类型检查,以确保项目代码的类型正确性,从而减少运行时错误的出现。同时,将两个命令放在一个 run-p 命令中并行运行,可以加快构建时间,提高开发效率。

多环境配置根据命令打包出来不同的目录包

https://cn.vitejs.dev/guide/env-and-mode.html

.env.development

ini
NODE_ENV=development

.env.production

ini
NODE_ENV=production

.env.qa

ini
NODE_ENV=production

package.json

json
    "build": "run-p type-check build-only",
    "qa": "run-p type-check build-qa",
    "build-only": "MODE=prod vite build",
    "build-qa": "MODE=qa vite build --mode qa",

vite.config.ts

js
  build: {
    outDir: process.env.MODE || 'dist'
  },

运行打包命令

bash
# 测试1
npm run testing
# 测试2
npm run qa
# 生产|线上环境
npm run build

utils request axios 请求拦截响应拦截

https://axios-http.com/zh/docs/interceptors

bash
npm install axios --save

@/utils/request.ts

ts
/**
 * @file request
 * @description 封装 Axios 请求库
 * @module utils/request
 */

import axios from 'axios';

// Axios 实例
const service = axios.create({
  baseURL: import.meta.env.VITE_BASE_URL, // url = base url + request url
  // `withCredentials` 表示跨域请求时是否需要使用凭证
  withCredentials: false, // 默认的
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 1000 * 60 // request timeout
});

// 请求拦截  设置统一 header
service.interceptors.request.use(
  (config) => {
    // const userToken = localStorage.getItem('userToken', userToken);
    // if (userToken) {
    //     config.headers['Authorization'] = userToken;
    // }
    return config;
  },
  (error: any) => {
    return Promise.reject(error);
  }
);

// 响应拦截  401 token过期处理
service.interceptors.response.use(
  (response) => {
    return response;
  },
  (error: any) => {
    return Promise.reject(error);
  }
);

export default service;

@/apis/roles.ts

ts
/**
 * @file 系统管理 => 角色管理 api
 * @description 程序员李钟意
 */

import request from '@/utils/request';

// 角色管理 => 获取用户列表
export function getRoles(params: Record<string, unknown> = {}): Promise<any> {
  return request({method: 'get', url: '/api/roles/', params});
}

// 角色管理 => 根据角色id获取用户权限
export function getRolesByIdPermission(roleId: number): Promise<any> {
  return request({method: 'get', url: `/api/roles/${roleId}/`});
}

// 角色管理 => 添加用户
export function postRoles(data: any): Promise<any> {
  return request({method: 'post', url: '/api/roles/', data});
}

// 角色管理 => 修改用户
export function putRoles(id: number, data: any): Promise<any> {
  return request({method: 'put', url: `/api/roles/${id}/`, data});
}

// 角色管理 => 删除用户
export function deleteRoles(id: number): Promise<any> {
  return request({method: 'delete', url: `/api/roles/${id}/`});
}

https://jsonplaceholder.typicode.com/todos/

配置反向代理

https://cn.vitejs.dev/config/server-options.html#server-proxy

ts
  server: {
    proxy: {
      // 带选项写法:http://localhost:5173/api/todos -> http://jsonplaceholder.typicode.com/todos
      '/api': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        // logLevel: 'debug', // 日志级别,设置为debug
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },

添加 element-plus 组件库、按需引入配置

https://element-plus.gitee.io/zh-CN/guide/installation

bash
npm install element-plus --save

如果您使用 Volar,请在 tsconfig.json 中通过 compilerOptions.type 指定全局组件类型。

json
// tsconfig.json
{
  "compilerOptions": {
    // ...
    "types": ["element-plus/global"]
  }
}

按需引入

bash
npm install -D unplugin-vue-components unplugin-auto-import

Vite 打包优化

路由异步组件

defineAsyncComponent

解决路径问题

https://cn.vuejs.org/api/general.html#defineasynccomponent

https://ffffee.com

vite 打出 .gzip 包怎么做

https://github.com/vbenjs/vite-plugin-compression/tree/main#readme

bash
npm i vite-plugin-compression -D

webpack 的 webpack-bundle-analyzer 这个插件,在 vite 里面有差不多的插件吗

https://github.com/btd/rollup-plugin-visualizer

bash
npm install --save-dev rollup-plugin-visualizer
js
import { visualizer } from "rollup-plugin-visualizer";

import { defineConfig, type PluginOption } from 'vite'
export default defineConfig({
  plugins: [visualizer() as PluginOption],
})

代码分割的配置

vite.config.ts

ts
 rollupOptions: {
      output: {
        manualChunks(id) {
          // @ts-ignore
          if (id.includes('node_modules')) {
            return id.toString().split('node_modules/')[1].split('/')[0].toString();
          }
        },
        chunkFileNames: (chunkInfo) => {
          const facadeModuleId = chunkInfo.facadeModuleId ? chunkInfo.facadeModuleId.split('/') : [];
          const fileName = facadeModuleId[facadeModuleId.length - 2] || '[name]';
          return `js/${fileName}/[name].[hash].js`;
        }
      }
    }

这段代码是用来配置 Vite 使用 Rollup 打包时的一些选项。其中,output 选项用来配置输出文件的相关选项,这里包括了两个选项:

  1. manualChunks: 该选项指定了如何对代码进行分块,返回的是一个函数,用于根据代码的 ID 判断应该将代码分块到哪个文件中。这里的代码分块是指将代码分割为多个文件,以便在浏览器中并行下载,提高网页的加载速度。该函数的实现逻辑是,如果代码的 ID 中包含了 node_modules 字符串,那么就将该代码分块到该模块的名称(即 node_modules 目录下的第一级目录名)对应的文件中。

  2. chunkFileNames: 该选项指定了代码块文件的文件名格式。在该选项的函数中,根据 chunkInfo 对象中的信息,可以生成每个代码块文件的文件名。这里的文件名格式是 js/${fileName}/[name].[hash].js,其中 ${fileName} 是从 chunkInfo 中解析出来的文件名,[name] 代表代码块的名称,[hash] 代表该文件的哈希值。

docker 部署前端项目

https://www.docker.com/products/docker-desktop/

Dockerfile

Dockerfile
# 使用官方的 node 镜像作为基础镜像
# 从 node:14.19.3 镜像创建一个名为 development 的镜像
FROM node:14.19.3 AS development

# 设置维护者信息
LABEL maintainer="1454598684@qq.com"

# 设置工作目录为 /app
WORKDIR /app

# 将 package.json 和 package-lock.json 复制到容器中
COPY package*.json ./

# 安装所需的包
RUN npm install --registry=https://registry.npmmirror.com

# 将项目中的所有其他文件复制到容器中
COPY . .

# 打包构建
RUN npm run prod

# 从 development 镜像创建一个名为 build 的镜像
FROM development AS build

# 从 nginx:1.21.6 镜像创建一个镜像
FROM nginx:1.21.6

# 从 build 镜像中复制 nginx 配置文件到容器中的 /etc/nginx/conf.d/default.conf
COPY --from=build /app/.nginx/nginx.conf /etc/nginx/conf.d/default.conf

# 设置工作目录为 /usr/share/nginx/html
WORKDIR /usr/share/nginx/html

# 删除该目录中的所有内容
RUN rm -rf ./*

# 从 build 镜像中复制项目文件到容器中的 /usr/share/nginx/html 目录
COPY --from=build /app/prod .

# 设置容器的入口点,运行 nginx 命令
ENTRYPOINT ["nginx", "-g", "daemon off;"]

# 此 Dockerfile 使用了多阶段构建,先在开发环境中构建项目,再在生产环境中运行项目。
# 这样可以保证镜像的小尺寸,同时避免了在生产环境中安装多余的工具。

docker-compose.yml

yml
services:
  vite_vue3_demo:
    build: .
    container_name: vite_vue3_demo
    volumes:
      - ./prod:/app/prod # 挂载卷 把当前项目根目录的 ./frontend/rd 映射到 容器/app/rd
    ports:
      - 8000:80
bash
docker-compse build
bash
docker-compse up -d