Skip to content

Vue CLI

大白话解释: Vue CLI 就像"项目模板生成器"。手动搭建 Vue 项目需要配置 Webpack、Babel、ESLint、Router、Vuex 等很多东西,Vue CLI 帮你一键生成,开箱即用。

为什么用脚手架?

  • 省时间:不用手动配置 Webpack、Babel 等复杂工具
  • 最佳实践:官方推荐的项目结构和配置
  • 插件生态:需要什么功能,装个插件就行

Vue CLI vs Vite:

  • Vue CLI:基于 Webpack,功能成熟但启动慢,Vue 2 项目用
  • Vite:基于 ESBuild,启动快,Vue 3 项目推荐

⚠️ Vue CLI 已进入维护模式(不再开发新功能),Vue 3 项目推荐使用 Vite。详见 Vue CLI → Vite 迁移指南

Vue CLI 是 Vue 官方提供的脚手架工具,基于 Webpack 封装,用于快速创建、开发和构建 Vue 项目。Vue CLI 5 同时支持 Vue 2 和 Vue 3,但 Vue 3 新项目已推荐使用 Vite。


核心概念

Vue CLI 架构

┌─────────────────────────────────────────────────────────┐
│                     vue-cli-service                      │
│                                                          │
│  vue.config.js ──→ Webpack 配置合并 ──→ 最终 Webpack 配置  │
│                                                          │
│  configureWebpack(简单合并)                              │
│  chainWebpack(链式 API 修改)                             │
│                                                          │
│  内置 loader:js / vue / css / scss / less / ts / 图片     │
│  内置插件:html / define / copy / preload / prefetch       │
└─────────────────────────────────────────────────────────┘

configureWebpack vs chainWebpack

方式用法适用场景
configureWebpack直接写 Webpack 配置对象,自动 merge简单配置(加 alias、externals)
chainWebpack链式 API,可精确修改已有配置复杂操作(删插件、改 loader、条件判断)

💡 两者可以同时使用,chainWebpack 后执行,会覆盖 configureWebpack 的同名配置。


安装与版本管理

bash
# 全局安装
npm install -g @vue/cli

# 查看版本
vue --version

# 升级到最新
npm update -g @vue/cli

# 安装指定大版本
npm install -g @vue/cli@4   # 4.x 主要用于 Vue 2
npm install -g @vue/cli@5   # 5.x 同时支持 Vue 2 和 Vue 3

# 从旧版 webpack 模板迁移(兼容 vue init 语法)
npm install -g @vue/cli-init
vue init webpack my-project

创建项目

交互式创建

bash
vue create my-project

可选预设:

  • Default (Vue 3) — Vue 3 + Babel + ESLint
  • Default (Vue 2) — Vue 2 + Babel + ESLint
  • Manually select features — 手动勾选特性

手动可选特性:

  • Babel — ES6+ 语法转译
  • TypeScript — TS 支持
  • Router — vue-router
  • Vuex — 状态管理
  • CSS Pre-processors — Sass / Less / Stylus
  • Linter / Formatter — ESLint / Prettier
  • Unit Testing — Jest / Mocha
  • E2E Testing — Cypress / Nightwatch
  • PWA — 渐进式 Web 应用

命令行参数

bash
# 指定预设(跳过交互)
vue create my-project --preset __default_vue_2__

# 从 JSON 文件加载预设
vue create my-project --preset ./preset.json

# 指定包管理器
vue create my-project --packageManager yarn

# 跳过 Git 初始化
vue create my-project --no-git

预设文件

⚠️ JSON 不支持注释,实际使用时需去掉 // 注释部分。

json
{
  "useConfigFiles": true,      // true: 每个插件单独配置文件;false: 全部写入 package.json
  "plugins": {
    "@vue/cli-plugin-babel": {},
    "@vue/cli-plugin-eslint": {
      "config": "base",         // base | airbnb | standard
      "lintOn": ["save"]        // 保存时检查:["save"] | ["save", "commit"]
    },
    "@vue/cli-plugin-router": {
      "historyMode": true       // true: history 模式;false: hash 模式
    },
    "@vue/cli-plugin-vuex": {},
    "@vue/cli-plugin-typescript": {
      "classComponent": true,   // 是否使用 class 风格组件
      "useTsWithBabel": true    // 是否与 Babel 配合使用
    },
    "@vue/cli-plugin-css-preprocessor": {
      "sass": true              // sass | less | stylus
    }
  },
  "vueVersion": "2"             // "2" | "3"
}

项目开发命令

package.json 中的 scripts

jsonc
{
  "scripts": {
    "serve": "vue-cli-service serve",       // 启动开发服务器
    "build": "vue-cli-service build",       // 生产构建
    "lint": "vue-cli-service lint",         // ESLint 检查修复
    "test:unit": "vue-cli-service test:unit",   // 单元测试
    "test:e2e": "vue-cli-service test:e2e"      // E2E 测试
  }
}

开发服务器

bash
npm run serve                               # 启动(默认 localhost:8080)
npm run serve -- --port 3000                # 指定端口
npm run serve -- --open                     # 自动打开浏览器
npm run serve -- --host 0.0.0.0             # 允许局域网访问(手机调试)
npm run serve -- --https                    # 启用 HTTPS
npm run serve -- --port 3000 --open --host 0.0.0.0  # 组合使用

生产构建

bash
npm run build                               # 构建到 dist/
npm run build -- --report                   # 生成包体积分析报告(report.html)
npm run build -- --modern                   # 生成两份包:现代浏览器(ES Module)+ 旧浏览器兼容
npm run build -- --modern app               # 仅生成现代浏览器包
npm run build -- --no-clean                 # 构建前不清空 dist/

代码检查

bash
npm run lint                                # ESLint 检查并自动修复
npx vue-cli-service lint --no-fix           # 仅检查,不修复
npx vue-cli-service lint --fix src/         # 只修复指定目录

vue inspect(查看最终 Webpack 配置)

bash
vue inspect                                 # 输出完整 Webpack 配置
vue inspect --mode development              # 开发环境配置
vue inspect --mode production               # 生产环境配置
vue inspect --rule css                      # 只看 css 相关 loader
vue inspect --plugin html                   # 只看 html 插件配置
vue inspect > resolved.config.js            # 输出到文件方便查看

插件管理

bash
vue add typescript                          # 添加 TypeScript 支持
vue add router                              # 添加 vue-router
vue add vuex                                # 添加 Vuex
vue add sass                                # 添加 Sass 预处理器
vue add less                                # 添加 Less 预处理器
vue add @vue/cli-plugin-eslint              # 添加 ESLint
vue add @vue/cli-plugin-unit-jest           # 添加 Jest 单元测试
vue add @vue/cli-plugin-e2e-cypress         # 添加 Cypress E2E 测试
vue add @vue/cli-plugin-pwa                 # 添加 PWA 支持

⚠️ vue add 会修改项目文件(可能覆盖已有代码),建议在 Git 提交后再执行。


vue.config.js 完整配置

基本配置

js
// vue.config.js
const { defineConfig } = require('@vue/cli-service')
const path = require('path')

module.exports = defineConfig({
  // ========== 部署路径 ==========
  // 默认 '/',部署到子路径时需要修改
  // 例:部署到 https://example.com/my-app/ → 设为 '/my-app/'
  publicPath: process.env.NODE_ENV === 'production' ? '/my-app/' : '/',

  // ========== 构建输出 ==========
  outputDir: 'dist',              // 构建输出目录(相对于项目根目录)
  assetsDir: 'static',            // 静态资源子目录(相对于 outputDir)
  indexPath: 'index.html',        // 生成的 index.html 文件名
  filenameHashing: true,          // 文件名是否带哈希(用于缓存更新)
                                  // 设为 false 时所有文件名不带 hash

  // ========== 多页面模式 ==========
  // 默认是单页面(SPA),配置 pages 后变为多页面
  // 单页面时不需要配置此项
  pages: undefined,

  // /*
  // 多页面示例:
  // pages: {
  //   index: {
  //     entry: 'src/main.js',           // 入口文件
  //     template: 'public/index.html',  // HTML 模板
  //     filename: 'index.html',         // 输出文件名
  //     title: '首页',                   // HTML 标题
  //     chunks: ['chunk-vendors', 'chunk-common', 'index']  // 包含的 chunk
  //   },
  //   admin: {
  //     entry: 'src/admin/main.js',
  //     template: 'public/admin.html',
  //     filename: 'admin.html',
  //     title: '管理后台',
  //     chunks: ['chunk-vendors', 'chunk-common', 'admin']
  //   }
  // }
  // */

  // ========== 编译器选择 ==========
  // false(默认):使用 runtime-only 编译器,体积更小约 30%
  // true:使用完整编译器,支持 template 选项和字符串模板
  // 一般不需要开启,除非用到 runtime 编译器的特性
  runtimeCompiler: false,

  // ========== 依赖转译 ==========
  // 默认只转译 src 目录,node_modules 中的 ES6+ 代码不转译
  // 如果某个第三方库发布了 ES6+ 语法,需要在这里指定
  transpileDependencies: [
    'vue-echarts',      // 这个库用了 ES6+ 语法
    'resize-detector'
  ],

  // ========== Source Map ==========
  // 生产环境是否生成 Source Map
  // true:可调试线上代码,但暴露源码,且增大构建体积
  // false:不生成,推荐生产环境关闭
  productionSourceMap: false,

  // ========== ESLint ==========
  // 保存时是否检查
  // true:保存时检查  false:不检查
  // 'error':保存时报错(阻断编译)  'warning':保存时警告
  // process.env.NODE_ENV !== 'production':仅开发环境检查
  lintOnSave: process.env.NODE_ENV !== 'production',

  // ========== 跨域设置 ==========
  // HTML 中 <link> 和 <script> 的 crossorigin 属性
  // 'anonymous':不发送凭据  'use-credentials':发送凭据
  crossorigin: undefined,

  // ========== 子资源完整性 ==========
  // 为生成的 <link> 和 <script> 添加 integrity 属性
  integrity: false,
})

开发服务器配置(devServer)

js
module.exports = {
  devServer: {
    // 端口号,默认 8080
    port: 8080,

    // 主机地址
    // 'localhost':仅本机可访问
    // '0.0.0.0':局域网可访问(手机调试、团队联调)
    host: 'localhost',

    // 启动后自动打开浏览器
    open: true,

    // 是否启用 HTTPS(自动生成证书)
    https: false,

    // 编译错误时是否在浏览器中显示遮罩层
    overlay: {
      warnings: false,    // 警告不遮罩
      errors: true        // 错误显示遮罩
    },

    // ========== 代理配置(解决跨域) ==========
    // 原理:开发服务器作为中间人,转发请求到后端
    // 浏览器 → devServer(:8080) → 后端(:3000)
    // 因为是服务器间请求,不存在跨域问题
    proxy: {
      // 简单代理:/api/* → http://localhost:3000/api/*
      '/api': {
        target: 'http://localhost:3000',  // 后端地址
        changeOrigin: true                // 修改请求头中的 Host 为目标地址
      },

      // 路径重写:/api/v2/* → http://localhost:3000/v2/*
      // 适用于后端接口没有 /api 前缀的情况
      '/api/v2': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        pathRewrite: {
          '^/api/v2': '/v2'               // 正则替换路径
        }
      },

      // WebSocket 代理
      '/ws': {
        target: 'ws://localhost:3000',
        ws: true,                         // 启用 WebSocket 代理
        changeOrigin: true
      },

      // 多后端代理(微服务架构)
      '/user': {
        target: 'http://user-service:3001',   // 用户服务
        changeOrigin: true
      },
      '/order': {
        target: 'http://order-service:3002',  // 订单服务
        changeOrigin: true
      }
    },

    // 自定义中间件(mock 数据)
    // ⚠️ onBeforeSetupMiddleware 在 webpack-dev-server v5 中已废弃,
    // 推荐使用 setupMiddlewares 替代(Vue CLI 5.0.8+ 支持)
    // 以下为旧写法,保留仅供参考:
    onBeforeSetupMiddleware(devServer) {
      devServer.app.get('/api/mock/user', (req, res) => {
        res.json({ code: 0, data: { name: '张三', age: 25 } })
      })
    },

    // 推荐写法(webpack-dev-server v5+):
    // setupMiddlewares(middlewares, devServer) {
    //   devServer.app.get('/api/mock/user', (req, res) => {
    //     res.json({ code: 0, data: { name: '张三', age: 25 } })
    //   })
    //   return middlewares
    // },

    // Vue CLI 4 写法:
    // before(app) {
    //   app.get('/api/mock/user', (req, res) => {
    //     res.json({ code: 0, data: { name: '张三', age: 25 } })
    //   })
    // },

    // 热更新(默认 true)
    hot: true,

    // 文件监听选项(Docker / WSL 环境下热更新失效时开启 polling)
    watchOptions: {
      poll: false,            // true 或 1000:轮询模式
      aggregateTimeout: 300   // 防抖时间(ms)
    }
  }
}

configureWebpack(简单合并)

js
// 适合简单配置,直接写 Webpack 配置对象
// Vue CLI 会用 webpack-merge 自动合并,不会覆盖已有配置
const path = require('path')

module.exports = {
  configureWebpack: {
    // 路径别名
    resolve: {
      alias: {
        '@': path.resolve(__dirname, 'src'),
        '@components': path.resolve(__dirname, 'src/components'),
        '@views': path.resolve(__dirname, 'src/views'),
        '@utils': path.resolve(__dirname, 'src/utils'),
        '@assets': path.resolve(__dirname, 'src/assets'),
        '@api': path.resolve(__dirname, 'src/api')
      },
      // 省略的扩展名(从左到右优先级递减)
      extensions: ['.js', '.vue', '.json', '.ts', '.tsx']
    },

    // 外部依赖(CDN 引入,不打包进 bundle)
    // key: 包名(import 时用的名字)
    // value: 全局变量名(CDN 暴露到 window 上的变量)
    externals: {
      vue: 'Vue',                       // <script src="vue.js"> → window.Vue
      'vue-router': 'VueRouter',        // <script src="vue-router.js"> → window.VueRouter
      vuex: 'Vuex',                     // <script src="vuex.js"> → window.Vuex
      axios: 'axios',                   // <script src="axios.js"> → window.axios
      echarts: 'echarts',               // <script src="echarts.js"> → window.echarts
      lodash: '_',                      // <script src="lodash.js"> → window._
      moment: 'moment'                  // <script src="moment.js"> → window.moment
    },

    // 性能提示配置
    performance: {
      hints: process.env.NODE_ENV === 'production' ? 'warning' : false,
      maxAssetSize: 512000,             // 单个资源超过 500KB 警告
      maxEntrypointSize: 512000         // 入口 chunk 超过 500KB 警告
    }
  }
}

chainWebpack(链式 API)

js
// 适合复杂操作:删除/替换/条件修改已有配置
// 基于 webpack-chain 库,支持链式调用
const path = require('path')

module.exports = {
  chainWebpack: config => {
    // ========== 修改 HTML 插件 ==========
    config.plugin('html').tap(args => {
      args[0].title = 'My App'          // 修改 <title>
      return args
    })

    // ========== 路径别名 ==========
    config.resolve.alias
      .set('@', path.resolve(__dirname, 'src'))
      .set('@components', path.resolve(__dirname, 'src/components'))

    // ========== 删除预加载插件 ==========
    // prefetch:浏览器空闲时预加载,可能浪费带宽
    // preload:当前路由需要的资源预加载
    config.plugins.delete('prefetch')
    config.plugins.delete('preload')

    // ========== 修改 SVG loader ==========
    // 默认 SVG 被 file-loader 处理
    // 排除 src/icons 目录(用 svg-sprite-loader 处理图标)
    config.module
      .rule('svg')
      .exclude.add(path.resolve(__dirname, 'src/icons'))
      .end()

    // 为 src/icons 添加 svg-sprite-loader
    config.module
      .rule('icons')
      .test(/\.svg$/)
      .include.add(path.resolve(__dirname, 'src/icons'))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({ symbolId: 'icon-[name]' })  // symbol ID 格式
      .end()

    // ========== 图片压缩 ==========
    // 需要安装:npm install -D image-webpack-loader
    config.module
      .rule('images')
      .test(/\.(png|jpe?g|gif|webp|svg)(\?.*)?$/)
      .use('image-webpack-loader')
      .loader('image-webpack-loader')
      .options({
        mozjpeg: { progressive: true, quality: 65 },   // JPEG 压缩
        optipng: { enabled: false },                    // PNG 压缩(慢)
        pngquant: { quality: [0.65, 0.90], speed: 4 },  // PNG 压缩(快)
        gifsicle: { interlaced: false }                 // GIF 压缩
      })
      .end()

    // ========== 代码分割 ==========
    config.optimization.splitChunks({
      chunks: 'all',                      // 对所有 chunk 生效(initial / async / all)
      cacheGroups: {
        // 第三方库单独打包
        vendors: {
          name: 'chunk-vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: -10,                  // 优先级(数值越大越优先)
          chunks: 'initial'              // 只对同步导入的生效
        },
        // Element UI 单独打包(体积大,按需加载时有用)
        elementUI: {
          name: 'chunk-elementUI',
          priority: 20,                   // 优先级高于 vendors
          test: /[\\/]node_modules[\\/]element-ui[\\/]/
        },
        // ECharts 单独打包
        echarts: {
          name: 'chunk-echarts',
          priority: 20,
          test: /[\\/]node_modules[\\/]echarts[\\/]/
        },
        // 业务公共代码(被 2 个以上 chunk 引用的模块)
        common: {
          name: 'chunk-common',
          minChunks: 2,                   // 最少被引用次数
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true        // 已存在则复用
        }
      }
    })

    // ========== 生产环境优化 ==========
    if (process.env.NODE_ENV === 'production') {
      // 移除 console.log 和 debugger
      config.optimization.minimizer('terser').tap(args => {
        args[0].terserOptions.compress.drop_console = true    // 移除所有 console
        args[0].terserOptions.compress.drop_debugger = true   // 移除 debugger
        return args
      })
    }
  }
}

CSS 预处理器配置

js
module.exports = {
  css: {
    // 是否将 CSS 提取为独立文件
    // true(生产默认):提取为 .css 文件,利于缓存
    // false(开发默认):以 <style> 标签注入,支持热更新
    extract: process.env.NODE_ENV === 'production',

    // 是否生成 CSS Source Map
    sourceMap: false,

    // CSS Modules 的类名转换方式
    // true(默认):类名驼峰转换(.btn-primary → btnPrimary)
    // false:保持原始类名
    requireModuleExtension: true,

    // 各预处理器的 Loader 配置
    loaderOptions: {
      css: {
        // CSS Modules 配置
        modules: {
          // 类名格式:[name]文件名 [hash:base64:5]5位哈希
          localIdentName: '[name]-[hash:base64:5]'
        }
      },
      less: {
        // 全局注入 Less 变量(每个 .less 文件自动引入)
        // 不需要在每个文件手动 @import
        additionalData: `@import "@/styles/variables.less";`
      },
      sass: {
        // 全局注入 Sass 变量(Sass 新语法用 @use)
        additionalData: `@use "@/styles/variables" as *;`
      },
      scss: {
        // 全局注入 SCSS 变量
        additionalData: `@import "@/styles/variables.scss";`
      },
      postcss: {
        // PostCSS 插件(也可用 postcss.config.js 单独配置)
        plugins: [
          require('postcss-px-to-viewport')({
            viewportWidth: 375,           // 设计稿宽度
            unitPrecision: 5,             // 小数位数
            viewportUnit: 'vw'            // 转换单位
          })
        ]
      }
    }
  }
}

入口文件示例(main.js)

⚠️ 以下为 Vue 2 语法。Vue 3 使用 createApp() 创建应用,不再支持 Vue.component 全局注册、Vue.directiveVue.filterVue.prototype 等 API。

js
// src/main.js —— Vue 2 写法
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import dayjs from 'dayjs'

// 全局样式
import '@/styles/global.scss'

// 全局组件(如需要)
import BaseButton from '@/components/common/BaseButton.vue'
Vue.component('BaseButton', BaseButton)

// 全局指令(如需要)
Vue.directive('focus', {
  inserted: el => el.focus()
})

// 全局过滤器(如需要)
// ⚠️ Vue 3 已移除 filter API,建议用 computed 或函数替代
Vue.filter('dateFormat', (value, format = 'YYYY-MM-DD') => {
  return dayjs(value).format(format)
})

// 关闭生产提示
Vue.config.productionTip = false

new Vue({
  router,       // 注入路由
  store,        // 注入 Vuex
  render: h => h(App)
}).$mount('#app')

环境变量

Vue CLI 使用 VUE_APP_ 前缀暴露环境变量到客户端。Vite 项目使用 VITE_ 前缀,详见 环境变量管理

文件命名与加载顺序

.env                        所有环境都会加载
.env.local                  所有环境,git 忽略(本地覆盖)
.env.development            仅 development 模式
.env.development.local      仅 development,git 忽略
.env.production             仅 production 模式
.env.production.local       仅 production,git 忽略

优先级(从低到高):.env.env.local.env.[mode].env.[mode].local

命名规则

bash
# ✅ VUE_APP_ 开头 → 注入客户端(process.env.VUE_APP_XXX)
VUE_APP_API_URL=https://api.example.com
VUE_APP_TITLE=My App

# ✅ 内置变量(无需定义,自动可用)
NODE_ENV=development      # 自动设置:serve → development,build → production,test → test
BASE_URL=/                # 等于 publicPath 配置

# ❌ 不以 VUE_APP_ 开头 → 仅 Node.js 环境可用(服务端渲染、构建脚本)
# 注意:NODE_ENV 和 BASE_URL 由 Vue CLI 自动设置,不要手动定义在 .env 中
DB_HOST=localhost          # 客户端代码中无法访问
SECRET_KEY=xxx             # 不会暴露到前端

按环境构建

bash
npm run serve                           # 默认加载 .env.development
npm run build                           # 默认加载 .env.production
npm run serve --mode test               # 加载 .env.test
npm run build --mode staging            # 加载 .env.staging

使用示例

bash
# .env.development
VUE_APP_BASE_URL=http://localhost:3000
VUE_APP_ENV=development

# .env.production
VUE_APP_BASE_URL=https://api.example.com
VUE_APP_ENV=production
VUE_APP_CDN_URL=https://cdn.example.com
js
// src/utils/request.js
import axios from 'axios'

const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_URL,   // 根据环境自动切换
  timeout: 10000
})

export default service
html
<!-- public/index.html -->
<title><%= VUE_APP_TITLE %></title>
<link rel="icon" href="<%= BASE_URL %>favicon.ico">

静态资源处理

src/assets/ vs public/

特性src/assets/public/
Webpack 处理✅ 编译、压缩、哈希❌ 原样复制到 dist/
引用方式import / require() / 相对路径绝对路径字符串
小图优化✅ 自动 base64 内联❌ 不处理
适用场景组件级资源、需要优化的图片第三方库、favicon、manifest
vue
<template>
  <!-- src/assets 下的图片:Webpack 处理后输出带哈希的路径 -->
  <img src="./assets/logo.png">

  <!-- 动态路径:必须用 require() -->
  <img :src="require('./assets/' + iconName + '.png')">

  <!-- public 下的资源:直接用绝对路径 -->
  <img src="/images/banner.jpg">
</template>

<script>
// JS 中引入 src/assets
import logo from './assets/logo.png'
console.log(logo)  // 输出: /img/logo.3f8a2b12.png(带哈希)

// JS 中引用 public 下的资源
const url = `${process.env.BASE_URL}data/config.json`
</script>

部署配置

Nginx 部署

nginx
server {
    listen 80;
    server_name example.com;
    root /var/www/my-app/dist;

    # history 模式必须配置:所有路径回退到 index.html
    # 否则刷新页面会 404
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 静态资源长期缓存(文件名带 hash,更新后自动失效)
    location /static {
        expires 1y;                                   # 缓存 1 年
        add_header Cache-Control "public, immutable"; # 不再验证
    }

    # API 反向代理
    location /api {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

子路径部署

js
// vue.config.js
// 部署到 https://example.com/my-app/
module.exports = {
  publicPath: '/my-app/'
}

Docker 部署

dockerfile
FROM nginx:alpine
COPY dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

迁移到 Vite

配置对照

功能vue.config.jsvite.config.ts
部署路径publicPath: '/app/'base: '/app/'
端口devServer.port: 3000server.port: 3000
代理devServer.proxyserver.proxy
别名configureWebpack.resolve.aliasresolve.alias
全局变量注入loaderOptions.scss.additionalDatacss.preprocessorOptions.scss.additionalData
环境变量前缀VUE_APP_VITE_
入口 HTMLpublic/index.html根目录 index.html

迁移步骤

bash
# 1. 创建 Vite 项目
npm create vite@latest my-app -- --template vue

# 2. 复制源码
cp -r old-project/src my-app/src
cp -r old-project/public my-app/public

# 3. 替换环境变量前缀
# VUE_APP_API_URL → VITE_API_URL

# 4. 替换 require() 为 import
# const img = require('./logo.png') → import img from './logo.png'

# 5. 创建 vite.config.ts,删除 vue.config.js

项目结构

my-project/
├── public/
│   ├── index.html                # HTML 模板(EJS 语法可用环境变量)
│   ├── favicon.ico               # 网站图标
│   └── static/                   # 不经 Webpack 处理的静态资源
│       └── lib/                  # CDN 备份的第三方库
├── src/
│   ├── api/                      # API 请求封装
│   │   ├── index.js              # axios 实例配置
│   │   └── modules/              # 按模块拆分
│   │       ├── user.js
│   │       └── order.js
│   ├── assets/                   # Webpack 处理的静态资源
│   │   ├── images/
│   │   └── styles/
│   │       ├── variables.scss    # 全局变量
│   │       ├── mixins.scss       # 全局 mixin
│   │       └── global.scss       # 全局样式
│   ├── components/               # 公共组件
│   │   ├── common/               # 基础组件(按钮、弹窗等)
│   │   └── business/             # 业务组件
│   ├── views/                    # 页面组件(对应路由)
│   │   ├── Home.vue
│   │   ├── Login.vue
│   │   └── user/
│   ├── router/                   # 路由配置
│   │   └── index.js
│   ├── store/                    # Vuex 状态管理
│   │   ├── index.js              # Store 入口
│   │   └── modules/              # 模块化
│   │       ├── user.js
│   │       └── app.js
│   ├── utils/                    # 工具函数
│   │   ├── request.js            # axios 封装
│   │   ├── auth.js               # 权限工具
│   │   └── validate.js           # 表单校验规则
│   ├── mixins/                   # 全局混入
│   ├── directives/               # 自定义指令
│   ├── App.vue                   # 根组件
│   └── main.js                   # 入口文件
├── tests/
│   ├── unit/                     # 单元测试
│   └── e2e/                      # E2E 测试
├── .env                          # 环境变量(所有环境)
├── .env.development              # 开发环境变量
├── .env.production               # 生产环境变量
├── vue.config.js                 # Vue CLI 配置
├── babel.config.js               # Babel 配置
├── .eslintrc.js                  # ESLint 配置
├── .browserslistrc               # 浏览器兼容目标
├── postcss.config.js             # PostCSS 配置
├── tsconfig.json                 # TypeScript 配置(如使用 TS)
└── package.json

常见问题

1. 端口被占用

js
// vue.config.js
module.exports = {
  devServer: {
    port: 3000    // 改用其他端口
  }
}

2. 跨域

js
// vue.config.js — 配置代理
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',  // 后端地址
        changeOrigin: true,               // 修改 Host 头
        pathRewrite: { '^/api': '' }      // 去掉 /api 前缀
      }
    }
  }
}

3. 构建体积大

bash
# 分析哪些包最大
npm run build -- --report
js
// 排除大依赖,改为 CDN 引入
module.exports = {
  configureWebpack: {
    externals: {
      vue: 'Vue',
      'vue-router': 'VueRouter',
      vuex: 'Vuex',
      axios: 'axios',
      echarts: 'echarts'
    }
  }
}
html
<!-- public/index.html 中引入 CDN -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.min.js"></script>

4. 首屏加载慢

js
// 路由懒加载:按需加载页面代码
const Home = () => import(/* webpackChunkName: "home" */ '@/views/Home.vue')
const User = () => import(/* webpackChunkName: "user" */ '@/views/User.vue')

// 移除 prefetch(避免预加载不需要的页面)
// vue.config.js
module.exports = {
  chainWebpack: config => {
    config.plugins.delete('prefetch')
  }
}

5. Docker / WSL 热更新失效

js
// vue.config.js
module.exports = {
  devServer: {
    hot: true,
    watchOptions: {
      poll: 1000    // 开启轮询模式
    }
  }
}

6. Element UI 按需引入

⚠️ 以下方案适用于 Vue 2 + Element UI。Vue 3 项目应使用 Element Plus,按需引入推荐使用 unplugin-vue-components + unplugin-auto-import,详见 Element Plus 按需引入文档

bash
npm install -D babel-plugin-component
js
// babel.config.js
module.exports = {
  plugins: [
    ['component', {
      libraryName: 'element-ui',
      styleLibraryName: 'theme-chalk'
    }]
  ]
}
js
// src/plugins/element.js
import Vue from 'vue'
import { Button, Input, Table, Message } from 'element-ui'

Vue.use(Button)
Vue.use(Input)
Vue.use(Table)
Vue.prototype.$message = Message

常用命令速查

命令说明
vue create <name>创建项目
vue add <plugin>添加插件
vue serve快速原型开发(需先 npm i -g @vue/cli-service-global
vue build快速原型构建(需先 npm i -g @vue/cli-service-global
vue inspect查看最终 Webpack 配置
vue inspect --mode production查看生产环境配置
vue inspect --rule css查看指定 loader
vue ui图形化管理界面
npm run serve启动开发服务器
npm run serve -- --port 3000指定端口
npm run build生产构建
npm run build -- --report构建 + 包分析
npm run build -- --modern生成现代 + 兼容两份包
npm run lintESLint 检查修复

参考

个人学习笔记,部分内容借助 AI 辅助整理,仅供查阅参考,请以官方文档为准