我司的一個系統(tǒng)要重構(gòu),新系統(tǒng)計劃用vue開發(fā)扬卷,但是由于舊系統(tǒng)過于龐大颤介,在開發(fā)時使用分包開發(fā)(多系統(tǒng)),所以老大要求在開發(fā)新系統(tǒng)時也要分成多個系統(tǒng)跟匆。但是本人又不想在多個系統(tǒng)中維護一堆同樣的代碼(為保持風格一致异袄,需要使用公共的樣式、組件等)玛臂,遂研究如何在一個vue項目中創(chuàng)建多個系統(tǒng)烤蜕。
先根據(jù)這篇文章搭建一個可以本地訪問的多系統(tǒng)前端項目
創(chuàng)建新項目
vue create multi-project
創(chuàng)建時的選項可以自己選擇,為簡潔計迹冤,我只在基本的選擇上添加了router
和store
創(chuàng)建完成的項目結(jié)構(gòu)如下:
使用腳手架創(chuàng)建的vue項目均為單頁應用讽营,但在此處我們需要把它修改為多頁應用。
將目錄結(jié)構(gòu)調(diào)整如下:
在官方文檔中給出的解決方法是在vue.config.js配置項pages
vue.config.js配置參考文檔
修改
vue.config.js
文件為:
module.exports = {
pages: {
projectA: {
// page 的入口
entry: 'src/modules/projectA/main.js',
// 模板來源
template: 'public/index.html',
// 在 dist/index.html 的輸出
filename: 'projectA.html',
// 當使用 title 選項時泡徙,
// template 中的 title 標簽需要是 <title><%= htmlWebpackPlugin.options.title %></title>
title: 'project A Page',
// 在這個頁面中包含的塊橱鹏,默認情況下會包含
// 提取出來的通用 chunk 和 vendor chunk。
chunks: ['chunk-vendors', 'chunk-common', 'projectA']
},
// 當使用只有入口的字符串格式時堪藐,
// 模板會被推導為 `public/subpage.html`
// 并且如果找不到的話莉兰,就回退到 `public/index.html`。
// 輸出文件名會被推導為 `subpage.html`礁竞。
projectB: {
// page 的入口
entry: 'src/modules/projectB/main.js',
// 模板來源
template: 'public/index.html',
// 在 dist/index.html 的輸出
filename: 'projectB.html',
// 當使用 title 選項時糖荒,
// template 中的 title 標簽需要是 <title><%= htmlWebpackPlugin.options.title %></title>
title: 'project B Page',
// 在這個頁面中包含的塊,默認情況下會包含
// 提取出來的通用 chunk 和 vendor chunk模捂。
chunks: ['chunk-vendors', 'chunk-common', 'projectB']
}
}
}
重新啟動項目捶朵,訪問下面兩個地址即可看到多頁面的效果。
http://localhost:8080/projectA/
http://localhost:8080/projectB/
此時可以正確看到兩個項目的App
頁面狂男,但在App
通過router-vue
暴露出去的頁面卻看不到综看,查看network
報錯為We're sorry but multi-project3 doesn't work properly without JavaScript enabled. Please enable it to continue.
查資料后,把router的history
修改為hash
模式即可
打包部署到服務器
此時執(zhí)行npm run build
打包岖食,打包結(jié)果如下(自己當時沒有截圖,借用別人的圖):
兩個不同的項目的文件各自編譯打包红碑,但并沒有按項目分成不同的文件夾,我們進一步修改配置县耽,將其改為按項目打包句喷。
修改
vue.config.js
代碼如下:
var projectname = process.argv[3]
var glob = require('glob')
function getEntry() {
var entries = {}
if (process.env.NODE_ENV == 'production') {
entries = {
index: {
// page的入口
entry: 'src/modules/' + projectname + '/main.js',
// 模板來源
template: 'public/index.html',
// 在 dist/index.html 的輸出
filename: 'index.html',
title: projectname,
chunks: ['chunk-vendors', 'chunk-common', 'index']
}
}
} else {
var items = glob.sync( './src/modules/*/*.js')
for (var i in items) {
var filepath = items[i]
var fileList = filepath.split('/');
var fileName = fileList[fileList.length-2];
entries[fileName] = {
entry: `src/modules/${fileName}/main.js`,
// 模板來源
template: `public/index.html`,
// 在 dist/index.html 的輸出
filename: `${fileName}.html`,
// 提取出來的通用 chunk 和 vendor chunk镣典。
chunks: ['chunk-vendors', 'chunk-common', fileName]
}
}
}
return entries
}
var pages = getEntry()
module.exports = {
productionSourceMap: false, // 生產(chǎn)禁止顯示源代碼
outputDir: 'dist/' + projectname,
pages: pages
}
現(xiàn)在執(zhí)行以下命令進行打包:
# npm run build [projectFileName]
npm run build projectA
npm run build projectB
打包結(jié)果如下:
把打包好的文件放到nginx
上,訪問結(jié)果如下:
跟上面是同樣的問題唾琼,請求的
js
報404
看了下打包后的文件兄春,是打包后的文件中
js
和css
等文件引用路徑錯誤,手動修改后可以正常訪問锡溯,所以只要修改打包配置赶舆,使文件引用路徑符合要求就可以了
此時需要參考官網(wǎng)修改vue.config.js
文件
publicPath: '/', // 原配置
publicPath: '/' + projectname + '/', // 修改后
之后再打包的文件引用路徑就沒有問題了
把之前的單項目修改為多項目
由于我們之前已經(jīng)有一個小項目(vue單項目-修改自開源項目vue-admin-template),考慮到修改為多項目的代碼修改量不是很大祭饭,所以繼續(xù)在之前的項目中進行修改芜茵。
首先根據(jù)上面的步驟修改目錄結(jié)構(gòu)和vue.config.js
文件,由于現(xiàn)有的項目采用后端返回路由倡蝙,前端進行解析的方式動態(tài)加載路由九串,所以除了views
外,api
寺鸥、layout
猪钮、router
和store
目錄也都在子項目目錄中存在。其中store
中的公共部分提取到父項目中胆建,子項目中需要用到的路由部分留在子項目中烤低。
目前為止,項目可以正常訪問笆载,只是公共組件SvgIcon
無法使用扑馁,原因是把之前項目的vue.config.js
文件中的內(nèi)容給修改了,再加回去就好了凉驻。
直接把加載svg
的配置加進去會報錯Error: Cannot call .tap() on a plugin that has not yet been defined. Call plugin('preload').use(<Plugin>) first.
:
參考文章修改如下:
修改前:
config.plugin('preload').tap(() => [
{
rel: 'preload',
fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
include: 'initial'
}
])
修改后:
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
config.plugin('preload').use(PreloadWebpackPlugin).tap(() => [
{
rel: 'preload',
// to ignore runtime.js
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
include: 'initial'
}
])
此時便可以正常使用SvgIcon
組件了
此時vue.config.js
文件如下:
'use strict'
const projectname = process.argv[3]
const glob = require('glob')
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
function getEntry() {
let entries = {}
if (process.env.NODE_ENV == 'production') {
entries = {
index: {
// page的入口
entry: 'src/modules/' + projectname + '/main.js',
// 模板來源
template: 'public/index.html',
// 在 dist/index.html 的輸出
filename: 'index.html',
title: projectname,
chunks: ['chunk-vendors', 'chunk-common', 'index']
}
}
} else {
let items = glob.sync( './src/modules/*/*.js')
for (let i in items) {
let filepath = items[i]
let fileList = filepath.split('/');
let fileName = fileList[fileList.length-2];
entries[fileName] = {
entry: `src/modules/${fileName}/main.js`,
// 模板來源
template: `public/index.html`,
// 在 dist/index.html 的輸出
filename: `${fileName}.html`,
// 提取出來的通用 chunk 和 vendor chunk腻要。
chunks: ['chunk-vendors', 'chunk-common', fileName]
}
}
}
return entries
}
let pages = getEntry()
const path = require('path')
const defaultSettings = require('./src/settings.js')
function resolve(dir) {
return path.join(__dirname, dir)
}
const name = defaultSettings.title || 'vue Admin Template' // page title
// If your port is set to 80,
// use administrator privileges to execute the command line.
// For example, Mac: sudo npm run
// You can change the port by the following methods:
// port = 9528 npm run dev OR npm run dev --port = 9528
const port = process.env.port || process.env.npm_config_port || 9528 // dev port
// All configuration item explanations can be find in https://cli.vuejs.org/config/
module.exports = {
/**
* You will need to set publicPath if you plan to deploy your site under a sub path,
* for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
* then publicPath should be set to "/bar/".
* In most cases please use '/' !!!
* Detail: https://cli.vuejs.org/config/#publicpath
*/
outputDir: 'dist/' + projectname,
publicPath: '/' + projectname + '/',
pages: pages,
assetsDir: 'static',
lintOnSave: process.env.NODE_ENV === 'development',
productionSourceMap: false,
devServer: {
host: '0.0.0.0',
port: port,
open: true,
https: true,
overlay: {
warnings: false,
errors: true
},
before: require('./mock/mock-server.js'),
proxy: {
'/downloadFile': {
// target: 'http://10.141.128.102:8401',
// target: 'http://dhr-basic-service.apps.uat.taikangcloud.com/', //test
target: 'http://127.0.0.1:88', //dev
changeOrigin: true,
ws: true,
pathRewrite: {
'^/downloadFile': '/'
}
},
},
},
configureWebpack: {
// provide the app's title in webpack's name field, so that
// it can be accessed in index.html to inject the correct title.
name: name,
resolve: {
alias: {
'@': resolve('src')
}
}
},
chainWebpack(config) {
// it can improve the speed of the first screen, it is recommended to turn on preload
config.plugin('preload').use(PreloadWebpackPlugin).tap(() => [
{
rel: 'preload',
// to ignore runtime.js
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
// fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/], // 文件黑名單
fileBlacklist: [/\.map$/, /hot-update\.js$/],
include: 'initial'
}
])
// when there are many pages, it will cause too many meaningless requests
config.plugins.delete('prefetch')
// set svg-sprite-loader
config.module
.rule('svg')
.exclude.add(resolve('src/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
config
.when(process.env.NODE_ENV !== 'development',
config => {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/
}])
.end()
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
})
// https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
config.optimization.runtimeChunk('single')
}
)
}
}
但是,我又雙叕發(fā)現(xiàn)問題啦Q爻蕖4车凇市栗!
打包部署到nginx后缀拭,有一個runtime.**.js
文件找不到
找了半天沒找到好方法,最后把
vue.config.js
文件中的最后一個config
注釋掉后好了但是這個配置這兒不太熟填帽,別人寫的配置也看不太懂蛛淋,之后可能會遇到其他問題,到時候自求多福吧
修改后的
vue.config.js
文件如下:
'use strict'
const projectname = process.argv[3]
const glob = require('glob')
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
function getEntry() {
let entries = {}
if (process.env.NODE_ENV == 'production') {
entries = {
index: {
// page的入口
entry: 'src/modules/' + projectname + '/main.js',
// 模板來源
template: 'public/index.html',
// 在 dist/index.html 的輸出
filename: 'index.html',
title: projectname,
chunks: ['chunk-vendors', 'chunk-common', 'index']
}
}
} else {
let items = glob.sync( './src/modules/*/*.js')
for (let i in items) {
let filepath = items[i]
let fileList = filepath.split('/');
let fileName = fileList[fileList.length-2];
entries[fileName] = {
entry: `src/modules/${fileName}/main.js`,
// 模板來源
template: `public/index.html`,
// 在 dist/index.html 的輸出
filename: `${fileName}.html`,
// 提取出來的通用 chunk 和 vendor chunk篡腌。
chunks: ['chunk-vendors', 'chunk-common', fileName]
}
}
}
return entries
}
let pages = getEntry()
const path = require('path')
const defaultSettings = require('./src/settings.js')
function resolve(dir) {
return path.join(__dirname, dir)
}
const name = defaultSettings.title || 'vue Admin Template' // page title
// If your port is set to 80,
// use administrator privileges to execute the command line.
// For example, Mac: sudo npm run
// You can change the port by the following methods:
// port = 9528 npm run dev OR npm run dev --port = 9528
const port = process.env.port || process.env.npm_config_port || 9528 // dev port
// All configuration item explanations can be find in https://cli.vuejs.org/config/
module.exports = {
/**
* You will need to set publicPath if you plan to deploy your site under a sub path,
* for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
* then publicPath should be set to "/bar/".
* In most cases please use '/' !!!
* Detail: https://cli.vuejs.org/config/#publicpath
*/
outputDir: 'dist/' + projectname,
publicPath: '/' + projectname + '/',
pages: pages,
assetsDir: 'static',
lintOnSave: process.env.NODE_ENV === 'development',
productionSourceMap: false,
devServer: {
host: '0.0.0.0',
port: port,
open: true,
https: true,
overlay: {
warnings: false,
errors: true
},
before: require('./mock/mock-server.js'),
proxy: {
'/downloadFile': {
// target: 'http://10.141.128.102:8401',
// target: 'http://dhr-basic-service.apps.uat.taikangcloud.com/', //test
target: 'http://127.0.0.1:88', //dev
changeOrigin: true,
ws: true,
pathRewrite: {
'^/downloadFile': '/'
}
},
},
},
configureWebpack: {
// provide the app's title in webpack's name field, so that
// it can be accessed in index.html to inject the correct title.
name: name,
resolve: {
alias: {
'@': resolve('src')
}
}
},
chainWebpack(config) {
// it can improve the speed of the first screen, it is recommended to turn on preload
config.plugin('preload').use(PreloadWebpackPlugin).tap(() => [
{
rel: 'preload',
// to ignore runtime.js
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/], // 文件黑名單
include: 'initial'
}
])
// when there are many pages, it will cause too many meaningless requests
config.plugins.delete('prefetch')
// set svg-sprite-loader
config.module
.rule('svg')
.exclude.add(resolve('src/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
// config
// .when(process.env.NODE_ENV !== 'development',
// config => {
// config
// .plugin('ScriptExtHtmlWebpackPlugin')
// .after('html')
// .use('script-ext-html-webpack-plugin', [{
// // `runtime` must same as runtimeChunk name. default is `runtime`
// inline: /runtime\..*\.js$/
// }])
// .end()
// config
// .optimization.splitChunks({
// chunks: 'all',
// cacheGroups: {
// libs: {
// name: 'chunk-libs',
// test: /[\\/]node_modules[\\/]/,
// priority: 10,
// chunks: 'initial' // only package third parties that are initially dependent
// },
// elementUI: {
// name: 'chunk-elementUI', // split elementUI into a single package
// priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
// test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
// },
// commons: {
// name: 'chunk-commons',
// test: resolve('src/components'), // can customize your rules
// minChunks: 3, // minimum common number
// priority: 5,
// reuseExistingChunk: true
// }
// }
// })
// // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
// config.optimization.runtimeChunk('single')
// }
// )
}
}