前端加亿点料 | Day 4

215 阅读5分钟

本文将以完整的 vite 前端开发视角,逐一解读增加的依赖目录结构的细节。

依赖

在本次迭代中,项目增加了很多依赖。

根目录 下,总包的依赖有:

{
  "dependencies": {
    "vue-i18n": "^10.0.4"                 # 在根目录中安装该应用是为了激活 i18n ally 插件
  },
  "devDependencies": {
    "@antfu/eslint-config": "^3.7.3",     # 全局引用的 antfu 的 ESLint 预设
    "concurrently": "^9.0.1",             # 在同一个 shell session 中运行前后端应用并分别输出 log
    "eslint": "^9.12.0",                  # elint 安装,为了命令行的运行
    "lint-staged": "^15.2.10",            # 检查已更改的文件
    "simple-git-hooks": "^2.11.1",        # git hooks 的触发器
    "vite": "^5.4.9"                      # 前后端公用的 Vite 依赖
  },
}

在前端子包 client 中的依赖有:

{
  "dependencies": {
    "@vueuse/core": "^11.1.0",                      # Vue 常用的组合式函数库
    "pinia": "^2.2.4",                              # Pinia 官方推荐的一个状态管理
    "vue": "^3.5.12",                               # Vue 的主体
    "vue-router": "^4.4.5"                          # 路由
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3.1.0",                   # 导入 .eslintrc-auto-import.json 配置使用
    "@unocss/eslint-plugin": "^0.63.4",             # UnoCSS 的 ESLint 插件,会自动读取 UnoCSS 配置
    "@unocss/preset-rem-to-px": "^0.63.4",          # UnoCSS 的 rem 转 px 的预设
    "@vitejs/plugin-legacy": "^5.4.2",              # Vite 打包兼容旧版本浏览器插件
    "@vitejs/plugin-vue": "^5.1.4",                 # Vite 的 Vue 插件
    "unocss": "^0.63.4",                            # UnoCSS
    "@iconify/json": "^2.2.261",                    # iconify 的图标集合
    "sass": "^1.80.2",                              # sa(c)ss 样式的编译器
    "unplugin-auto-import": "^0.18.3",              # 自动导入依赖 API,可以减少写 import
    "unplugin-imagemin": "^0.5.20",                 # 图片压缩插件
    "unplugin-vue-components": "^0.27.4",           # 自动导入 Vue 全局组件
    "vite-plugin-html": "^3.2.2",                   # HTML 插件
    "vite-plugin-mkcert": "^1.17.6",                # 在开发环境中使用 HTTPS 链接的插件
    "vite-plugin-obfuscator": "^1.0.5",             # 打包代码混淆插件
    "vite-plugin-vue-devtools": "^7.5.2"            # 随页面的 Vue 开发工具插件         
  },
}

目录结构

合理地规划项目目录结构,可以更好地管理同类型的资源和代码,经过安装目录结构规划,前端项目的目录结构如下所示:

client                         # 前端项目根目录 packages/client
 ┣ public                      # 静态资源,在构建阶段不需要进行处理的资源
 ┃ ┗ vite.svg
 ┣ src                         # 源代码
 ┃ ┣ assets                    # 在构建阶段需要进行处理的资源
 ┃ ┃ ┗ vue.svg
 ┃ ┣ components                # 公共组件,一般放置在项目中需要复用的组件
 ┃ ┃ ┣ global                  # 全局组件,通过 unplugin-vue-components 注册到全局
 ┃ ┃ ┃ ┗ Example.vue
 ┃ ┃ ┗ HelloWorld.vue
 ┃ ┣ composables               # 自定义组合式函数
 ┃ ┃ ┗ index.js
 ┃ ┣ layouts                   # 布局组件,通过 unplugin-vue-components 注册到全局
 ┃ ┃ ┗ Layout.vue
 ┃ ┣ locales                   # i18n 多语言配置
 ┃ ┃ ┣ en
 ┃ ┃ ┃ ┗ index.json
 ┃ ┃ ┣ zh
 ┃ ┃ ┃ ┗ index.json
 ┃ ┃ ┗ index.js
 ┃ ┣ router                     # 路由
 ┃ ┃ ┣ guards                   # 路由守卫
 ┃ ┃ ┃ ┣ index.js
 ┃ ┃ ┃ ┗ title.js
 ┃ ┃ ┗ index.js
 ┃ ┣ store                      # 数据仓库,使用 Pinia
 ┃ ┃ ┣ modules
 ┃ ┃ ┃ ┣ app.js
 ┃ ┃ ┃ ┗ index.js
 ┃ ┃ ┗ index.js
 ┃ ┣ styles                     # 样式
 ┃ ┃ ┣ index.js
 ┃ ┃ ┗ style.css
 ┃ ┣ utils                      # 工具函数
 ┃ ┃ ┣ modules
 ┃ ┃ ┃ ┣ index.js
 ┃ ┃ ┃ ┣ is.js
 ┃ ┃ ┃ ┗ request.js
 ┃ ┃ ┗ index.js
 ┃ ┣ views                      # 页面组件
 ┃ ┃ ┣ Home.vue
 ┃ ┃ ┗ Login.vue
 ┃ ┣ App.vue                    # App 组件
 ┃ ┗ main.js                    # App 入口文件
 ┣ vite                         # Vite 配置的辅助文件
 ┃ ┣ helper.js
 ┃ ┣ index.js
 ┃ ┗ plugins.js
 ┣ .env                         # 环境变量
 ┣ .env.development
 ┣ .env.production
 ┣ .eslintrc-auto-import.json   # unplugin-auto-import 为 ESLint 生成的全局配置文件
 ┣ .gitignore
 ┣ eslint.config.js             # ESLint 在本目录下的配置文件
 ┣ index.html                   # 页面入口文件
 ┣ jsconfig.json
 ┣ package.json
 ┣ README.md
 ┣ uno.config.js                # UnoCSS 配置文件
 ┗ vite.config.js               # Vite 配置文件

同类型文件的集中管理

在项目目录下,同类型的文件集中在一起管理有利于提高开发效率,一般的模式,以 utils 为例,该文件夹主要放置一些公用的工具函数,比如请求封装数据类型判断等等。一般的结构如下:

utils
 ┣ modules
 ┃ ┣ index.js
 ┃ ┣ is.js
 ┃ ┗ request.js
 ┗ index.js

utils/index.js 用以导出各个模块的命名导出,和一些临时和不太好分类的功能导出:

export * from './modules'

export function someUtil() {}

modules 目录用以分类放置各个不同的模块功能,用 modules/index.js 来收集这些功能供 utils/index.js 进行统一导出:

export * from './is'
export * from './request'

这样做的好处是在消费这些功能时,在导入语句编写时,只需要写一级的模块名,比如:

import { request, isNull, isObject } from '@/utils'

之所以不适用 Vite 的 import.meta.glob 进行载入,是因为在增减功能时,必须去 utils/index.js 中具名修改对应的模块名称,比较繁琐。

Vite 的配置

Vite 的配置使用了一些辅助函数,放置在 client/vite 目录下,所以整个配置文件看起来像这样:

import { resolve } from 'node:path'
import { cwd } from 'node:process'

import { defineConfig, loadEnv } from 'vite'

import { generateEnv, generatePlugins, generateProxy } from './vite'

export default defineConfig(({ mode }) => {
  const env = generateEnv(loadEnv(mode, cwd()))

  const {
    VITE_BASE,
    VITE_HTTPS,
    VITE_DROP_CONSOLE,
    VITE_SOURCEMAP,
    VITE_OPEN,
    VITE_PORT,
    VITE_PROXY,
  } = env

  return {
    root: cwd(),
    base: VITE_BASE || '/',
    plugins: generatePlugins({ mode, env }),
    resolve: {
      alias: {
        '@': resolve(cwd(), 'src'),
        '~': resolve(cwd()),
      },
    },
    server: {
      host: true,
      https: VITE_HTTPS,
      open: VITE_OPEN,
      port: VITE_PORT || 5173,
      proxy: generateProxy(VITE_PROXY),
    },
    preview: {
      port: VITE_PORT || 5173,
      proxy: generateProxy(VITE_PROXY),
    },
    esbuild: {
      pure: VITE_DROP_CONSOLE ? ['console.log', 'debugger'] : [],
    },
    build: {
      target: 'esnext',
      chunkSizeWarningLimit: 2000,
      sourcemap: VITE_SOURCEMAP,
    },
  }
})

大部分的功能可以通过 .env 进行配置,同时也把比较集中的 pluginsproxy 等篇幅较大的功能提取到 vite 目录下单独管理。具体的 vite 目录文件,请点击查看

main.js 出发

main.js 是整个前端程序的入口文件,经过 vite 和其他一系列工具配置,再加上同类型文件的集中管理,该文件看起来像这样:

import { setupI18n } from '@/locales'
import { setupRouter } from '@/router'
import { setupStore } from '@/store'

import App from './App.vue'

import '@/styles'

async function setupApp() {
  const app = createApp(App)

  setupStore(app)
  setupRouter(app)
  setupI18n(app)

  app.mount('#app')
}

setupApp()

该文件显示了stylestorerouteri18n 的初始化,style 直接将相关的样式文件在style/index.js中导入,而后三者则分别只导入了三个获取 app 实例的配置函数,将实例发送到对应的目录下管理各自的逻辑。

其中没有涉及到全局组件的注册,这是因为使用了 unplugin-vue-components ,直接将 components/global 下的 vue 文件注册为全局组件。配置如下:

import Components from 'unplugin-vue-components/vite'

Components({
  dts: false,
  globs: [
    './src/components/global/*.vue',
    './src/components/global/**/index.vue',
    './src/layouts/Layout.vue',
  ],
})

App.vue 主组件

因为有 vue-router 的参与,所以 App.vue 主组件的内容可以只放置一个 router-view

<template>
  <router-view />
</template>

具体细节可以直接参考 vue-router 官网,这里不再赘述。

storei18n

store 采用了比较简单的 pinia 作为数据仓库管理工具。属于比较常规的应用,可以参考 pinia 官网

i18n 使用时需要注意,因为 i18n ally 插件是通过读取根目录下的 vue-i18n 依赖进行激活的,所以,该依赖需要装在主包中。同时,因为 i18n ally 只能编辑和修改 json 文件,所以 i18n 使用json 导入:

import en from './en/index.json'
import zh from './zh/index.json'

const i18n = createI18n({
  locale: 'zh',
  messages: {
    zh,
    en,
  },
})

源码

前端部分的简要变更说明如有未尽之处,请参照源码