开源脚手架lerna源码分析
学习目标
- Lerna源码结构和执行流程分析
import-local源码深度精读
学习收获
如何将源码分析的收获写进简历
学习明星项目的架构设计
获得脚手架执行流程的一种思路
脚手架调试本地源码的另外一种方法
Nodejs加载node_modules模块的流程(罕见)
各种文件操作算法和最佳实践
学习建议:需要具备一定的Node基础
源码学习
1.准备源码
源码阅读准备完成的标准(重点):
找到入口文件
能够本地调试
调试按钮:
Step Over 一行行向下执行
Step Into 进入到函数里面去执行
Step Out 跳出函数去执行
2.源码分析
import-local库分析作用:当我们本地node_modules存在一个脚手架命令,同时全局在node_modules中也存在这个脚手架命令的时候,优先选用本地node_modules中的版本
last、知识点
require(".")是什么意思?require("."),其实
.就等同于./,代表当前目录,../就代表上一个目录,如果只有.,没有后面的文件,则默认文件是index.js所以,这里的
require(",")就是require("./index.js")require是同步还是异步,require是编译执行还是运行时执行,import呢?require是编译时加载"@lerna/global-options":"file:../global-options"——package.json中存在这种依赖是什么意思?(重点,本地调试推荐使用)"dependencies": { @lerna/global-options":"file:../global-options" }这种写法代表加载的是本地的依赖,依赖的相对路径为
../global-options这个包这种本地调试的方法,非常方便,不需要
npm link****,但是需要install这种方法在本地调试可以使用,发布后,无法找到,lerna框架专门写了一个解析本地路径成远程库连接的方法来解决
如果一个函数的参数,使用的是反引号,那么可以不写括号,比如
const dedent = require("dedent") console.log(dedent` 111 111`) /* 这里就相当于dedent(` 111 111`) */require("process")可以不写require,两者等价constructor.name是什么?- 就是这个类的名称
class Command{ constructor(){ console.log(this.constructor.name) // } } new Command() // 这时候this.constructor.name就是"Command"class Command{ constructor(){ console.log(this.constructor.name) } } class ListCommand extends Command{ } new ListCommand() // 这时候this.constructor.name就是"ListCommand"__filename与__dirname的区别__filename当前文件的路径__dirname当前文件所在文件夹的路径path.dirname(文件或文件夹)文件或文件夹所在父级文件夹的路径- 所以:
path.dirname(__filename)等同于__dirname
node加载一个模块的时候,会向node注入哪些变量?
等同于:require一个模块的时候,会注入哪些变量?
等同于:require源码有哪些变量?
- 总共有5个?
- module
- require
- __filename
- __dirname
- exports方法,因为要对外去输出
- 总共有5个?
module.exports暴露多个方法:// cwd.js module.exports = cwd => {} // 暴露1 module.exports.sync = cwd => {} // 暴露2 module.exports.dir = cwd => {} // 暴露3- 使用:
const findUp = require('cwd.js') findUp() // 执行暴露1 findUp.sync() // 执行暴露2 findUp.dir(); // 执行暴露3path.resolve(a, b, c, ...)- 用于**
当前路径与cd命令的合并**,相当于先获取当前路径,然后cd a,再cd b,然后cd c,…,最终返回的是绝对路径,最后一个是文件的话就拼起来
- 用于**
path.join('/user','/yh','..')- 用于路径简单合并,相当于先将
/user和/yh合并成/user/yh,然后合并..对于这种点,用cd ..,那么结果就是/user- 练习
path.join('/user','/yh','../a')结果是什么?- 答案:
/user/a
- 答案:
- 练习
- 用于路径简单合并,相当于先将
path.dirname(相对或绝对路径)- 用于获取父级目录
- 如果是对相对路径处理,返回的是相对路径
- 如果是对绝对路径处理,返回的是绝对路径
- 总之:
- 值是什么,去掉最后一级即可
- 特殊:值为"…/“或”./“则结果为”."
- 用于获取父级目录
path.parse(路径)- 返回:
root根路径dir父文件夹路径base最后一级路径ext后缀,文件有后缀,文件夹没后缀为空字符串name最后一级的文件名(不带后缀)或文件夹名
- 举例:
path.parse("/a/b/c.js")- 答案:
{ root: '/', dir: '/a/b', base: 'c.js', ext: '.js', name: 'c' }
- 答案:
- 返回:
fs.access(路径)和fs.accessSync(路径)用于判断
路径是否存在,这就可以判断文件或文件夹是否存在了举例:
// 异步判断路径是否存在 fs.access("./index.js", (err) => { console.log(err) }) // 同步判断路径是否存在 try { fs.accessSync("./index.js") } catch (err) { console.log(err) }同步的错误要用
try{...}catch(err){...}捕获
fs.exists(path, callback)与fs.existsSync(path)- 与
access通用的效果 - 但是,异步方法
fs.exists(path, callback)已经弃用,以后就不要再使用了。同步的还可以使用 - 总的来说,建议使用
access方法
- 与
如果要判断路径是否存在,建议使用
path-exists库,这个库会帮我们做维护,哪些方法弃用,哪些没弃用,会维护path.isAbsolute(路径)判断是否为绝对路径,返回布尔值