需求背景
公司有8个前端项目,共使用四个公共包,基础包(baqi)、医生公共包(baqi-doctor)、患者公共包(baqi-patient)、聊天包(baqi-chat)。
开发分支上使用file引入公共包(file引入的方式可以查看这一篇),到测试和生产上需要使用版本号来引入这几个公共包。这就需要一个方便的publish公共包的工具,写了一个node脚本publish.js
实现
主流程:
- 计算下一个版本号
- 设置npm代理为淘宝
- 设置baqi包、baqi-xxx包中package.json的version号
- publish baqi包和baqi-xxx包
- 替换前端8个项目引入baqi包和baqi-xxx包的版本号
- 判断是否安装cnpm,若没安装cnpm,则安装
- 新开4个进程,执行cnpm sync命令,同步新发布的包到淘宝代理
- 新开8个进程,进入8个前端项目执行npm install
需要注意的是:
- 可以使用http的get请求获取npm包的信息,url为http://registry.npm.taobao.org/baqi/latest,其中baqi为包名
- 需要设置taobao的npm代理,不然执行npm命令进程偶尔会卡住
- 设置taobao代理后无法publish,在publish时指定registry为npmjs官方url
- publish和进入各项目执行npm install时可以开多个进程执行,提高效率,使用
await Promise.all(promiseArr)
保证执行顺序 - 如在linux下使用,路径会错误,可以使用
path.sep()
来代替路径的斜杠 - 使用
process.cwd()
来代替__dirname
,减少因路径带来的问题
package.json增加执行脚本
"scripts": {
"pb": "node bin/publish.js"
},
使用方法
npm run pb <version>
version可选,若输入version,按照指定的版本号publish,若不输入version,自动计算版本号
代码
主流程代码
#!/usr/bin/env node
const fs = require('fs')
const path = require('path')
const {
getRequest, execCmdSync, execCmd } = require('./util')
const cwd = process.cwd()
const modules = ['baqi', 'baqi-chat', 'baqi-doctor', 'baqi-patient']
const projects = [
'doctor_web',
'patient_web',
'heart-doctor',
'heart-patient',
'tumour-doctor',
'tumour-patient',
'mini-hospital',
'mh-doctor'
]
let version = process.argv[2]
main()
async function main () {
if (version === undefined) {
console.log('查找版本号!')
version = await computeVersion()
console.log('版本号为:' + version)
}
console.log('设置npm代理')
execCmdSync('npm config set registry https://registry.npm.taobao.org')
console.log('替换baqi包、baqi-xxx包的version号\n将baqi-xxx包中引入baqi的方式替换成version引入')
for (let i = 0; i < modules.length; i++) {
replaceVersionAndImport(modules[i])
}
console.log('publish baqi包和baqi-xxx包')
const publishArr = []
for (let i = 0; i < modules.length; i++) {
console.log('正在publish ' + modules[i])
const cdPath = path.join(cwd, '../' + modules[i])
publishArr.push(execCmd('cd ' + cdPath + ' && npm publish --registry http://registry.npmjs.org'))
}
await Promise.all(publishArr) // 不catch,报错就中断
console.log('将baqi-xxx包中引入baqi的方式替换成file引入')
for (let i = 1; i < modules.length; i++) {
replaceImportToFile(modules[i])
}
console.log('替换所有项目引入baqi包和baqi-xxx包的版本号')
for (let i = 0; i < projects.length; i++) {
replaceImport(projects[i])
}
console.log('判断是否安装cnpm')
checkCnpm()
console.log('新开4个进程,同步cnpm')
let cnpmSyncArr = []
for (let i = 0; i < modules.length; i++) {
cnpmSyncArr.push(execCmd('cnpm sync ' + modules[i]))
}
await Promise.all(cnpmSyncArr) // 不catch,报错就中断
console.log('新开8个进程,进入各项目执行npm install')
const npmInstallArr = []
for (let i = 0; i < projects.length; i++) {
const lockFilePath = path.join(cwd, '../' + projects[i] + '/package-lock.json')
if (fs.existsSync(lockFilePath)) {
console.log('删除文件' + lockFilePath)
fs.unlinkSync(lockFilePath)
}
const cdPath = path.join(cwd, '../' + projects[i])
npmInstallArr.push(execCmd('cd ' + cdPath + ' && npm install'))
}
try {
await Promise.all(npmInstallArr)
} catch (e) {
console.error(e)
}
console.log('----------搞定了----------')
}
其他函数代码
function checkCnpm () {
try {
const out = execCmdSync('where cnpm')
if (out.startsWith('信息')) {
execCmdSync('npm install -g cnpm')
}
} catch (e) {
execCmdSync('npm install -g cnpm')
}
}
async function computeVersion () {
let maxVersion = ''
for (let i = 0; i < modules.length; i++) {
const moduleInfo = await getRequest('http://registry.npm.taobao.org/' + modules[i] + '/latest')
const packageVersion = moduleInfo.version
if (packageVersion > maxVersion) {
maxVersion = packageVersion
}
}
let lastVersion = parseInt(maxVersion.substring(maxVersion.lastIndexOf('.') + 1)) + 1
maxVersion = maxVersion.substring(0, maxVersion.lastIndexOf('.') + 1) + lastVersion
return maxVersion
}
function replaceImportToFile (moduleName) {
let filePath = path.join(cwd, '../' + moduleName + '/package.json')
let fileStr = readPackageJson(filePath)
fileStr = fileStr.replace(/("baqi"\s*:\s*").*?(")/g, '$1file:../baqi$2')
writePackageJson(filePath, fileStr)
}
function replaceImport (projectName) {
let filePath = path.join(cwd, '../' + projectName + '/package.json')
let fileStr = readPackageJson(filePath)
fileStr = fileStr.replace(/("baqi.*\s*:\s*").*?(")/g, '$1' + version + '$2')
writePackageJson(filePath, fileStr)
}
function replaceVersionAndImport (moduleName) {
let filePath = path.join(cwd, '../' + moduleName + '/package.json')
let fileStr = readPackageJson(filePath)
fileStr = fileStr.replace(/("version": ").*?(")/, '$1' + version + '$2')
fileStr = fileStr.replace(/("baqi.*\s*:\s*").*?(")/, '$1' + version + '$2')
writePackageJson(filePath, fileStr)
}
function writePackageJson (packageJsonPath, str) {
fs.writeFileSync(packageJsonPath, str, 'utf8')
}
function readPackageJson (packageJsonPath) {
const fileStr = fs.readFileSync(packageJsonPath, 'utf8')
return fileStr
}
util.js
const http = require('http')
const execSync = require('child_process').execSync
const exec = require('child_process').exec
function execCmdSync (cmdStr) {
console.log('执行:' + cmdStr)
const out = execSync(cmdStr, {
encoding: 'utf8' })
return out
}
function execCmd (cmdStr) {
return new Promise((resolve,rejcect) => {
console.log('执行:' + cmdStr)
exec(cmdStr, {
encoding: 'utf8' }, function (error, stdout, stderr) {
error ? rejcect(error) : resolve(stdout)
})
})
}
function getRequest (url) {
return new Promise(resolve => {
console.log('请求URL:' + url)
http.get(url, res => {
let html = ''
res.on('data', function (data) {
html += data
})
res.on('end', function () {
console.log('返回:' + html)
try {
resolve(JSON.parse(html))
} catch (e) {
resolve(html)
}
})
})
})
}
module.exports = {
exec,
execCmd,
getRequest
}
结语
实现了一键发布4个公共包并且修改引用的功能,但有时候只改动某一个地方确要发布4个公共包,下一步计划增加只发布某一个公共包的功能