目录
resolve-from 源码分析(彻底搞懂 node_modules 模块加载逻辑)
Lerna简介
原生脚手架开发痛点分析
·痛点一:重复操作
多 Package 本地 link
多 Package 依赖安装
多 Package 单元测试
多 Package 代码提交
多 Package 代码发布
·痛点二:版本一致性
发布时版本一致性
发布后相互依赖版本升级
package越多,管理复杂度越高
Lerna简介
Lerna 是一个优化基于 git + npm 的多 package 项目的管理工具。
优势
·大幅减少重复操作
·提升操作的标准化(减少排查错误的时间)
【上面两项 是 架构师核心思考点】
【Lerna 是架构优化的产物,它揭示了一个架构真理;项目复杂度提升后,就需要对项目进行架构优化。架构优化的主要目标往往都是以效能为核心】
官网
https://lerna.js.org/
Lerna 开发脚手架流程(划重点)
步骤一:脚手架项目初始化
初始化 npm 项目、安装 lerna、lerna init 初始化项目
步骤二:创建 package
lerna create 创建 Package、lerna add 安装依赖、lerna link 链接依赖
步骤三:脚手架开发和测试
lerna exec 执行 shell 脚本、lerna run 执行 npm 命令、lerna clean 清空依赖、lerna bootstrap 重装依赖
步骤四:脚手架发布上线
lerna version bump version、lerna changed 查看上版本以来的所有变更、lerna diff 查看diff、lerna publish 项目发布
基于 Lerna 搭建脚手架框架
// .gitignore 文件
.vscode
.idea
node_modules
packages/**/node_modules
lerna-debug.log
@imooc-cli-dev/core
@组织名/package名
现在npm网站上注册 组织名,再发包,不然发不上去。
Lerna 核心操作
......
Lerna 发布流程
发布之前,要对 版本号version 进行升级。
git checkout -- lerna-debug.log
//丢弃工作区的改动
//package.json中
"publishConfig": {
"access": "public"
}
Lerna 源码分析
学习目标
·Lerna 源码结构和执行流程分析
·import-local 源码深度精读
学习收获
·如何将源码分析的收获写进简历
·学习明星项目的架构设计
·获得脚手架执行流程的一种思路
·脚手架调试本地源码的另外一种方法(npm link 之外)
·NodeJs加载node_modules模块的流程
·各种文件操作算法和最佳实践
源码仓库
https://github.com/lerna/lerna
准备源码
源码阅读前准备工作:下载源码、安装依赖、IDE打开
源码阅读准备完成的标准:找到入口文件、能够本地调试
Node 源码调试过程中必会的小技巧
取消 Do not step into library scripts
Coding assistant for NodeJs
lerna 初始化过程源码详细分析
require(.) 相当于 require(./index.js)
// npm 项目本地依赖引用方法
"dependencies": {
"@lerna/global-options": "file:../global-options"
}
脚手架框架 yargs 快速入门
#!/usr/bin/env node
const yargs = require('yargs/yargs')
const { hideBin } = require('yargs/helpers')
const arg = hideBin(process.argv)
const cli = yargs(arg)
cli
.usage('Usage: imooc-test [command] <options>')
.demandCommand(1, 'A command is required. Pass --help to see all available commands and options')
.strict()
.alias('h', 'help')
.wrap(cli.terminalWidth())
.epilogue(`结尾的话`)
.options({
debug: {
type: 'boolean',
describe: 'Bootstrap debug mode',
alias: 'd'
}
})
.group(['debug'], '命令分组')
.group(['registry'], 'Extra Options:')
.argv
yargs 高级用法讲解
const argv = process.argv.slice(2)
const context = {
imoocVersion: pkg.version
}
yargs(hideBin(process.args))
.command('serve [port]', 'start the server', (yargs) => {
//builder
yargs.positional('port', {describe: 'port to bind on',
default: 5000
})
},(argv) => {
//handler
if (argv.verbose) console.info(`start server on :${argv.port}`)
serve(argv.port)
})
.option('verbose', {
alias: 'v',
type: 'boolean',
description: 'Run with verbose logging'
})
.recommendCommands()
.fail((err, msg) => {
console.log(err)
})
.parse(argv, context)
import-local 执行流程深度分析
作用:当项目和全局都存在 某个命令时,优先执行项目中的命令。
node 代码执行过程中,会向当前上下文中注入5个变量。__dirname、 __filename、 module、 require、 exports。
pkg-dir 源码解析(一大波优秀的文件操作库)
作用:逐级向上找,找到 package.json 所在位置。
path.resolve()
path.join()
path.parse()
好用的库:findUp、locatePath、pathExists
resolve-from 源码分析(彻底搞懂 node_modules 模块加载逻辑)
// 命令执行路径
process.cwd()
// 生成node_modules可能的路径
Module._nodeModulePaths()
// 解析模块的真实路径,node模块加载核心方法
Module._resolveFilename()
// 将paths和环境变量node_modules合并
Module._resolveLookupPaths()
// 在paths中解析模块的真实路径
Module._findPath()
fs.realpathSync()
赚回学费之基于Lerna重新设计简历
完全掌握本章内容的同学可以在简历中增加:
·熟悉 Yargs 脚手架开发框架
·熟悉多 Package 管理工具 lerna 的使用方法和实现原理
·深入了解 Nodejs 模块路径解析流程
面试官问起细节之后如何回答?
yargs
·脚手架构成
bin:package.json中配置bin属性,npm link 本地安装
command:命令
options:参数(boolean / string / number)
文件顶部增加 #!/usr/bin/env node
·脚手架初始化流程
构造函数:Yargs()
常用方法:Yargs.options Yargs.option Yargs.group Yargs.demandCommand Yargs.recommendCommands Yargs.strict Yargs.fail Yargs.alias Yargs.wrap Yargs.epilogue
·脚手架参数解析方法
hideBin(process.argv)
Yargs.parse(argv, options)
·命令注册方法
Yargs.command(command, describe, builder, handler)
Yargs.command({ command, describe, builder, handler })
Lerna
·Lerna 是基于 git + npm 的多 package 项目管理工具
·实现原理
通过 import-local 优先调用本地 lerna 命令
通过 Yargs 生成脚手架,先注册全局属性,再注册命令,最后通过 parse 方法解析参数
lerna 命令注册时需要传入 builder 和 handler 两个方法, builder 方法用于注册命令专属的options,handler 用来处理命令的业务逻辑
lerna 通过配置 npm 本地依赖的方式来进行本地开发,具体写法是在 package.json 的依赖中写入:file:your-local-module-path,在 lerna publish 时会自动将该路径替换
Nodejs 模块路径解析流程
·Nodejs 项目模块路径解析是通过 require.resolve 方法来实现的
·require.resolve 就是通过 Module._resolveFileName 方法实现的
·require.resolve 实现原理
Module._resolveFileName 方法核心流程有 3 点:
判断是否为内置模块
通过 Module._resolveLookupPaths 方法生成 node_modules 可能存在的路径
通过 Module._findPath 查询模块的真实路径
Module._findPath 核心流程有 4 点:
查询缓存(将 request 和 paths 通过 \x00 合并成 cacheKey)
遍历 paths,将 path 与 request 组成文件路径 basePath
如果 basePath 存在则调用 fs.realPathSync 获取文件真实路径
将文件真实路径缓存到 Module._pathCache (key 就是前面生成的 cacheKey)
fs.realPathSync 核心流程有 3 点:
查询缓存(缓存的 key 为 p,即 Module._findPath 中生成的文件路径)
从左往右遍历路径字符串,查询到 / 时,拆分路径,判断该路径是否为软链接。如果是软链接则查询真实链接,并生成新路径 p,然后继续往后遍历,这里有 1 个细节需要特别注意:遍历过程中生成的子路径 base 会缓存在 knownHard 和 cache 中,避免重复查询。
遍历完成得到模块对应的真实路径,此时会将原始路径 original 作为 key,真实路径作为 value,保存到缓存中
·require.resolve.paths 等价于 Module._resolveLookupPaths,该方法用于获取所有 node_modules 可能存在的路径
·require.resolve.paths 实现原理:
如果路径为 / (根目录),直接返回 ['/node_modules']
否则,将路径字符串从后往前遍历,查询到 / 时,拆分路径,在后面加上 node_modules,并传入一个 paths 数组,直至查询不到 / 后返回 paths 数组