当我们在构建复杂项目时,往往会将部分功能拆分出一个独立的npm模块,以便其他项目复用。现在遇到一个这样的场景:我们构建了最新的组件库,希望在A项目中使用并查看效果,但是组件库还没发布,这时候该如何处理呢?
解决办法:
- 直接在
package.json中修改包路径
"dependencies": {
"bar": "file:../foo/bar"
}
npm link
cd ~/projects/node-redis # go into the package directory
npm link # creates global link
cd ~/projects/node-bloggy # go into some other package directory.
npm link redis # link-install the package
- yalc
yalc 将npm link的步骤简化,并实现了包更新后,其他项目依赖项的更新。
简单使用
首先,我们来尝试下用法
# 在公用库 package1 中
yalc publish --push
# 在项目中引入
yalc add package1
# 在项目中移除
yalc remove package1
项目中会导入package1和生成相应的链接
用法非常简单,接下来我们来看这个过程是怎么实现的
源码分析
通过一个简单的例子,我们大概知道他的原理:
- 这个库会将需要发布的包存入全局的
.yalc中,当其他项目引入依赖时,将对应的包存放至当前项目的.yalc文件夹中,修改对应包的软连接。
接下来,我们来看下源码是怎么实现的
github.com/wclr/yalc?s…
跳转至package.json查看入口文件
// index.js
// 省略...
// 项目主目录
const userHome = homedir()
// 基本配置参数
export const values = {
myNameIs: 'yalc',
ignoreFileName: '.yalcignore',
myNameIsCapitalized: 'Yalc',
lockfileName: 'yalc.lock',
yalcPackagesFolder: '.yalc',
prescript: 'preyalc',
postscript: 'postyalc',
installationsFile: 'installations.json',
}
// 省略...
/*
Not using Node.Global because in this case
<reference types="mocha" /> is aded in built d.ts file
*/
export const yalcGlobal: YalcGlobal = global as any
// 获取全局yalc的存储路径
export function getStoreMainDir(): string {
if (yalcGlobal.yalcStoreMainDir) {
return yalcGlobal.yalcStoreMainDir
}
if (process.platform === 'win32' && process.env.LOCALAPPDATA) {
return join(process.env.LOCALAPPDATA, values.myNameIsCapitalized)
}
return join(userHome, '.' + values.myNameIs)
}
// 找到当前包的对应全局yalc的存储路径
export function getStorePackagesDir(): string {
return join(getStoreMainDir(), 'packages')
}
// 获取当前包的存储路径
export const getPackageStoreDir = (packageName: string, version = '') =>
join(getStorePackagesDir(), packageName, version)
export const execLoudOptions = { stdio: 'inherit' } as ExecSyncOptions
const signatureFileName = 'yalc.sig'
// 读取签名文件
export const readSignatureFile = (workingDir: string) => {
const signatureFilePath = join(workingDir, signatureFileName)
try {
const fileData = fs.readFileSync(signatureFilePath, 'utf-8')
return fileData
} catch (e) {
return ''
}
}
// 读取yalcignore
export const readIgnoreFile = (workingDir: string) => {
const filePath = join(workingDir, values.ignoreFileName)
try {
const fileData = fs.readFileSync(filePath, 'utf-8')
return fileData
} catch (e) {
return ''
}
}
// 写入签名文件 yalc.sig
export const writeSignatureFile = (workingDir: string, signature: string) => {
const signatureFilePath = join(workingDir, signatureFileName)
try {
fs.writeFileSync(signatureFilePath, signature)
} catch (e) {
console.error('Could not write signature file')
throw e
}
}
- 当我们执行
yalc publish --push的时候,会执行哪些流程 - 首先在源码中找到对应的命令
- github.com/wclr/yalc/b…
.command({
// 定义指令
command: 'publish',
// 指令描述
describe: 'Publish package in yalc local repo',
builder: () => {
// 设置命令的默认选项
return yargs
.default('sig', false)
.default('scripts', true)
.default('dev-mod', true)
.default('workspace-resolve', true)
.default(rcArgs)
.alias('script', 'scripts')
.boolean(['push'].concat(publishFlags))
},
handler: (argv) => {
// 推送到所有安装的地方
return publishPackage(getPublishOptions(argv))
},
})
export const publishPackage = async (options: PublishPackageOptions) => {
const workingDir = options.workingDir
// 检查包的清单文件
const pkg = readPackageManifest(workingDir)
if (!pkg) {
return
}
// 获取包管理器
const pm = getPackageManager(workingDir)
// 执行脚本 eg. npm run build
const runPmScript = (script: keyof PackageScripts) => {
// 省略..
}
// 检查包的私有性
if (pkg.private && !options.private) {
// 省略..
}
// 执行预发布脚本
const preScripts: (keyof PackageScripts)[] = [
'prepublish',
'prepare',
'prepublishOnly',
'prepack',
'preyalcpublish',
]
preScripts.forEach(runPmScript)
// 拷贝包到store
const copyRes = await copyPackageToStore(options)
// 省略..
// 执行发布后脚本
const postScripts: (keyof PackageScripts)[] = [
'postyalcpublish',
'postpack',
'publish',
'postpublish',
]
postScripts.forEach(runPmScript)
// 省略...
// 遍历每个安装目录,更新包
if (options.push) {
const installationsConfig = readInstallationsFile()
const installationPaths = installationsConfig[pkg.name] || []
const installationsToRemove: PackageInstallation[] = []
for (const workingDir of installationPaths) {
console.info(`Pushing ${pkg.name}@${pkg.version} in ${workingDir}`)
const installationsToRemoveForPkg = await updatePackages([pkg.name], {
replace: options.replace,
workingDir,
update: options.update,
noInstallationsRemove: true,
})
installationsToRemove.push(...installationsToRemoveForPkg)
}
await removeInstallations(installationsToRemove)
}
}
总结
通过学习这个库,我们了解到这个库的原理就是通过建立一个全局的公共库,在项目引用时,自动修改对应的包成软连接,从而实现了本地modules的使用。
- 代码中的publishPackage函数会运行包管理器的预发布脚本和发布后脚本,以确保在发布过程中执行必要的脚本操作。
- 使用了yargs库来解析命令行的参数。
- 考虑了边界情况,
~,^,*的版本号 - overloadConsole 定义了多种颜色的命令行