Vue-cli 2.X 多项目集成 配置步骤

本次目标

多模块集成的 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

根据不同的 命令,启动 或 打包 对应 的 模块项目

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345