背景
使用VUE搭建多頁面應用,實現(xiàn)公司共享頁面的需求。
設計思想
所有系統(tǒng)都在同一目錄下,配置多入口多出口。各系統(tǒng)間可以鏈接,但是各系統(tǒng)內部依然采用SPA模式開發(fā)。
復用性
將所有系統(tǒng)的公共組件和方法放在系統(tǒng)目錄的最外層,以達到復用的目的。在系統(tǒng)內部依然可以單獨封裝私有組件,這樣可以最大限度的提高組件的復用性。
路由
各系統(tǒng)單獨進行路由配置
數(shù)據管理
各系統(tǒng)數(shù)據倉庫單獨處理
整體目錄結構
為了便于打包,我們創(chuàng)建一個views的文件夾,在其下創(chuàng)建子文件夾代表每個應用系統(tǒng)。每個子文件夾中建立各自的spa應用體系,這樣做的好處是,我們在配置webpack的打包入口時,比較好操作,而且這樣的結構也較為清晰。

image.png
下面以example為例展開說明:

image.png
注意需要將默認的 html 模板文件 public/index.html 移動到根目錄下。
| 目錄 | 釋義 |
|---|---|
| build | 項目構建(webpack)相關代碼,dev-modules.js用于自定義本地開發(fā)時需要編譯的模塊,index.js用于配置執(zhí)行構建命令,循環(huán)執(zhí)行 vue-cli-service build ,pages.js用于獲取vue-cli需要的多頁對象 |
| public | 公共資源目錄 |
| src | 源碼目錄 |
| .editorconfig | 定義代碼格式 |
| .env.all | 打包所有文件的開發(fā)環(huán)境配置 |
| .env.development | 默認打包的開發(fā)環(huán)境配置 |
| .env.production | 生產環(huán)境配置 |
| .env.test | 測試環(huán)境配置 |
| .eslintrc.js | eslint配置 |
| .gitignore | git上傳需要忽略的文件格式 |
| babel.config.js | ES6語法編譯配置 |
| package.json | 項目基本信息,包依賴信息等 |
| vue.config.js | webpack配置 |
| dist | 項目打包后產生的目錄 |
src目錄說明
| 目錄 | 目錄釋義 |
|---|---|
| assets | 靜態(tài)資源目錄 |
| components | 公共組件庫 |
| filters | 公共過濾器 |
| styles | 公共樣式表 |
| utils | 公共方法文件 |
| views | 各系統(tǒng)spa應用體系 |
src/views/example example目錄說明
| 目錄 | 目錄釋義 |
|---|---|
| api | example api文件 |
| components | example組件庫 |
| pages | example頁面資源庫 |
| router | example路由配置 |
| store | example數(shù)據管理文件 |
| utils | example公共方法文件 |
| App.vue | example入口文件 |
| example.html | example入口頁面模板 |
| example.js | example核心文件 |
打包方式
各系統(tǒng)模塊應用獨立打包
打包后的整體資源路徑:
| 目錄 | 目錄釋義 |
|---|---|
| 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,
// 兼容開發(fā)和生產時 html 頁面層級一致
filename: isProduction ? 'index.html' : `${basename}/index.html`
}
}
})
return pages
}
const pages = getPages(path.join(__dirname, '../src/views/*'))
module.exports = pages
執(zhí)行構建命令,循環(huán)執(zhí)行 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
// 逐個執(zhí)行模塊編譯
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('總編譯時間')
}
})
}
自定義本地開發(fā)時需要編譯的模塊,模塊名為 src/pages 下的文件夾名
// dev-modules.js
// 本地開發(fā)需要編譯的模塊
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 {
// 本地開發(fā)編譯的模塊
// 編譯全部
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升級為最新