本次目标
多模块集成的 vue 项目,多项目共用一份配置,可以互相依赖,也可以独立打包部署
使用业务场景
(1).如果多个项目使用同一样式 或 依赖 相同的 js / 组件, 可以每个项目 都导入一次, 但后期维护时, 公用引用的部分一旦修改就会就牵一发而动全身,所有引用此文件的都得改动,有些繁琐.
(2).如果项目有多个子模块(同时子模块之间又存在互相依赖关系);对于这样的场景是可以把项目独立发布到npm仓库,但是这样又涉及到每个模块都需要独立编译好再发布,实际过程有显得有些繁琐。
对于以上场景可以使用一个项目管理多个子模块也是一个不错的选择
多页面 和 多模块的区别
多页面:指一个项目有多个入口,打包是会生成多个html文件,实际开发过程中都是混合在一个项目中开发;
多模块:是指不同的业务模块可以进行拆分;各自独立运行、也可以互相引用,这一点和通过 npm 发布是类似的;
对于一些项目本身不允许发布的情况下,既可以独立开发,又不需要发布到共有仓库;
待解决问题
(1).如何划分子模块;
(2).如何分离可复用组件;
(3).如何独立编译,每个子模块独立打包编译、运行;
多模块优点
(1).高复用性
(2).统一管理依赖库
(3).不同模块使用的依赖各自按需打包
(4).模块之间相互独立运行、编译、打包
(5).模块之间可以直接互相引用,不需要iframe
接下来我们看下具体配置步骤
第一步: 把src目录下的文件换成多模块的形式
项目模块结构安装上面的改动完毕之后,控制台会报一些路径错误之类的:
这是因为 webpack.base.conf.js 里面的 main.js 的路径发生改变导致的,之前项目是单模块只要一个main.js,
现在换成多模块之后每个模块都有自己独立的 main.js。故此要修改配置。
第二步: 增加config/multi.conf.js 多模块配置文件
const path = require('path')
const pack = require('../package.json')
const argvs = process.argv.slice(2)
class MultiModule {
constructor (multiName, opts) {
let datetime = Date.now(), name = multiName.split('_')[0];
Object.assign(this, {
name,
multiName,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
port: 8080,
host: '0.0.0.0',
proxyTable: null,
entry: {
app: ['babel-polyfill', `./src/${name}/main.js`]
},
alias: resolve(`src/${name}`),
index: path.resolve(__dirname, `../dist/${name}/index.html`),
favicon: path.resolve(__dirname, `../src/${name}/assets/favicon.ico`),
assetsRoot: path.resolve(__dirname, `../dist/${name}/`),
pubdate: `${name}_v${pack.version}_${datetime}`,
publics: [name].concat(opts.statics || []),
deployConfig: null
}, opts)
}
}
// 多模块独立配置 ==> 所有模块的入口选择
var importModules = [
new MultiModule('proj1', {
port: 8081,
statics: ['static1'],
assetsPublicPath:'/',
baseUrl: '/api/',
proxyTable: {
'/api/': getProxyConfig( {'^/ent':'/'} , 'http://XX.XX.XX.XX')
}
}),
new MultiModule('proj2', {
port: 8082,
statics: ['static2'],
assetsPublicPath:'/',
baseUrl: '/api/',
proxyTable: {
'/api/': getProxyConfig( {'^/ent':'/'} , 'http://XX.XX.XX.XX')
}
}),
new MultiModule('proj3', {
port: 8083,
statics: ['static1'],
assetsPublicPath:'/',
baseUrl: '/api/',
proxyTable: {
'/api/': getProxyConfig( {'^/ent':'/'} , 'http://XX.XX.XX.XX')
}
})
]
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
function getParams (key) {
let item = argvs.find(item => item.split('=')[0] === key)
return item ? item.split('=') : []
}
function getModuleAlias () {
let alias = {}
importModules.forEach(({ name }) => {
alias[`@${name}`] = resolve(`src/${name}`)
})
return alias
}
function getModuleProcess (name) {
let mItem = importModules.find(item => item.multiName === name)
return mItem || importModules[0]
}
function proxyHandle (proxyReq, req, res, options) {
let origin = `${options.target.protocol}//${options.target.hostname}`
proxyReq.setHeader('origin', origin)
proxyReq.setHeader('referer', origin)
}
function onProxyReq (proxyReq, req, res, options) {
proxyHandle(proxyReq, req, res, options)
}
function onProxyReqWs (proxyReq, req, socket, options, head) {
proxyHandle(proxyReq, req, socket, options)
}
// 生成代理类
function getProxyConfig (pathRewrite,target, options) {
return Object.assign({
target,
secure: false,
changeOrigin: true,
ws: false,
// cookieDomainRewrite: { '*': '' },
// cookiePathRewrite: { '*': '/' },
onProxyReq,
onProxyReqWs,
pathRewrite:pathRewrite
}, options)
}
var lifecycleEvents = String(process.env.npm_lifecycle_event).split(':')
var moduleName = getParams('name')[1] || lifecycleEvents[1]
const multiConfig = {
modules: importModules,
moduleAlias: getModuleAlias(),
process: getModuleProcess(moduleName),
}
module.exports = multiConfig;
第三步.修改 build/webpack.base.conf.js 文件
(1).引入新配置 const multiConfig = require('../config/multi.conf')
(2).修改入口配置 entry: multiConfig.process.entry // 之前为 app: ['babel-polyfill', `./src/${name}/main.js`]
(3).修改资源路径 publicPath: process.env.ASSETS_PUBLICPATH
(4).修改 alias: {
'vue$': 'vue/dist/vue.esm.js',
// '@': resolve('src'), //旧配置 , 以下为新配置
'@comm': resolve(`src/comm`),
'@': multiConfig.process.alias,
...multiConfig.moduleAlias
}
第三步.修改 config/index.js 文件
(1).引入新配置
const multiConfig = require('../config/multi.conf')
(2).放入全局对象
process.env.PROJ_NAME = multiConfig.process.name;
process.env.BASE_URL = multiConfig.process.baseUrl;
process.env.FILE_BASE_URL = multiConfig.process.fileBaseUrl;
process.env.SOCKET_URL = multiConfig.process.socketUrl;
process.env.ASSETS_SUBDIRECTORY= multiConfig.process.assetsSubDirectory;
process.env.ASSETS_PUBLICPATH = multiConfig.process.assetsPublicPath;
process.env.ASSETS_PUBLICS = multiConfig.process.publics;
(3). 修改 dev 配置
assetsSubDirectory: multiConfig.process.assetsSubDirectory
assetsPublicPath: multiConfig.process.assetsPublicPath
proxyTable: multiConfig.process.proxyTable
host: multiConfig.process.host
port: multiConfig.process.port
(4).修改 build 配置
assetsPublicPath: multiConfig.process.assetsPublicPath
第四步.修改 build/webpack.dev.conf.js 与 build/webpack.prod.conf.js 文件
引入新配置
const multiConfig = require('../config/multi.conf')
可根据 项目本身需要 进行相应修改 plugins
new webpack.DefinePlugin({
'process.env.PROJ_NAME': '\"' + process.env.PROJ_NAME + '\"',
'process.env.BASE_URL': '\"' + process.env.BASE_URL + '\"',
'process.env.ASSETS_PUBLICPATH': '\"' + process.env.ASSETS_PUBLICPATH + '\"',
'process.env.ASSETS_SUBDIRECTORY': '\"' + process.env.ASSETS_SUBDIRECTORY + '\"',
'process.env.FILE_BASE_URL': '\"' + process.env.FILE_BASE_URL + '\"',
'process.env.SOCKET_URL': '\"' + process.env.SOCKET_URL + '\"'
})
build/webpack.dev.conf.js 文件 修改如下:
引入文件
const chalk = require('chalk')
const pack = require('../package.json')
const os = require('os')
新增此方法 放在 顶部
function getIPAdress () {
var interfaces = os.networkInterfaces()
for (var devName in interfaces) {
var iface = interfaces[devName]
for (var i = 0; i < iface.length; i++) {
var alias = iface[i]
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
return alias.address
}
}
}
}
let host = ['localhost', '127.0.0.1', '0.0.0.0'].includes(devWebpackConfig.devServer.host) ? 'localhost' : devWebpackConfig.devServer.host
//旧配置
// compilationSuccessInfo: {
// messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
// },
//新配置
compilationSuccessInfo: {
messages: [
chalk`{bold.rgb(255,255,0) [${pack.name} => ${multiConfig.process.name}]} App running at:\n - Local: {bold.cyan http://${host}:${port}${config.dev.assetsPublicPath}}\n - Network: {bold.cyan http://${getIPAdress()}:${port}${config.dev.assetsPublicPath}}`
]
}
build/webpack.prod.conf.js 文件 修改如下:
引入 fs 文件
const fs = require('fs')
在头部新增此方法
function isDirectory (path) {
try {
let stat = fs.statSync(path)
return stat.isDirectory()
} catch (e) {
return false
}
}
// copy custom static assets 旧配置
// new CopyWebpackPlugin([
// {
// from: path.resolve(__dirname, '../static'),
// to: config.build.assetsSubDirectory,
// ignore: ['.*']
// }
// ])
// copy custom static assets 新配置new CopyWebpackPlugin(multiConfig.process.publics.filter(name => isDirectory(path.resolve(__dirname, `../static/${name}`))).map(name => {
return {
from: path.resolve(__dirname, `../static/${name}`),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
})),
引入 文件
...require('./packZip')
第五步: build 文件夹 引入packZip.js 和 clear.js
安装插件
npm i filemanager-webpack-plugin --s
packZip.js 内容如下
const pack = require('../package.json')
const path = require('path')
const FileManagerPlugin = require('filemanager-webpack-plugin')
const multiConfig = require('../config/multi.conf')
const argvs = process.argv
function getParams(key) {
let item = argvs.find(item => item.split('=')[0] === key)
return item ? item.split('=') : []
}
let dateime = Date.now();
let plugins = []
let zipPros = getParams('zip');
if (zipPros.length) {
plugins.push(new FileManagerPlugin({
onEnd: {
delete: [
path.join(__dirname, `../${pack.name}_${multiConfig.process.name}_*.zip`)
],
archive: [{
source: path.join(__dirname, `../dist/${multiConfig.process.name}`),
destination: path.join(__dirname, zipPros[1] ? `../${pack.name}_${zipPros[1]}.zip` : `../${pack.name}_${multiConfig.process.name}_v${pack.version}_${dateime}.zip`)
}]
}
}))
clear.js 内容如下
const path = require('path')
const fs = require('fs')
function deleteFolder(path) {
let files = [];
if (fs.existsSync(path)) {
files = fs.readdirSync(path);
files.forEach(file => {
let curPath = path + '/' + file
let stat = fs.statSync(curPath)
if (stat.isDirectory()) {
deleteFolder(curPath)
} else {
fs.unlinkSync(curPath)
}
});
fs.rmdirSync(path)
}
}
function deleteTemp(path) {
let files = [];
if (fs.existsSync(path)) {
files = fs.readdirSync(path)
files.forEach(file => {
let curPath = path + '/' + file
let stat = fs.statSync(curPath)
if (stat.isFile() && /.+\.(zip|log)$/.test(file)) {
fs.unlinkSync(curPath)
}
});
}
}
deleteFolder(path.join(__dirname, '..', 'dist'))
deleteTemp(path.join(__dirname, '..'))
deleteTemp(path.join(__dirname, '../deploy'))
第六步. 配置 package.json 文件
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"dev:proj1": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"dev:proj2": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"dev:proj3": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"build": "npm install && npm run build:proj1 && npm run build:proj2 && npm run build:proj3 ",
"build:proj1": "node build/build.js name=proj1",
"build:proj1:zip": "node build/build.js name=proj1 zip",
"build:proj2": "node build/build.js name=proj2",
"build:proj2:zip": "node build/build.js name=proj2 zip",
"build:proj3": "node build/build.js name=proj3",
"build:proj3:zip": "node build/build.js name=proj3 zip",
"clear": "node build/clear.js"
}
第七步.启动 / 打包 项目
npm run dev:proj1
npm run dev:proj2
npm run dev:proj3
npm run build :proj1
npm run build :proj2
npm run build :proj3
根据不同的 命令,启动 或 打包 对应 的 模块项目