背景
使用VUE搭建多页面应用,实现公司共享页面的需求。
设计思想
所有系统都在同一目录下,配置多入口多出口。各系统间可以链接,但是各系统内部依然采用SPA模式开发。
复用性
将所有系统的公共组件和方法放在系统目录的最外层,以达到复用的目的。在系统内部依然可以单独封装私有组件,这样可以最大限度的提高组件的复用性。
路由
各系统单独进行路由配置
数据管理
各系统数据仓库单独处理
整体目录结构
为了便于打包,我们创建一个views的文件夹,在其下创建子文件夹代表每个应用系统。每个子文件夹中建立各自的spa应用体系,这样做的好处是,我们在配置webpack的打包入口时,比较好操作,而且这样的结构也较为清晰。
下面以example为例展开说明:
注意需要将默认的 html 模板文件 public/index.html 移动到根目录下。
目录 | 释义 |
---|---|
build | 项目构建(webpack)相关代码,dev-modules.js 用于自定义本地开发时需要编译的模块,index.js 用于配置执行构建命令,循环执行 vue-cli-service build ,pages.js 用于获取vue-cli需要的多页对象 |
public | 公共资源目录 |
src | 源码目录 |
.editorconfig | 定义代码格式 |
.env.all | 打包所有文件的开发环境配置 |
.env.development | 默认打包的开发环境配置 |
.env.production | 生产环境配置 |
.env.test | 测试环境配置 |
.eslintrc.js | eslint配置 |
.gitignore | git上传需要忽略的文件格式 |
babel.config.js | ES6语法编译配置 |
package.json | 项目基本信息,包依赖信息等 |
vue.config.js | webpack配置 |
dist | 项目打包后产生的目录 |
src目录说明
目录 | 目录释义 |
---|---|
assets | 静态资源目录 |
components | 公共组件库 |
filters | 公共过滤器 |
styles | 公共样式表 |
utils | 公共方法文件 |
views | 各系统spa应用体系 |
src/views/example example目录说明
目录 | 目录释义 |
---|---|
api | example api文件 |
components | example组件库 |
pages | example页面资源库 |
router | example路由配置 |
store | example数据管理文件 |
utils | example公共方法文件 |
App.vue | example入口文件 |
example.html | example入口页面模板 |
example.js | example核心文件 |
打包方式
各系统模块应用独立打包
打包后的整体资源路径:
目录 | 目录释义 |
---|---|
example | example资源文件夹 |
打包后的各文件夹内资源路径,以example为例:
目录 | 目录释义 |
---|---|
img | example图片资源文件夹 |
js | example js资源文件夹 |
favicon.ico | example应用的浏览器图标 |
index.html | example页面入口 |
依赖
// package.json
{
"name": "object-name",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"serve:all": "vue-cli-service serve --mode all",
"build:test": "node build/index.js",
"build:prod": "node build/index.js",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"tasksfile": "^5.1.1",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
"lint-staged": "^9.5.0",
"node-sass": "^4.12.0",
"sass-loader": "^8.0.2",
"vue-template-compiler": "^2.6.11"
},
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"@vue/standard"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
],
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.{js,jsx,vue}": [
"vue-cli-service lint",
"git add"
]
}
}
获取vue cli需要的多页对象
// build/pages.js
const path = require('path')
const glob = require('glob')
const fs = require('fs')
const isProduction = process.env.NODE_ENV === 'production'
// 自定义不同模块的页面 title
const titleMap = {
index: '首页'
}
function getPages (globPath) {
const pages = {}
glob.sync(globPath).forEach((item) => {
const stats = fs.statSync(item)
if (stats.isDirectory()) {
const basename = path.basename(item, path.extname(item))
const template = `${item}/${basename}.html`
pages[basename] = {
entry: `${item}/${basename}.js`,
title: titleMap[basename] || '默认页面',
template,
// 兼容开发和生产时 html 页面层级一致
filename: isProduction ? 'index.html' : `${basename}/index.html`
}
}
})
return pages
}
const pages = getPages(path.join(__dirname, '../src/views/*'))
module.exports = pages
执行构建命令,循环执行 vue-cli-service build
// build/index.js
const chalk = require('chalk')
const rimraf = require('rimraf')
const { sh } = require('tasksfile')
const PAGES = require('./pages')
// vue-cli-service --mode 值
const mode = process.env.MODE || 'prod'
// 模块名,可能为多个
const moduleNames = process.argv[2]
// 全部页面列表
const pageList = Object.keys(PAGES)
// 有效模块列表 未指定则为全部页面列表
const validPageList = moduleNames ? moduleNames.split(',').filter((item) => pageList.includes(item)) : pageList
if (!validPageList.length) {
console.log(chalk.red('**模块名不正确**'))
return
}
console.log(chalk.blue(`有效模块:${validPageList.join(',')}`))
// 删除 dist 目录
rimraf.sync('dist')
console.time('总编译时间')
const count = validPageList.length
let current = 0
// 逐个执行模块编译
for (let i = 0; i < validPageList.length; i += 1) {
const moduleName = validPageList[i]
process.env.MODULE_NAME = moduleName
console.log(chalk.blue(`${moduleName} 模块开始编译`))
// 通过 vue-cli-service build 编译
sh(`vue-cli-service build --mode ${mode}`, { async: true }).then(() => {
console.log(chalk.blue(`${moduleName} 模块编译完成`))
console.log()
current += 1
if (current === count) {
console.log(chalk.blue('-----全部模块编译完成-----'))
console.timeEnd('总编译时间')
}
})
}
自定义本地开发时需要编译的模块,模块名为 src/pages 下的文件夹名
// dev-modules.js
// 本地开发需要编译的模块
module.exports = [
'example'
]
vue.config.js
// vue.config.js
const chalk = require('chalk')
const devModuleList = require('./build/dev-modules')
const isProduction = process.env.NODE_ENV === 'production'
// 总的页面
const PAGES = require('./build/pages')
for (const basename in PAGES) {
if (Object.prototype.hasOwnProperty.call(PAGES, basename)) {
PAGES[basename].chunks = [
'chunk-vue',
'chunk-vendors',
'chunk-common',
`${basename}`
]
}
}
let pages = {}
const moduleName = process.env.MODULE_NAME
if (isProduction) {
// 构建模块的名称
if (!PAGES[moduleName]) {
console.log(chalk.red('**模块名不正确**'))
return
}
pages[moduleName] = PAGES[moduleName]
} else {
// 本地开发编译的模块
// 编译全部
if (process.env.DEV_MODULE === 'all') {
pages = PAGES
} else {
// 编译部分模块
const moduleList = [
'index', // 固定编译的模块
...devModuleList // 自定义编译的模块
]
moduleList.forEach(item => {
pages[item] = PAGES[item]
})
}
}
module.exports = {
// 这行代码很重要
publicPath: isProduction ? './' : '/',
pages,
// 这行代码很重要
outputDir: isProduction ? `dist/${moduleName}` : 'dist',
productionSourceMap: false,
chainWebpack: (config) => {
config.optimization.splitChunks({
cacheGroups: {
vue: {
name: 'chunk-vue',
test: /[\\/]node_modules[\\/]_?(vue|vue-router|vuex)(@.*)?[\\/]/,
priority: -1,
chunks: 'initial'
},
vendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
})
}
}
多页面路由配置
const router = new VueRouter({
mode: 'history',
base: '/example/', // 注意这里
routes
})
多页面部署到服务器报错
Uncaught (in promise) Error: Error
解决方案:将vue-router升级为最新