新晋码农一枚,小编会定期整理一些写的比较好的代码和知识点,作为自己的学习笔记,试着做一下批注和补充,转载或者参考他人文献会标明出处,非商用,如有侵权会删改!欢迎大家斧正和讨论!本章内容较多,可点击文章目录进行跳转!

小编整理和学习了机器人的相关知识,可作为扫盲使用,后续也会更新一些技术类的文章,大家共同交流学习!

您的点赞、关注、收藏就是对小编最大的动力!

 机器人系列文章

机器人驭风而行:低空经济如何开启智能新纪元【科普类】

从齿轮到智能:机器人如何重塑我们的世界【科普类】

机器人的“神经网络”:以太网技术如何重塑机器人内部通信?【技术类】

半双工 vs 全双工:对讲机与电话的根本区别【技术类】

CRC校验:二进制除法的魔法如何守护你的数据安全?【技术类】

解密ROS:机器人时代的“安卓系统”,凭什么让开发者集体狂欢?【科普类】

2026年AI原生应用开发趋势:从概念到落地【科普类】

边缘计算与云计算的协同发展:未来算力布局的核心逻辑【科普类】

前端工程化进阶:从搭建完整项目脚手架到性能优化【技术类】


前言

在前端技术飞速迭代的今天,“工程化”已不再是大厂专属的高端概念,而是中小团队提升开发效率、保障项目质量、降低维护成本的核心支撑。当项目规模从单人小demo升级为多人协作的企业级应用,零散的开发模式、混乱的代码规范、繁琐的手动部署,都会成为制约项目推进的瓶颈。2026年,随着Vite、Vue3、React18等工具与框架的成熟,前端工程化迎来了“轻量化、高效化、标准化”的新阶段,掌握从脚手架搭建到性能优化的全链路工程化能力,已成为前端开发者从“初级”迈向“进阶”的关键门槛。

本文聚焦前端工程化核心落地路径,从工程化组成架构拆解,到基于Vite+Vue3/React18的企业级脚手架搭建,再到代码规范、自动化测试、性能优化的实战落地,全程配套详细步骤与避坑指南,帮助前端工程师系统性提升工程化能力,让项目开发从“无序混乱”走向“标准化高效协同”。

一、认知升级:现代前端工程化的核心组成

前端工程化的本质,是通过工具链与规范体系,将前端开发的全流程(构建、编码、测试、部署)标准化、自动化,解决“多人协作一致性”“开发与生产环境差异”“项目可维护性”三大核心问题。现代前端工程化并非单一工具的应用,而是由构建工具、代码规范、自动化测试、部署流程四大核心模块构成的完整体系,各模块相互协同,形成闭环。

1.1 构建工具:工程化的核心引擎

构建工具是前端工程化的基石,负责将开发者编写的源代码(Vue/React组件、TypeScript、SCSS等)转换为浏览器可识别的静态资源,核心能力包括代码编译、模块打包、热更新、环境变量注入等。近年来,构建工具从Webpack的“全能型”向Vite、Turbopack的“轻量化、高速化”演进,解决了传统构建工具冷启动慢、热更新延迟高的痛点。

当前主流构建工具各有侧重:Webpack生态完善、适配场景广,适合复杂大型项目;Vite基于ESModule实现按需编译,冷启动速度较Webpack提升10倍以上,成为Vue3、React18项目的首选;Turbopack依托Rust语言,在大型项目中性能优势显著,但生态尚在完善中。选择构建工具的核心原则的是“适配项目规模+团队技术栈”,中小项目优先选Vite,复杂项目可选用Webpack搭配Vite做开发环境优化。

1.2 代码规范:多人协作的“统一语言”

多人协作开发中,代码风格不一致、语法错误、潜在bug等问题,会大幅增加代码评审成本与维护难度。代码规范体系通过“自动化校验+格式化”,强制约束编码行为,确保团队代码风格统一、质量可控。其核心包含两部分:一是代码语法与风格规范(如变量命名、缩进格式、语句规范),二是代码质量规范(如避免死循环、禁止未使用变量、规范函数复杂度)。

主流工具组合为“ESLint+Prettier”:ESLint负责语法校验与质量检测,可针对性配置Vue/React/TypeScript规则;Prettier专注代码格式化,统一缩进、引号、换行等风格,两者配合可实现“编码即规范”。同时,通过husky在Git提交环节添加钩子,可强制拦截不符合规范的代码,从源头保障代码质量。

1.3 自动化测试:保障项目稳定性的“护城河”

前端项目迭代过程中,新增功能或修改代码极易引发“回归bug”,手动测试效率低、覆盖范围有限,难以满足企业级项目的稳定性需求。自动化测试通过编写测试用例,自动验证代码功能正确性,覆盖单元测试、组件测试、E2E测试等场景,实现“代码变更即验证”,大幅降低回归bug风险。

前端自动化测试工具生态已趋于成熟:Jest是通用型测试框架,支持断言、模拟、覆盖率统计,适配Vue/React项目;Vue Test Utils与React Testing Library分别为Vue、React组件测试提供专用API,模拟组件渲染与交互;Vitest作为Vite生态的测试工具,兼容Jest API,测试速度更快,成为Vue3项目的优选。企业级项目通常采用“单元测试+组件测试”组合,覆盖率目标建议不低于70%。

1.4 部署流程:从开发到上线的“自动化通道”

传统前端部署依赖手动打包、上传服务器,流程繁琐且易因环境差异导致线上问题。现代前端部署流程通过CI/CD(持续集成/持续部署)工具,实现“代码提交→自动构建→自动测试→自动部署”全链路自动化,缩短迭代周期,降低人为失误。同时,结合环境隔离(开发、测试、预发布、生产)、版本控制、回滚机制,保障上线流程的稳定性与可控性。

主流CI/CD工具包括Jenkins、GitHub Actions、GitLab CI,其中GitHub Actions与GitLab CI无需本地部署,可直接与代码仓库联动,配置简单,适合中小团队;Jenkins自定义能力强,适合复杂的部署场景。部署目标可根据项目需求选择静态资源服务器(Nginx)、云存储(OSS)、Serverless平台(Vercel、Netlify)等。

二、实战搭建:基于Vite+Vue3/React18的企业级脚手架

脚手架是前端工程化的“落地载体”,一个完善的企业级脚手架需集成构建工具、路由、状态管理、请求封装、代码规范、测试框架等核心能力,支持开发者快速启动项目,无需重复搭建基础架构。本节分别以Vite+Vue3、Vite+React18为技术栈,详细讲解企业级脚手架的搭建步骤,覆盖核心功能配置。

2.1 Vite+Vue3脚手架搭建(企业级标准)

2.1.1 基础项目初始化

首先确保本地已安装Node.js(版本≥18.0.0,建议使用nvm管理Node版本),执行以下命令初始化Vite+Vue3项目:

# 初始化项目,选择Vue+TypeScript
npm create vite@latest vue3-enterprise-scaffold -- --template vue-ts
# 进入项目目录
cd vue3-enterprise-scaffold
# 安装依赖
npm install
# 启动开发服务器
npm run dev

初始化完成后,项目基础结构包含src(源代码目录)、public(静态资源目录)、vite.config.ts(Vite配置文件)、tsconfig.json(TypeScript配置文件)等核心文件。接下来逐步完善企业级所需功能模块。

2.1.2 核心依赖安装与配置

安装路由、状态管理、请求封装等核心依赖:

# 安装路由(Vue Router 4.x)、状态管理(Pinia)、请求库(Axios)
npm install vue-router@4 pinia axios
# 安装UI组件库(以Element Plus为例,按需引入)
npm install element-plus @element-plus/icons-vue
# 安装样式预处理器(SCSS)
npm install sass --save-dev

路由配置(src/router/index.ts)

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
// 引入布局组件
import MainLayout from '@/layouts/MainLayout.vue'

// 路由规则
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    component: MainLayout,
    redirect: '/home',
    children: [
      {
        path: 'home',
        name: 'Home',
        component: () => import('@/views/HomeView.vue'),
        meta: { title: '首页', requiresAuth: false }
      }
    ]
  },
  // 登录页(无布局)
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/LoginView.vue'),
    meta: { title: '登录', requiresAuth: false }
  },
  // 404页面
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('@/views/NotFoundView.vue'),
    meta: { title: '页面不存在' }
  }
]

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

// 路由守卫:设置页面标题、权限校验
router.beforeEach((to) => {
  document.title = to.meta.title as string || 'Vue3企业级脚手架'
  // 权限校验逻辑(示例:未登录跳转登录页)
  const isLogin = localStorage.getItem('token')
  if (to.meta.requiresAuth && !isLogin) {
    return '/login'
  }
})

export default router

Pinia状态管理配置(src/store/modules/userStore.ts)

import { defineStore } from 'pinia'
import axios from '@/utils/request'

interface UserState {
  token: string | null
  userInfo: {
    username: string
    role: string
  } | null
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    token: localStorage.getItem('token'),
    userInfo: JSON.parse(localStorage.getItem('userInfo') || 'null')
  }),
  actions: {
    // 登录动作
    async login(username: string, password: string) {
      const res = await axios.post('/api/login', { username, password })
      const { token, userInfo } = res.data
      this.token = token
      this.userInfo = userInfo
      // 本地存储
      localStorage.setItem('token', token)
      localStorage.setItem('userInfo', JSON.stringify(userInfo))
      return res.data
    },
    // 退出登录
    logout() {
      this.token = null
      this.userInfo = null
      localStorage.removeItem('token')
      localStorage.removeItem('userInfo')
    }
  }
})

 Axios请求封装(src/utils/request.ts)

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { ElMessage, ElLoading } from 'element-plus'
import { useUserStore } from '@/store/modules/userStore'

// 创建Axios实例
const service: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL, // 环境变量配置基础地址
  timeout: 5000,
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  }
})

// 加载动画实例
let loadingInstance: ReturnType<typeof ElLoading.service> | null = null

// 请求拦截器:添加Token、显示加载动画
service.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    const userStore = useUserStore()
    // 添加Token到请求头
    if (userStore.token) {
      config.headers = config.headers || {}
      config.headers.Authorization = `Bearer ${userStore.token}`
    }
    // 显示加载动画(非GET请求)
    if (config.method !== 'get') {
      loadingInstance = ElLoading.service({
        lock: true,
        text: '加载中...',
        background: 'rgba(0, 0, 0, 0.5)'
      })
    }
    return config
  },
  (error) => {
    loadingInstance?.close()
    ElMessage.error('请求参数错误')
    return Promise.reject(error)
  }
)

// 响应拦截器:处理错误、隐藏加载动画
service.interceptors.response.use(
  (response: AxiosResponse) => {
    loadingInstance?.close()
    const { code, message, data } = response.data
    // 业务错误处理
    if (code !== 200) {
      ElMessage.error(message || '操作失败')
      // Token过期:退出登录并跳转登录页
      if (code === 401) {
        const userStore = useUserStore()
        userStore.logout()
        window.location.href = '/login'
      }
      return Promise.reject(new Error(message || 'Error'))
    }
    return data
  },
  (error) => {
    loadingInstance?.close()
    // 网络错误处理
    if (error.message.includes('timeout')) {
      ElMessage.error('请求超时,请重试')
    } else if (error.message.includes('404')) {
      ElMessage.error('接口不存在')
    } else {
      ElMessage.error('网络异常,请检查网络连接')
    }
    return Promise.reject(error)
  }
)

export default service

Vite配置优化(vite.config.ts)

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import path from 'path'
// Element Plus按需引入插件
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
  // 加载环境变量
  const env = loadEnv(mode, process.cwd())
  return {
    plugins: [
      vue(),
      vueJsx(),
      // 自动导入Element Plus API
      AutoImport({
        resolvers: [ElementPlusResolver()],
        dts: path.resolve(path.resolve(__dirname, 'src'), 'auto-imports.d.ts')
      }),
      // 自动导入Element Plus组件
      Components({
        resolvers: [ElementPlusResolver()],
        dts: path.resolve(path.resolve(__dirname, 'src'), 'components.d.ts')
      })
    ],
    // 路径别名配置
    resolve: {
      alias: {
        '@': path.resolve(__dirname, 'src'),
        '@assets': path.resolve(__dirname, 'src/assets'),
        '@components': path.resolve(__dirname, 'src/components')
      }
    },
    // 开发服务器配置
    server: {
      host: '0.0.0.0',
      port: Number(env.VITE_DEV_PORT) || 3000,
      open: true, // 自动打开浏览器
      proxy: {
        // 接口代理,解决跨域问题
        '/api': {
          target: env.VITE_API_BASE_URL,
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, '')
        }
      }
    },
    // 构建配置
    build: {
      outDir: 'dist',
      assetsDir: 'static',
      sourcemap: mode === 'development', // 开发环境生成sourcemap
      rollupOptions: {
        // 打包优化:分割代码
        output: {
          chunkFileNames: 'static/js/[name]-[hash].js',
          entryFileNames: 'static/js/[name]-[hash].js',
          assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
        }
      }
    }
  }
})

环境变量配置(.env.development/.env.production)

.env.development(开发环境):

NODE_ENV=development
VITE_DEV_PORT=3000
VITE_API_BASE_URL=http://localhost:8080/api
VITE_APP_TITLE=Vue3企业级脚手架(开发环境)

.env.production(生产环境):

NODE_ENV=production
VITE_API_BASE_URL=https://api.example.com
VITE_APP_TITLE=Vue3企业级脚手架(生产环境)

2.2 Vite+React18脚手架搭建(企业级标准)

2.2.1 基础项目初始化

# 初始化项目,选择React+TypeScript
npm create vite@latest react18-enterprise-scaffold -- --template react-ts
# 进入项目目录
cd react18-enterprise-scaffold
# 安装依赖
npm install
# 启动开发服务器
npm run dev

2.2.2 核心依赖安装与配置

 路由配置(src/router/index.tsx)

import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom'
import { lazy, Suspense } from 'react'
// 加载组件
import Loading from '@/components/Loading'
// 布局组件
import MainLayout from '@/layouts/MainLayout'

// 懒加载页面组件
const Home = lazy(() => import('@/views/Home'))
const Login = lazy(() => import('@/views/Login'))
const NotFound = lazy(() => import('@/views/NotFound'))

// 权限校验组件
const RequireAuth = ({ children }: { children: React.ReactNode }) => {
  const token = localStorage.getItem('token')
  if (!token) {
    return <Navigate to="/login" replace />
  }
  return <>{children}</>
}

// 路由配置
const router = createBrowserRouter([
  {
    path: '/',
    element: (
      <RequireAuth>
        <MainLayout />
      </RequireAuth>
    ),
    children: [
      {
        path: '/',
        element: <Navigate to="/home" replace />
      },
      {
        path: 'home',
        element: (
          <Suspense fallback={<Loading />}>
            <Home />
          </Suspense>
        )
      }
    ]
  },
  {
    path: '/login',
    element: (
      <Suspense fallback={<Loading />}>
        <Login />
      </Suspense>
    )
  },
  {
    path: '*',
    element: <NotFound />
  }
])

// 路由提供者组件
export default function AppRouter() {
  return <RouterProvider router={router} />
}

 Redux状态管理配置(src/store/modules/userSlice.ts)

import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
import axios from '@/utils/request'

interface UserState {
  token: string | null
  userInfo: {
    username: string
    role: string
  } | null
  loading: boolean
  error: string | null
}

// 初始状态
const initialState: UserState = {
  token: localStorage.getItem('token'),
  userInfo: JSON.parse(localStorage.getItem('userInfo') || 'null'),
  loading: false,
  error: null
}

// 异步登录动作
export const loginAsync = createAsyncThunk(
  'user/login',
  async ({ username, password }: { username: string; password: string }, { rejectWithValue }) => {
    try {
      const res = await axios.post('/api/login', { username, password })
      return res.data
    } catch (error: any) {
      return rejectWithValue(error.response?.data?.message || '登录失败')
    }
  }
)

// 用户Slice
const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    // 退出登录
    logout: (state) => {
      state.token = null
      state.userInfo = null
      localStorage.removeItem('token')
      localStorage.removeItem('userInfo')
    },
    clearError: (state) => {
      state.error = null
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(loginAsync.pending, (state) => {
        state.loading = true
        state.error = null
      })
      .addCase(loginAsync.fulfilled, (state, action: PayloadAction<{ token: string; userInfo: any }>) => {
        state.loading = false
        state.token = action.payload.token
        state.userInfo = action.payload.userInfo
        localStorage.setItem('token', action.payload.token)
        localStorage.setItem('userInfo', JSON.stringify(action.payload.userInfo))
      })
      .addCase(loginAsync.rejected, (state, action) => {
        state.loading = false
        state.error = action.payload as string
      })
  }
})

export const { logout, clearError } = userSlice.actions
export default userSlice.reducer

全局Store配置(src/store/index.ts)

import { configureStore } from '@reduxjs/toolkit'
import userReducer from './modules/userSlice'

export const store = configureStore({
  reducer: {
    user: userReducer
    // 后续可添加更多模块
  },
  middleware: (getDefaultMiddleware) => 
    getDefaultMiddleware({
      serializableCheck: false // 允许非序列化值(如Date)
    })
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

Vite配置、Axios封装与Vue3版本类似,可参考上文调整,核心差异在于框架专属插件与语法适配,此处不再赘述。脚手架搭建完成后,需集成代码规范、自动化测试模块,形成完整的工程化体系。

三、规范落地:ESLint+Prettier+husky打造闭环校验体系

代码规范的核心是“自动化强制执行”,而非依赖开发者自觉。本节通过ESLint+Prettier配置实现语法与风格校验,结合husky与lint-staged在Git提交环节拦截不规范代码,打造“编码→保存→提交”全链路规范校验体系。

3.1 基础依赖安装

# 安装ESLint及Vue/React适配插件
npm install eslint eslint-plugin-vue @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev # Vue3项目
# npm install eslint eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev # React18项目

# 安装Prettier及与ESLint兼容插件
npm install prettier eslint-config-prettier eslint-plugin-prettier --save-dev

# 安装husky与lint-staged(Git提交钩子)
npm install husky lint-staged --save-dev

# 初始化husky
npx husky install

在package.json中添加脚本,确保项目启动时自动启用husky:

"scripts": {
  "prepare": "husky install"
}

3.2 ESLint配置(.eslintrc.js)

Vue3+TypeScript项目配置

module.exports = {
  root: true,
  env: {
    browser: true,
    es2021: true,
    node: true
  },
  // 解析器配置
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
    ecmaVersion: 'latest',
    sourceType: 'module'
  },
  // 插件配置
  plugins: ['vue', '@typescript-eslint', 'prettier'],
  // 扩展配置:整合ESLint、Vue、Prettier规则
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-essential',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended' // 使Prettier规则优先级高于ESLint
  ],
  // 自定义规则
  rules: {
    // 关闭console(生产环境)
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    // 关闭debugger
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    // TypeScript规则:允许any类型
    '@typescript-eslint/no-explicit-any': 'off',
    // Vue规则:允许单文件组件无setup语法糖
    'vue/setup-compiler-macros': 'off',
    // Prettier规则:将格式化错误视为错误
    'prettier/prettier': 'error'
  }
}

3.3 Prettier配置(.prettierrc.js)

module.exports = {
  printWidth: 120, // 每行最大长度
  tabWidth: 2, // 缩进宽度
  useTabs: false, // 使用空格代替Tab
  semi: true, // 语句末尾加分号
  singleQuote: true, // 使用单引号
  quoteProps: 'as-needed', // 仅在必要时为对象属性加引号
  jsxSingleQuote: false, // JSX使用双引号
  trailingComma: 'all', // 数组、对象末尾加逗号
  bracketSpacing: true, // 对象字面量括号内加空格
  bracketSameLine: false, // 标签闭合括号换行
  arrowParens: 'always', // 箭头函数参数始终加括号
  rangeStart: 0,
  rangeEnd: Infinity,
  requirePragma: false,
  insertPragma: false,
  proseWrap: 'preserve',
  htmlWhitespaceSensitivity: 'css',
  vueIndentScriptAndStyle: true, // Vue单文件组件中脚本和样式缩进
  endOfLine: 'lf', // 行结束符使用LF
  embeddedLanguageFormatting: 'auto'
}

3.4 Git提交钩子配置(husky+lint-staged)

配置lint-staged(package.json)

"lint-staged": {
  "*.{js,jsx,ts,tsx,vue}": [
    "eslint --fix", // 自动修复ESLint错误
    "prettier --write" // 自动格式化
  ],
  "*.{css,scss,less}": [
    "prettier --write"
  ],
  "*.{json,md}": [
    "prettier --write"
  ]
}

 添加husky钩子

# 添加pre-commit钩子:提交前执行lint-staged
npx husky add .husky/pre-commit "npx lint-staged"

# 添加commit-msg钩子:校验提交信息格式(可选,推荐)
npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"

# 安装commitlint(如需校验提交信息)
npm install @commitlint/cli @commitlint/config-conventional --save-dev

创建commitlint配置文件(commitlint.config.js):

module.exports = {
  extends: ['@commitlint/config-conventional']
}

配置完成后,每次Git提交时,会先对暂存区文件执行ESLint修复与Prettier格式化,若存在无法自动修复的错误,将拦截提交并提示,从源头保障代码规范。

四、自动化测试入门:Jest+组件测试框架实战

自动化测试的核心目标是“验证功能正确性”与“预防回归bug”,前端测试优先覆盖核心业务逻辑与公共组件,本节以Vue3+Vitest、React18+Jest为例,讲解单元测试与组件测试的基础实现。

4.1 Vue3+Vitest测试配置

4.1.1 依赖安装

# 安装Vitest、Vue Test Utils、测试相关依赖
npm install vitest @vitest/ui @vue/test-utils jsdom --save-dev
# 安装测试覆盖率工具
npm install @vitest/coverage-v8 --save-dev

4.1.2 配置文件(vitest.config.ts)

import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  },
  test: {
    environment: 'jsdom', // 模拟浏览器环境
    globals: true, // 全局注入test、expect等API
    setupFiles: ['./src/test/setup.ts'], // 测试初始化文件
    coverage: {
      provider: 'v8',
      include: ['src/**/*.{vue,ts}'],
      exclude: ['src/main.ts', 'src/router/**', 'src/store/**'],
      reporter: ['text', 'html'] // 生成文本与HTML格式覆盖率报告
    }
  }
})

4.1.3 编写测试用例

组件测试(src/components/Button/Button.test.tsx)

import { mount } from '@vue/test-utils'
import Button from './Button.vue'

describe('Button组件', () => {
  // 测试默认渲染
  it('默认渲染正确的文本与样式', () => {
    const wrapper = mount(Button, {
      props: {
        label: '测试按钮'
      }
    })
    // 断言:按钮文本正确
    expect(wrapper.text()).toBe('测试按钮')
    // 断言:默认样式类存在
    expect(wrapper.classes()).toContain('btn-default')
  })

  // 测试类型属性
  it('不同type属性渲染不同样式', () => {
    const wrapper = mount(Button, {
      props: {
        label: '主要按钮',
        type: 'primary'
      }
    })
    expect(wrapper.classes()).toContain('btn-primary')
  })

  // 测试点击事件
  it('点击按钮触发click事件', async () => {
    const mockClick = vi.fn()
    const wrapper = mount(Button, {
      props: {
        label: '点击按钮',
        onClick: mockClick
      }
    })
    // 模拟点击
    await wrapper.trigger('click')
    // 断言:事件被调用
    expect(mockClick).toHaveBeenCalled()
  })
})

 工具函数测试(src/utils/format.test.ts)

import { formatDate } from './format'

describe('formatDate函数', () => {
  it('格式化日期为YYYY-MM-DD格式', () => {
    const date = new Date('2026-01-01')
    expect(formatDate(date)).toBe('2026-01-01')
  })

  it('处理空值返回空字符串', () => {
    expect(formatDate(null)).toBe('')
    expect(formatDate(undefined)).toBe('')
  })
})

4.1.4 运行测试脚本(package.json)

"scripts": {
  "test": "vitest",
  "test:ui": "vitest --ui", // 可视化测试界面
  "test:coverage": "vitest run --coverage" // 生成覆盖率报告
}

4.2 React18+Jest测试配置

4.2.1 依赖安装

# 安装Jest、React Testing Library、测试相关依赖
npm install jest @testing-library/react @testing-library/jest-dom @testing-library/user-event --save-dev
# 安装TypeScript适配插件
npm install ts-jest @types/jest --save-dev

4.2.2 配置文件(jest.config.js)

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '\\.(css|scss)$': 'identity-obj-proxy' // 模拟样式文件
  },
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
  testMatch: ['<rootDir>/src/**/*.test.(ts|tsx|js|jsx)'],
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
  coverageReporters: ['text', 'html'],
  collectCoverageFrom: [
    'src/**/*.{ts,tsx,js,jsx}',
    '!src/main.tsx',
    '!src/router/**',
    '!src/store/**'
  ]
}

4.2.3 编写测试用例

组件测试(src/components/Button/Button.test.tsx)

import { render, screen, fireEvent } from '@testing-library/react'
import Button from './Button'

describe('Button组件', () => {
  it('渲染正确的文本与样式', () => {
    render(<Button label="测试按钮" />)
    const buttonElement = screen.getByText('测试按钮')
    // 断言:组件渲染成功
    expect(buttonElement).toBeInTheDocument()
    // 断言:默认样式存在
    expect(buttonElement).toHaveClass('btn-default')
  })

  it('不同type属性渲染不同样式', () => {
    render(<Button label="主要按钮" type="primary" />)
    const buttonElement = screen.getByText('主要按钮')
    expect(buttonElement).toHaveClass('btn-primary')
  })

  it('点击按钮触发onClick事件', () => {
    const mockClick = jest.fn()
    render(<Button label="点击按钮" onClick={mockClick} />)
    const buttonElement = screen.getByText('点击按钮')
    // 模拟点击
    fireEvent.click(buttonElement)
    // 断言:事件被调用
    expect(mockClick).toHaveBeenCalledTimes(1)
  })
})

运行测试脚本与Vue3项目类似,在package.json中配置脚本后执行即可。企业级项目中,建议优先覆盖公共组件、工具函数、核心业务逻辑,逐步提升测试覆盖率。

五、性能优化实战:从打包到上线的全链路优化

前端性能直接影响用户体验与业务转化,性能优化需贯穿“开发→打包→部署→运行”全链路。本节聚焦打包优化、首屏加载优化、缓存策略三大核心场景,提供可落地的优化方案与实战案例。

5.1 打包优化:减小资源体积,提升构建效率

5.1.1 代码分割与懒加载

通过代码分割将大文件拆分为多个小文件,结合路由懒加载、组件懒加载,实现“按需加载”,减少初始加载资源体积。Vite默认支持代码分割,可通过rollupOptions进一步优化:

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        // 按模块分割代码
        manualChunks: {
          vue: ['vue', 'vue-router', 'pinia'],
          elementPlus: ['element-plus'],
          axios: ['axios']
        }
      }
    }
  }
})

路由懒加载已在脚手架搭建环节实现,组件懒加载示例(Vue3):

<template>
  <div>
    <button @click="showModal = true">打开弹窗</button>
    <Teleport to="body">
      <Modal v-if="showModal" @close="showModal = false" />
    </Teleport>
  </div>
</template>

<script setup lang="ts">
import { ref, defineAsyncComponent } from 'vue'
// 懒加载组件
const Modal = defineAsyncComponent(() => import('@/components/Modal.vue'))
const showModal = ref(false)
</script>

5.1.2 依赖优化:剔除无用代码,替换轻量库

  • Tree Shaking:Vite默认开启Tree Shaking,剔除未使用的代码,需确保代码符合ESModule规范(避免使用CommonJS)。

  • 按需引入UI组件库:如Element Plus、Ant Design,通过插件自动按需引入组件与样式,避免全量引入导致体积过大。

  • 替换重依赖:将体积大的库替换为轻量替代方案,如用dayjs替换moment.js(体积缩小80%),用lodash-es替换lodash(支持Tree Shaking)。

5.1.3 资源压缩与优化

 图片优化

安装vite-plugin-imagemin插件压缩图片:

npm install vite-plugin-imagemin --save-dev
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import imagemin from 'vite-plugin-imagemin'

export default defineConfig({
  plugins: [
    vue(),
    imagemin({
      gifsicle: {
        optimizationLevel: 7,
        interlaced: false
      },
      optipng: {
        enabled: true
      },
      pngquant: {
        quality: [0.6, 0.8]
      },
      mozjpeg: {
        quality: 80
      },
      webp: {
        quality: 80
      }
    })
  ]
})

同时,推荐使用WebP格式图片(体积较PNG小50%),通过picture标签适配不同浏览器:

<picture>
  <source srcset="@assets/images/example.webp" type="image/webp">
  <img src="@assets/images/example.png" alt="示例图片">
</picture>

CSS压缩与提取

Vite默认压缩CSS,可通过build.cssCodeSplit配置提取CSS为单独文件:

// vite.config.ts
export default defineConfig({
  build: {
    cssCodeSplit: true, // 提取CSS为单独文件
    minify: 'terser' // 使用terser压缩JS/CSS
  }
})

5.2 首屏加载优化:缩短白屏时间

5.2.1 预加载与预连接

通过link标签实现资源预加载、预连接,提升资源加载速度:

&lt;!-- public/index.html --&gt;
&lt;head&gt;
  <!-- 预连接API服务器 -->
  <link rel="preconnect" href="https://api.example.com">
  <!-- 预加载核心JS文件 -->
  <link rel="preload" href="/static/js/main-[hash].js" as="script"&gt;
  <!-- 预加载字体文件 -->
  <link rel="preload" href="/static/fonts/iconfont.woff2" as="font" type="font/woff2" crossorigin>
</head>

5.2.2 骨架屏与加载状态优化

首屏加载时显示骨架屏,替代白屏,提升用户感知体验。Vue3项目可通过组件实现骨架屏:

<template>
  <div class="home-page">
    <!-- 骨架屏 -->
    <HomeSkeleton v-if="loading" />
    <!-- 真实内容 -->
    <div v-else>
      <!-- 页面内容 -->
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import HomeSkeleton from './HomeSkeleton.vue'
import { fetchHomeData } from '@/api/home'

const loading = ref(true)

onMounted(async () => {
  await fetchHomeData()
  loading.value = false
})
</script>

5.2.3 服务端渲染(SSR)/静态站点生成(SSG)

对于首屏加载速度要求极高的项目(如官网、电商首页),可采用SSR或SSG。Vue3可通过Nuxt.js、React18可通过Next.js实现,将组件渲染为HTML字符串发送给浏览器,大幅缩短首屏加载时间。

5.3 缓存策略:减少重复请求,提升二次加载速度

5.3.1 HTTP缓存配置(Nginx)

通过Nginx配置HTTP缓存头,实现静态资源的浏览器缓存与协商缓存:

server {
  listen 80;
  server_name example.com;
  root /usr/share/nginx/html;

  # 静态资源缓存配置
  location ~* \.(js|css|png|jpg|webp|woff2)$ {
    # 强缓存:资源未过期直接使用本地缓存(7天)
    expires 7d;
    # 协商缓存:资源过期后验证ETag/Last-Modified
    add_header Cache-Control "public, max-age=604800";
    # 指纹文件(含hash)永久缓存
    if ($request_filename ~* \.(js|css|png|jpg|webp|woff2)\.[0-9a-f]{8,}\.) {
      expires 1y;
      add_header Cache-Control "public, max-age=31536000, immutable";
    }
  }

  # 入口文件(index.html)禁用强缓存,启用协商缓存
  location = /index.html {
    expires -1;
    add_header Cache-Control "no-cache";
  }
}

5.3.2 接口缓存策略

对于不常变化的接口数据,可通过本地存储(localStorage/sessionStorage)或请求缓存(Axios拦截器)实现缓存:

// src/utils/request.ts(Axios接口缓存示例)
import axios from 'axios'

// 接口缓存对象
const requestCache = new Map()

// 请求拦截器
service.interceptors.request.use((config) => {
  // 开启缓存的接口,从缓存中获取数据
  if (config.cache) {
    const cacheKey = config.url + JSON.stringify(config.params)
    const cacheData = requestCache.get(cacheKey)
    if (cacheData) {
      return Promise.resolve({ data: cacheData })
    }
  }
  return config
})

// 响应拦截器
service.interceptors.response.use((response) => {
  // 开启缓存的接口,存储数据到缓存
  const config = response.config
  if (config.cache) {
    const cacheKey = config.url + JSON.stringify(config.params)
    requestCache.set(cacheKey, response.data)
    // 设置缓存过期时间(可选)
    setTimeout(() => {
      requestCache.delete(cacheKey)
    }, config.cacheExpire || 300000) // 默认5分钟过期
  }
  return response
})

六、总结:前端工程化的进阶之路

前端工程化并非一成不变的标准,而是随着技术发展不断迭代的体系。从搭建标准化脚手架,到落地代码规范、自动化测试,再到全链路性能优化,每一步都是对“高效、稳定、可维护”开发目标的追求。对于前端工程师而言,工程化能力的提升,不仅是工具的熟练使用,更是对“全流程把控”思维的培养——既要关注编码细节,也要兼顾项目整体质量与用户体验。

本文讲解的工程化方案,已覆盖企业级项目的核心需求,开发者可根据项目规模、团队技术栈灵活调整。在实际开发中,建议逐步落地、持续优化:先搭建基础脚手架与代码规范,再引入自动化测试保障质量,最后通过性能优化提升用户体验。随着工程化体系的不断完善,团队开发效率与项目质量将实现质的飞跃,为业务发展提供坚实的技术支撑。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐