Vue2+Vite+TypeScript大型分包项目搭建
基于Vue2+Vite+TypeScript的大型分包项目搭建过程。项目采用pnpm工作区管理,包含common公共模块、main主入口和pages业务模块的分包结构。环境要求Node.js 16+和pnpm 7+,配置了完整的TS、ESLint和代理设置。核心步骤包括:初始化目录结构、配置pnpm工作区、设置全局基础文件(tsconfig.json和eslintrc.js等),实现模块独立运行和
·
Vue2 + Vite + TypeScript 大型分包项目搭建详细过程
搭建一个基于 pnpm、Vue2、Vite、TypeScript 和 ElementUI 的大型分包前端项目,按 common/main/pages 目录结构分包、模块可独立运行、配置完整的依赖/TS/ESLint/代理,实现基础的登录-首页跳转功能。
一、运行环境要求
| 工具 | 最低版本要求 | 推荐版本 |
|---|---|---|
| Node.js | 16.0.0 | 18.18.2 |
| pnpm | 7.0.0 | 8.15.6 |
| npm | 8.0.0 | 9.8.1 |
| 操作系统 | Windows/macOS/Linux | 无 |
环境验证命令:
node -v # 需输出 v16+
pnpm -v # 需输出 7+
二、项目搭建完整步骤
步骤1:初始化项目目录结构
# 创建根目录
mkdir xx-business && cd xx-business
# 初始化pnpm工作区
pnpm init
# 创建核心目录 或 手动添加目录文件
mkdir -p common/src main/src pages/login/src pages/home/src
mkdir -p common/public main/public pages/login/public pages/home/public
mkdir -p common/types main/types pages/login/types pages/home/types
mkdir -p config static
步骤2:配置pnpm工作区(根目录)
项目根目录的核心文件结构应该是这样的:
xx-business/
├── common/ # 公共模块
│ ├── src/ # 公共源码
│ ├── package.json # 公共模块依赖
│ └── vite.config.ts # 公共模块Vite配置(之前补的)
├── main/ # 主入口模块
│ ├── src/ # 主入口源码
│ ├── index.html # 主入口HTML
│ ├── package.json # 主模块依赖
│ └── vite.config.ts # 主模块Vite配置(之前补的)
├── pages/ # 业务模块
│ ├── login/ # 登录模块
│ │ ├── src/
│ │ ├── package.json
│ │ └── vite.config.ts
│ └── home/ # 首页模块
│ ├── src/
│ ├── package.json
│ └── vite.config.ts
├── static/ # 全局静态资源(外置)
├── .env # 环境变量
├── .eslintrc.js # ESLint配置
├── .prettierrc # Prettier配置
├── package.json # 根目录依赖/脚本
├── pnpm-workspace.yaml # pnpm工作区
├── tsconfig.json # 全局TS配置(之前给的)
├── vite.config.base.ts # 基础Vite配置(根目录)
└── vite.config.proxy.ts # 代理配置(根目录)
1. 根目录 package.json
{
"name": "xx-business",
"version": "1.0.0",
"description": "Vue2 + Vite + TS 大型分包项目",
"private": true,
"scripts": {
"dev": "pnpm dev:main",
"dev:main": "pnpm -F main run dev",
"dev:common": "pnpm -F common run dev",
"build": "pnpm -F main run build",
"build:all": "pnpm -r --filter=\"./**\" run build",
"build:main": "pnpm -F main run build",
"build:common": "pnpm -F common run build",
"lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix",
"type-check": "tsc --noEmit"
},
"keywords": [
"vue2",
"vite",
"typescript",
"monorepo",
"pnpm"
],
"author": "xx <xuxiong9@qq.com>",
"license": "MIT",
"devDependencies": {
"@types/node": "^18.19.39",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.27.0",
"prettier": "^2.8.8",
"typescript": "^4.9.5",
"vite": "^4.5.3",
"vite-plugin-vue2": "^2.0.3",
"vue-eslint-parser": "^9.4.3"
},
"dependencies": {
"@types/echarts": "^4.9.22",
"@types/jquery": "^3.5.30",
"@types/js-cookie": "^3.0.6",
"@types/lodash": "^4.17.7",
"@types/vue-router": "^2.0.0",
"axios": "^1.7.2",
"core-js": "^3.37.1",
"dayjs": "^1.11.12",
"echarts": "^5.4.3",
"element-ui": "^2.15.14",
"jquery": "^3.7.1",
"js-cookie": "^3.0.5",
"lodash": "^4.17.21",
"vue": "^2.6.14",
"vue-router": "^3.6.5",
"vuex": "^3.6.2",
"sass": "~1.69.5",
"sass-loader": "^13.3.3"
}
}
2. 根目录 pnpm-workspace.yaml
packages:
# 公共目录,包含utils,plugins,全局样式
- 'common'
# 主入口
- 'main'
# 主目录下所有项目
- 'pages/*'
# 打包配置
- 'packages/*'
步骤3:配置全局基础文件(根目录)
1. tsconfig.json (全局TS配置)
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["ES2020", "DOM"],
"types": ["vite/client", "node", "vue2"],
"baseUrl": ".",
"paths": {
"/common/*": ["common/*"],
"/main/*": ["main/*"],
"/pages/*": ["pages/*"],
"@/*": ["./src/*"]
},
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"common/**/*.ts",
"common/**/*.d.ts",
"common/**/*.tsx",
"common/**/*.vue",
"main/**/*.ts",
"main/**/*.d.ts",
"main/**/*.tsx",
"main/**/*.vue",
"pages/**/*.ts",
"pages/**/*.d.ts",
"pages/**/*.tsx",
"pages/**/*.vue",
"typings/**/*.d.ts"
],
"exclude": ["node_modules", "dist", "**/dist/*"]
}
2. .eslintrc.js (ESLint配置)
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"],
extends: [
"eslint:recommended",
"plugin:vue/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
],
rules: {
"vue/script-setup-uses-vars": "error",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"@typescript-eslint/no-explicit-any": "warn",
"vue/multi-word-component-names": "off",
"prettier/prettier": ["error", { singleQuote: true, semi: false }],
},
};
3. .prettierrc (Prettier配置)
{
"singleQuote": true,
"semi": false,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"bracketSpacing": true
}
4. vite.config.base.ts (基础Vite配置)
import { defineConfig } from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'
import path from 'path'
import { fileURLToPath } from 'url'
// 解决 ESModule 中 __dirname 不存在的问题
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
export default defineConfig({
// 插件配置:核心是 Vue2 支持
plugins: [
createVuePlugin({
// Vue2 模板编译配置
vueTemplateOptions: {
compilerOptions: {
preserveWhitespace: false // 移除模板空格,优化体积
}
},
// 支持 Vue2.7 的 setup 语法糖
jsx: true
})
],
// 路径解析配置:核心是别名,确保 /common /main 能正确访问
resolve: {
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
alias: {
// 全局别名:所有模块都能通过 /common 访问公共目录
'/common': path.resolve(__dirname, './common'),
// 全局别名:所有模块都能通过 /main 访问主入口目录
'/main': path.resolve(__dirname, './main'),
'/pages': path.resolve(__dirname, './pages'), // 新指向根目录下的 pages 文件夹
// 模块内别名:@ 指向当前模块的 src 目录(每个模块自己的 src)
'@': path.resolve(__dirname, './src'),
// 根目录别名
'~': path.resolve(__dirname, './')
}
},
// 依赖预构建:优化大型项目启动速度
optimizeDeps: {
include: [
'vue', 'vue-router', 'vuex', 'axios', 'dayjs', 'lodash',
'element-ui', 'js-cookie', 'echarts', 'jquery'
],
exclude: ['vue-demi'] // 排除 Vue2/3 兼容层,避免冲突
},
// CSS 配置:全局样式、SCSS 变量
css: {
preprocessorOptions: {
scss: {
// 全局注入 SCSS 变量,所有模块都能直接用 $primary-color 等
additionalData: `@import "/common/src/styles/variables.scss";`,
silenceDeprecations: ['legacy-js-api', 'import'],
charset: false // 解决 charset 警告
}
},
// 提取 CSS 为单独文件(生产环境)
devSourcemap: true, // 开发环境开启 CSS SourceMap
postcss: {
plugins: [
// 可选:添加 autoprefixer 自动补全浏览器前缀
// require('autoprefixer')({ overrideBrowserslist: ['> 1%', 'last 2 versions'] })
]
}
},
// 构建基础配置(所有模块共用)
build: {
target: 'es2015', // 兼容现代浏览器
cssTarget: 'chrome80', // CSS 兼容目标
minify: 'terser', // 生产环境压缩
terserOptions: {
compress: {
drop_console: true, // 生产环境移除 console
drop_debugger: true // 生产环境移除 debugger
}
},
chunkSizeWarningLimit: 1500, // 分包大小警告阈值
assetsDir: 'static', // 静态资源输出目录
rollupOptions: {
output: {
// 分包命名规则:带 hash 便于缓存
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
}
}
}
})
5. vite.config.proxy.ts (代理配置)
// 全局代理配置:所有模块共用的接口代理规则
export default {
proxy: {
// 接口代理示例:/api 开头的请求转发到后端服务
'/api': {
target: 'http://localhost:8080', // 后端接口地址(根据你的实际地址修改)
changeOrigin: true, // 跨域请求时添加 Origin 头
rewrite: (path) => path.replace(/^\/api/, ''), // 移除 /api 前缀
timeout: 5000, // 超时时间
secure: false // 允许访问 https 且证书无效的服务
},
// 上传接口代理
'/upload': {
target: 'http://localhost:8080',
changeOrigin: true,
timeout: 10000, // 上传超时时间更长
secure: false
},
// 静态资源代理(可选)
'/static': {
target: 'http://localhost:8080',
changeOrigin: true,
secure: false
}
}
}
步骤4:配置common模块(公共目录)
1. common/package.json
{
"name": "common",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "vite --config vite.config.ts",
"build": "vite build",
"preview": "vite preview --config vite.config.ts"
},
"main": "./src/index.ts",
"types": "./types/index.d.ts"
}
common 模块 Vite 配置(common/vite.config.ts)
import { defineConfig, mergeConfig } from 'vite'
import path from 'path'
import { fileURLToPath, pathToFileURL } from 'url'
// 1. 解决 ESModule 中 __dirname 问题(关键:定位main目录)
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// 2. 导入根目录的 base 配置(相对路径:main目录 → 根目录)
// 方式1:相对路径(推荐,简单直接)
import baseConfig from '../vite.config.base.ts'
// 方式2:绝对路径(备用,防止相对路径出错)
// const baseConfigPath = path.resolve(__dirname, '../vite.config.base.ts')
// const baseConfig = (await import(pathToFileURL(baseConfigPath).href)).default
// 3. 导入根目录的 proxy 配置
import proxyConfig from '../vite.config.proxy.ts'
// 备用绝对路径写法:
// const proxyConfigPath = path.resolve(__dirname, '../vite.config.proxy.ts')
// const proxyConfig = (await import(pathToFileURL(proxyConfigPath).href)).default
// 4. 合并配置并导出(main模块专属配置 + 全局公共配置)
export default mergeConfig(
baseConfig,
defineConfig({
// ✅ 开发服务器配置(main专属:端口、自动打开、代理)
server: {
port: 9895, // main模块固定端口
open: true, // 启动后自动打开浏览器
host: '0.0.0.0', // 允许局域网访问
strictPort: true, // 端口被占用时不自动切换
...proxyConfig // 合并根目录的代理配置
},
// ✅ 构建配置(main专属:输出目录、清空目录)
build: {
outDir: path.resolve(__dirname, 'dist'), // main模块打包输出到 main/dist
emptyOutDir: true, // 打包前清空dist
reportCompressedSize: false // 关闭压缩体积报告(加快打包)
},
// ✅ 入口配置(main专属:根目录指向main、HTML入口)
root: __dirname, // Vite根目录 = main目录
resolve: {
alias: {
// 模块内别名:@ 指向 main/src
'@': path.resolve(__dirname, './src')
}
},
publicDir: path.resolve(__dirname, 'public'), // 静态资源目录 = main/public
envDir: path.resolve(__dirname, '../'), // 环境变量读取根目录的.env
// ✅ 日志配置(可选,优化开发体验)
logLevel: 'info',
clearScreen: true
})
)
common /index.html (主入口HTML)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue2 common入口</title>
<meta name="version" content="1.0.0" />
<meta name="author" content="9894664" />
<!-- <link rel="icon" href="/favicon.ico" /> -->
</head>
<body>
<div id="comApp"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
2. common/src/index.ts (公共模块入口)
// 导出公共工具
export * from './utils/axios'
export * from './utils/cookie'
// export * from './utils/date'
// export * from './utils/common'
// 导出公共组件
// export { default as CommonButton } from './components/CommonButton.vue'
// export { default as CommonTable } from './components/CommonTable.vue'
// 导出全局样式
import './styles/index.scss'
// 导出插件
export * from './plugins/element-ui'
// export * from './plugins/axios'
3. common/src/utils/cookie.ts (Cookie工具)
import Cookies from 'js-cookie'
// Token相关
export const TOKEN_KEY = 'USER_TOKEN'
/**
* 获取Token
*/
export const getToken = (): string | undefined => {
return Cookies.get(TOKEN_KEY)
}
/**
* 设置Token
* @param token Token值
* @param expires 过期时间(天)
*/
export const setToken = (token: string, expires = 7): void => {
Cookies.set(TOKEN_KEY, token, { expires })
}
/**
* 移除Token
*/
export const removeToken = (): void => {
Cookies.remove(TOKEN_KEY)
}
/**
* 检查是否登录
*/
export const isLogin = (): boolean => {
return !!getToken()
}
4. common/src/utils/axios.ts (Axios封装)
import axios, { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
import { getToken, removeToken } from './cookie'
import { Message } from 'element-ui'
// 创建axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 10000,
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
})
// 请求拦截器
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
// 添加Token
if (getToken()) {
config.headers = config.headers || {}
config.headers.Authorization = `Bearer ${getToken()}`
}
return config
},
(error: AxiosError) => {
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
const res = response.data
// 业务错误处理
if (res.code !== 200) {
Message.error(res.message || '请求失败')
// Token过期
if (res.code === 401) {
removeToken()
window.location.href = '/login'
}
return Promise.reject(res)
}
return res
},
(error: AxiosError) => {
Message.error(error.message || '服务器错误')
return Promise.reject(error)
}
)
export default service
5. common/src/styles/variables.scss (全局样式变量)
// 颜色变量
$primary-color: #409eff;
$success-color: #67c23a;
$warning-color: #e6a23c;
$danger-color: #f56c6c;
$info-color: #909399;
// 字体变量
$font-size-xs: 12px;
$font-size-sm: 14px;
$font-size-md: 16px;
$font-size-lg: 18px;
// 布局变量
$header-height: 60px;
$sidebar-width: 200px;
$footer-height: 40px;
// 圆角变量
$border-radius-sm: 4px;
$border-radius-md: 8px;
$border-radius-lg: 12px;
6. common/src/styles/index.scss (全局样式)
@import './variables.scss';
// 全局重置样式
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
font-size: $font-size-sm;
color: #333;
background-color: #f5f5f5;
}
// 全局通用样式
.el-container {
height: 100vh;
}
.el-header {
height: $header-height !important;
line-height: $header-height !important;
background-color: #fff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
.el-aside {
width: $sidebar-width !important;
background-color: #2e3b4e;
}
.el-main {
padding: 20px;
background-color: #f5f5f5;
}
// 自定义类名
.flex {
display: flex;
}
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.flex-between {
display: flex;
align-items: center;
justify-content: space-between;
}
7. common/src/plugins/element-ui.ts (ElementUI插件)
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
/**
* 注册ElementUI
* @param app Vue实例
*/
export const useElementUI = (app: Vue): void => {
app.use(ElementUI, {
size: 'medium',
zIndex: 3000
})
}
步骤5:配置main模块(主入口)
1. main/package.json
{
"name": "main",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "vite --config vite.config.ts",
"build": "vite build",
"preview": "vite preview --config vite.config.ts"
},
"dependencies": {
"common": "workspace:*"
}
}
main 模块 Vite 配置(main/vite.config.ts)
import { defineConfig, mergeConfig } from 'vite'
import path from 'path'
import { fileURLToPath, pathToFileURL } from 'url'
// 1. 解决 ESModule 中 __dirname 问题(关键:定位main目录)
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// 2. 导入根目录的 base 配置(相对路径:main目录 → 根目录)
// 方式1:相对路径(推荐,简单直接)
import baseConfig from '../vite.config.base.ts'
// 方式2:绝对路径(备用,防止相对路径出错)
// const baseConfigPath = path.resolve(__dirname, '../vite.config.base.ts')
// const baseConfig = (await import(pathToFileURL(baseConfigPath).href)).default
// 3. 导入根目录的 proxy 配置
import proxyConfig from '../vite.config.proxy.ts'
// 备用绝对路径写法:
// const proxyConfigPath = path.resolve(__dirname, '../vite.config.proxy.ts')
// const proxyConfig = (await import(pathToFileURL(proxyConfigPath).href)).default
// 4. 合并配置并导出(main模块专属配置 + 全局公共配置)
export default mergeConfig(
baseConfig,
defineConfig({
// ✅ 开发服务器配置(main专属:端口、自动打开、代理)
server: {
port: 9894, // main模块固定端口
open: true, // 启动后自动打开浏览器
host: '0.0.0.0', // 允许局域网访问
strictPort: true, // 端口被占用时不自动切换
...proxyConfig // 合并根目录的代理配置
},
// ✅ 构建配置(main专属:输出目录、清空目录)
build: {
outDir: path.resolve(__dirname, 'dist'), // main模块打包输出到 main/dist
emptyOutDir: true, // 打包前清空dist
reportCompressedSize: false // 关闭压缩体积报告(加快打包)
},
// ✅ 入口配置(main专属:根目录指向main、HTML入口)
publicDir: path.resolve(__dirname, 'public'), // 静态资源目录 = main/public
envDir: path.resolve(__dirname, '../'), // 环境变量读取根目录的.env
root: __dirname, // 正确:指向 main 目录
resolve: {
alias: {
// 模块内别名:@ 指向 main/src
'@': path.resolve(__dirname, './src')
}
},
// ✅ 日志配置(可选,优化开发体验)
logLevel: 'info',
clearScreen: true
})
)
main基础环境配置
NODE_ENV=development
VITE_API_BASE_URL=/api
VITE_APP_NAME=xx-business
2. main/index.html (主入口HTML)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue2 Vite主入口</title>
<meta name="version" content="1.0.0" />
<meta name="author" content="9894664" />
<!-- <link rel="icon" href="/favicon.ico" /> -->
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
3. main/src/main.ts (主入口TS)
import Vue from 'vue'
import App from './App.vue'
import router from '@/router/index'
import store from '@/store/index'
import { useElementUI } from '/common/src/plugins/element-ui'
import '/common/src/styles/index.scss'
import { isLogin } from '/common/src/utils/cookie'
console.log('✅ main.ts 入口文件加载成功')
// 生产环境提示关闭
Vue.config.productionTip = false
// 注册ElementUI
useElementUI(Vue)
// 路由守卫:未登录跳转到登录页
router.beforeEach((to, from, next) => {
if (to.path !== '/login' && !isLogin()) {
next('/login')
} else {
next()
}
})
// 创建Vue实例
new Vue({
router,
store,
render: (h) => h(App)
}).$mount('#app')
// 创建Vue实例
new Vue({
router,
store,
render: (h) => h(App)
}).$mount('#app')
4. main/src/App.vue (主入口组件)
<template>
<div id="app">
<router-view />
</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
name: "App",
mounted() {
console.log("项目版本号:1.0.0");
console.log("个人介绍:前端开发工程师,专注Vue2/Vue3生态开发");
},
});
</script>
<style scoped>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
width: 100%;
height: 100vh;
}
</style>
5. main/src/router/index.ts (主路由配置)
import Vue from 'vue'
import Router from 'vue-router'
import { getToken } from '/common/src/utils/cookie'
// 懒加载路由
const Login = () => import("/pages/login/src/views/Login.vue");
const Home = () => import("/pages/home/src/views/Home.vue");
Vue.use(Router)
const router = new Router({
mode: 'history',
base: import.meta.env.BASE_URL,
routes: [
{
path: '/',
redirect: '/home'
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/home',
name: 'Home',
component: Home,
meta: {
requiresAuth: true
}
},
{
path: '*',
redirect: '/'
}
]
})
export default router
6. main/src/store/index.ts (Vuex配置)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userInfo: {
name: '',
avatar: '',
roles: []
},
sidebar: {
opened: true
}
},
mutations: {
SET_USER_INFO(state, userInfo) {
state.userInfo = userInfo
},
TOGGLE_SIDEBAR(state) {
state.sidebar.opened = !state.sidebar.opened
}
},
actions: {
setUserInfo({ commit }, userInfo) {
commit('SET_USER_INFO', userInfo)
},
toggleSidebar({ commit }) {
commit('TOGGLE_SIDEBAR')
}
},
getters: {
getUserInfo: (state) => state.userInfo,
getSidebarStatus: (state) => state.sidebar.opened
},
modules: {}
})
步骤6:配置pages/login模块(登录页)
1. pages/login/package.json
{
"name": "login",
"version": "1.0.0",
"private": true,
"scripts": {},
"dependencies": {
"common": "workspace:*",
"main": "workspace:*"
}
}
2. login 模块 Vite 配置(pages/login/vite.config.ts)
import { defineConfig, mergeConfig } from 'vite'
import baseConfig from '../../vite.config.base.ts'
import proxyConfig from '../../vite.config.proxy.ts'
import path from 'path'
import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
export default mergeConfig(
baseConfig,
defineConfig({
server: {
port: 9896,
open: true,
...proxyConfig
},
build: {
outDir: path.resolve(__dirname, 'dist'),
emptyOutDir: true
},
root: __dirname,
publicDir: path.resolve(__dirname, 'public')
})
)
2. pages/login/src/Login.vue (登录组件)
<template>
<div class="login-container flex-center">
<el-card class="login-card" shadow="hover">
<div class="login-header flex-center">
<h2>系统登录</h2>
</div>
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="loginRules"
label-width="80px"
class="login-form"
>
<el-form-item label="用户名" prop="username">
<el-input
v-model="loginForm.username"
placeholder="请输入用户名"
prefix-icon="el-icon-user"
clearable
></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
prefix-icon="el-icon-lock"
clearable
show-password
></el-input>
</el-form-item>
<el-form-item label="验证码" prop="code">
<el-row :gutter="10">
<el-col :span="16">
<el-input
v-model="loginForm.code"
placeholder="请输入验证码"
prefix-icon="el-icon-check"
clearable
></el-input>
</el-col>
<el-col :span="8">
<div class="code-img flex-center">
1234
</div>
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<el-button
type="primary"
class="login-btn"
@click="handleLogin"
:loading="loading"
>
登录
</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import { setToken } from '/common/src/utils/cookie'
export default Vue.extend({
name: 'Login',
data() {
return {
loading: false,
loginForm: {
username: '',
password: '',
code: ''
},
loginRules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' }
],
code: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{ len: 4, message: '验证码长度为4位', trigger: 'blur' }
]
}
}
},
methods: {
async handleLogin() {
try {
// 表单验证
const valid = await (this.$refs.loginFormRef as any).validate()
if (!valid) return
this.loading = true
// 模拟登录请求
setTimeout(() => {
// 生成模拟Token
const mockToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjM0NTYiLCJpYXQiOjE3MTg4MDAwMDAsImV4cCI6MTcxOTQwNDgwMH0.8Z9Z9Z9Z9Z9Z9Z9Z9Z9Z9Z9Z9Z9Z9Z9Z9Z9Z9Z9Z9Z9'
// 设置Token
setToken(mockToken)
// 提示并跳转首页
this.$message.success('登录成功!')
this.$router.push('/home')
this.loading = false
}, 1000)
} catch (error) {
this.loading = false
this.$message.error('登录失败,请重试!')
console.error('登录错误:', error)
}
}
}
})
</script>
<style scoped lang="scss">
.login-container {
width: 100%;
height: 100vh;
background: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
.login-card {
width: 450px;
padding: 20px;
background-color: #fff;
border-radius: $border-radius-md;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
.login-header {
margin-bottom: 20px;
h2 {
color: $primary-color;
font-size: $font-size-lg;
font-weight: 600;
}
}
.login-form {
.code-img {
width: 100%;
height: 40px;
background-color: #f5f5f5;
border-radius: $border-radius-sm;
color: #666;
font-size: $font-size-md;
letter-spacing: 5px;
}
.login-btn {
width: 100%;
height: 40px;
font-size: $font-size-md;
}
}
}
}
</style>
步骤7:配置pages/home模块(首页)
1. pages/home/package.json
{
"name": "home",
"version": "1.0.0",
"private": true,
"scripts": {},
"dependencies": {
"common": "workspace:*",
"main": "workspace:*"
}
}
2. home 模块 Vite 配置(pages/home/vite.config.ts)
import { defineConfig, mergeConfig } from 'vite'
import baseConfig from '../../vite.config.base.ts'
import proxyConfig from '../../vite.config.proxy.ts'
import path from 'path'
import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
export default mergeConfig(
baseConfig,
defineConfig({
server: {
port: 9897,
open: true,
...proxyConfig
},
build: {
outDir: path.resolve(__dirname, 'dist'),
emptyOutDir: true
},
root: __dirname,
publicDir: path.resolve(__dirname, 'public')
})
)
2. pages/home/src/Home.vue (首页组件)
<template>
<el-container class="home-container">
<el-header class="header flex-between">
<div class="logo flex-center">
<h1>Vue2 Vite 分包项目</h1>
</div>
<div class="user-info flex-center">
<el-dropdown>
<span class="el-dropdown-link flex-center">
<el-avatar icon="el-icon-user" size="medium"></el-avatar>
<span class="username">管理员</span>
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="handleLogout">
<i class="el-icon-switch-button"></i> 退出登录
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</el-header>
<el-container>
<el-aside width="200px" class="sidebar">
<el-menu
default-active="1"
class="el-menu-vertical-demo"
background-color="#2e3b4e"
text-color="#fff"
active-text-color="#ffd04b"
>
<el-menu-item index="1">
<i class="el-icon-menu"></i>
<span slot="title">首页</span>
</el-menu-item>
<el-submenu index="2">
<template slot="title">
<i class="el-icon-location"></i>
菜单管理
</template>
<el-menu-item index="2-1">菜单1</el-menu-item>
<el-menu-item index="2-2">菜单2</el-menu-item>
</el-submenu>
<el-menu-item index="3">
<i class="el-icon-setting"></i>
<span slot="title">系统设置</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-main class="main-content">
<el-card>
<div class="welcome">
<h2>欢迎使用 Vue2 + Vite + TypeScript 大型分包项目</h2>
<p>当前版本:1.0.0</p>
<p>开发者:新时代农民工徐哈哈</p>
<p>技术栈:Vue2.7 + Vite4 + TypeScript + ElementUI + pnpm</p>
</div>
<div class="stats">
<el-row :gutter="20">
<el-col :span="6">
<el-card class="stat-card" shadow="hover">
<div class="stat-content">
<p class="stat-title">用户数</p>
<p class="stat-value">1,234</p>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card" shadow="hover">
<div class="stat-content">
<p class="stat-title">订单数</p>
<p class="stat-value">5,678</p>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card" shadow="hover">
<div class="stat-content">
<p class="stat-title">销售额</p>
<p class="stat-value">¥98,765</p>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card" shadow="hover">
<div class="stat-content">
<p class="stat-title">访问量</p>
<p class="stat-value">123,456</p>
</div>
</el-card>
</el-col>
</el-row>
</div>
</el-card>
</el-main>
</el-container>
</el-container>
</template>
<script lang="ts">
import Vue from 'vue'
import { removeToken } from '/common/src/utils/cookie'
export default Vue.extend({
name: 'Home',
methods: {
handleLogout() {
this.$confirm('确定要退出登录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 移除Token
removeToken()
// 跳转到登录页
this.$router.push('/login')
this.$message.success('退出登录成功!')
}).catch(() => {
this.$message.info('已取消退出')
})
}
}
})
</script>
<style scoped lang="scss">
.home-container {
.header {
.logo {
h1 {
font-size: $font-size-lg;
color: $primary-color;
margin: 0;
}
}
.user-info {
.username {
margin: 0 10px;
}
}
}
.sidebar {
.el-menu {
border-right: none;
height: 100%;
}
}
.main-content {
.welcome {
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
h2 {
color: $primary-color;
margin-bottom: 10px;
}
p {
color: $info-color;
line-height: 1.8;
}
}
.stats {
.stat-card {
.stat-content {
.stat-title {
color: $info-color;
font-size: $font-size-sm;
margin-bottom: 10px;
}
.stat-value {
color: $primary-color;
font-size: $font-size-lg;
font-weight: 600;
}
}
}
}
}
}
</style>
步骤8:安装依赖并运行项目
# 安装所有依赖
pnpm install
# 运行主入口项目(默认打开登录页)
pnpm dev / pnpm dev:main
# 打包
pnpm run build pnpm run build:all 等 看package配置

运行成功后,访问 http://localhost:9894 即可看到登录页面,输入任意用户名/密码(验证码填1234)即可登录跳转到首页。
三、项目优化方案
1. 性能优化
- 代码分割:通过Vite的rollup配置实现路由懒加载和chunk分割
- 静态资源优化:静态资源外置到static目录,通过CDN加速
- 依赖预构建:Vite optimizeDeps 预构建常用依赖
- 图片优化:使用vite-plugin-imagemin压缩图片
- CSS优化:提取CSS为单独文件,开启CSS压缩
2. 开发体验优化
- 类型检查:完整的TypeScript配置,提供类型提示
- ESLint+Prettier:代码规范和格式化
- 热更新:Vite原生热更新,提升开发效率
- 环境变量:配置不同环境的.env文件
3. 打包优化
- Tree Shaking:移除未使用代码
- 压缩混淆:Terser压缩JS,CSS压缩
- chunk大小控制:设置chunkSizeWarningLimit,避免大包
- 缓存优化:文件名添加hash,实现长效缓存
四、新增pages模块快速生成模板
新增模块脚本(根目录 create-page.js)
#!/usr/bin/env node
const fs = require('fs')
const path = require('path')
// 获取模块名称
const pageName = process.argv[2]
if (!pageName) {
console.error('请输入模块名称,例如:node create-page.js dashboard')
process.exit(1)
}
// 创建目录
const pagePath = path.join(__dirname, 'pages', pageName)
const srcPath = path.join(pagePath, 'src')
fs.mkdirSync(srcPath, { recursive: true })
// 创建package.json
const packageJson = {
name: pageName,
version: '1.0.0',
private: true,
scripts: {},
dependencies: {
"common": "workspace:*",
"main": "workspace:*"
}
}
fs.writeFileSync(
path.join(pagePath, 'package.json'),
JSON.stringify(packageJson, null, 2)
)
// 创建组件模板
const componentContent = `<template>
<div class="${pageName}-container">
<h1>${pageName} 模块</h1>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
name: '${pageName.charAt(0).toUpperCase() + pageName.slice(1)}',
data() {
return {}
},
mounted() {
console.log('${pageName} 模块加载完成')
}
})
</script>
<style scoped lang="scss">
.${pageName}-container {
padding: 20px;
}
</style>
`
fs.writeFileSync(
path.join(srcPath, `${pageName.charAt(0).toUpperCase() + pageName.slice(1)}.vue`),
componentContent
)
console.log(`✅ ${pageName} 模块创建成功!路径:${pagePath}`)
使用方法:
# 创建dashboard模块 需要独立运行则拷贝home文件夹下vite.config.ts
node create-page.js dashboard
五、总结
核心关键点回顾
- 环境要求:Node.js ≥16,pnpm ≥7,确保基础运行环境兼容
- 目录结构:common(公共)/main(主入口)/pages(业务模块)分离,各模块可独立运行
- 核心功能:
- 登录页通过Cookie模拟Token实现登录跳转
- 路由守卫实现未登录重定向
- 全局样式/工具/组件统一维护在common模块
- 配置要点:
- TS路径别名配置
/common和/main实现跨模块访问 - 代理配置统一管理接口请求
- ESLint+Prettier保证代码规范
- TS路径别名配置
- 运行方式:
pnpm dev:main启动主项目,默认打开登录页
项目运行验证
- 执行
pnpm dev启动项目 - 访问
http://localhost:9894看到登录页面 - 输入任意用户名/密码,验证码填1234,点击登录跳转到首页
- 点击首页退出登录,可回到登录页
整个项目已完整实现,包括分包架构、版本号、登录跳转、TS/ESLint配置、静态资源优化等,可直接用于大型前端项目开发。
很多功能未提取为公共文件 自行按需调整。
更多推荐



所有评论(0)