【源码共读】第27期 | 从read-pkg源码实现3个方法中看如何解析package.json数据

前言

清单

  1. 了解read-pkg包使用场景及源码实现
  2. 测试用例方式调试代码
  3. 从源码看如何实现ts声明
  4. 源码中哪些优秀代码和思想可以投入到自己的项目中

前提准备

  • 先下载源代码 git clone https://github.com/sindresorhus/read-pkg.git
  • 然后cd read-pkg && yarn下载相关包
  • 打开package.json文件看启动方式

场景应用及源码实现

不先了解清楚包的来龙去脉就直接调试看源码细节,就会感觉分外生疏,有种不知尔尔的感觉,接下来就先说下这个源码包存在的来源~

场景应用

  • 是什么?
    主要用来读package.json文件
  • 为什么要写专门的库来读取这个文件?

image.png
从上述代码中可以看到,我以正常的方式引入package.json文件,想读取里面的json数据,结果…

阮一峰老师的文档所写:

image.png

所以有了这个代码库的出现,就是为了解决读取package.json文件的问题

  • 怎么使用?
npm install read-pkg

import {readPackage} from 'read-pkg'; 


console.log(await readPackage()); 
//=> {name: 'read-pkg', …} 

console.log(await readPackage({cwd: 'some-other-directory'})); 
//=> {name: 'unicorn', …}
  • 从API提供上看,只有三个api:readPackage、readPackageSync、parsePackage,分别表示异步读取文件、同步读取文件、解析文件

源码分析

从上面的场景应用中了解到了read-pkg库的存在原因,为了解决什么问题出现的,也了解到了怎么使用的,接下来就看下作者在给出解决方案的过程中是如何思考的?

接着上述的前提准备的步骤,看到了package.json的script中只有"test": "xo && tsd && cd test && ava",这样看来面向测试用例调试是一种不错的方式,

  • xo && tsd && cd test && ava了解
    • xo: 自带js,ts默认的eslint规则,不需要再单独配置
    • tsd:用来检查ts的类型,enn~,项目中给xxx.d.ts也写了测试用例
    • ava:用nodejs写法的单元测试框架
    • 这三个都是轻量级,不需要做太多配置,拿来即用
  • test.js文件

image.png
上述代码中看出,要进入test文件夹,找到test.js文件,通过vscode断点的方式调试,如上图所示;

跟随断点,进入到index.js文件,可以看到导出了三个方法readPackagereadPackageSyncparsePackage,其中两个是读取方法,一个是解析方法,按照断点的路径,先看readPackage这个函数中的方法实现:

image.png
readPackage函数中,通过fsPromises.readFile函数读取到package.json文件,这里用了文件读取,返回promise数据;

接着,通过_readPackage方法,来解析成json格式数据返回;

这里看到了parseJson函数,哟~,以后不想自己封装json解析,也可以使用parse-json包来直接解析string类型json数据;

然后又看到了normalizePackageData函数,这个是干嘛的呢?
现在不知道,先看下测试用例

test('async', async t => {
	const package_ = await readPackage();
	t.is(package_.name, 'unicorn');
	t.truthy(package_._id);
});

因为normalize参数默认是true,就走到了normalizePackageData函数,从上面的这个测试用例可以看到package_._id,_id?一般情况下,_xxx表示私有属性,这个在package.json中,不存在_id属性,带着这个疑问,找到/node_modules/normalize-package-data/lib/normalize.js文件,如下图,看到了_id是什么

image.png
了解了readPackage函数后,再去对比了解下readPackageSync函数有什么区别?

export function readPackageSync({cwd, normalize = true} = {}) {
	const packageFile = fs.readFileSync(getPackagePath(cwd), 'utf8');
	return _readPackage(packageFile, normalize);
}

除了fs.readFileSync函数用了文件读取同步的操作,其他都一样;

接着看下parsePackage函数中如何实现呢?

export function parsePackage(packageFile, {normalize = true} = {}) {
	const isObject = packageFile !== null && typeof packageFile === 'object' && !Array.isArray(packageFile);
	const isString = typeof packageFile === 'string';


	if (!isObject && !isString) {
		throw new TypeError('`packageFile` should be either an `object` or a `string`.');
	}

	// Input should not be modified - if `structuredClone` is available, do a deep clone, shallow otherwise
	// TODO: Remove shallow clone when targeting Node.js 18
	const clonedPackageFile = isObject
		? (globalThis.structuredClone === undefined
			? {...packageFile}
			: structuredClone(packageFile))
		: packageFile;

	return _readPackage(clonedPackageFile, normalize);
}

先不说中间发生了什么,就看最后return也是执行的_readPackage函数,所以,parsePackage函数中间也就就是根据类型判断边界错误,以及对数据做了拷贝处理…

看了这个函数,最好奇structuredClone是什么?去查了MDN后,才知道:

  • 是一个js(不支持nodejs使用)的API,用来做深拷贝;
  • 相比较JSON.parse(JSON.stringfy(val)),其优势,保持数据结构,对于对象自身引用、数组等都可以拷贝;

到这里为止,源码的分析就结束了~,接下来看下项目的ts声明是如何实现的?

从js源码看其ts项目实践

现在周围各个项目和框架都在应用ts做开发,ts在项目中的实践就显得尤为重要,那如何提高ts的应用水平呢?个人觉得,先从别人的优秀源码中学习他人是如何使用的

index.d.ts文件入手,上面的源码实现如何写ts类型声明?

在看之前,自己思考如何写ts类型声明呢?
个人觉得先从源码js开始,根据export导出的函数为入手点,要导出函数声明,然后抱着这样的想法去看类型声明,咦?三个函数,导出了6个函数类型声明,为啥呢?第一天看没看懂,先放着吧,然后第二天再打开接着看,这次我从上往下看,先看参数的定义,

export type Options = {
	readonly cwd?: URL | string;
	readonly normalize?: boolean;
};

type _NormalizeOptions = {
	readonly normalize?: true;
};
export type NormalizeOptions = _NormalizeOptions & Options;

前两个我也常写,看得懂,这第三个&是什么?为什么要这样写?查了一番资料:

/**
 * 为什么合并? 合并后是什么样的?
 * &:交叉运算
 * 作用:合并对象 交集,交集冲突时,会自动转换为never
 * interface也可以使用&
 *
 * 使用场景区分:
 *  - type: 定义单个字段活定义工具类(做类型体操)时用
 *  - interface:定义base类、模块对外提供功能,提供接口、模块
 */

看了这个后,&就是为了进一步约束参数类型和默认值,我懂了~

接下来,看到了一个熟悉的类型声明export type ParseOptions = Omit<Options, 'cwd'>;,曾经写过一篇源码共读是关于omit源码共读,omit的作用就是剔除对象上的某个属性,这里ts中也是类似的功能,提出某个类型声明类型,如下图:

image.png
同时,这也是ts自带的工具类函数~

最后再看下函数的类型声明为啥是两个?

export function readPackage(options?: NormalizeOptions): Promise<NormalizedPackageJson>;
export function readPackage(options: Options): Promise<PackageJson>;

readPackage函数中有两个参数,其中normalize决定了_readPackage返回的类型是否经过了normalizePackageData函数的处理,所以有了两中不同的ts类型声明(个人理解),好了ts的类型声明的分析到这里也结束了~

最后,我有一个疑问,小伙伴们知道的,可以在评论区分享下,关于ts类型声明中,对参数的声明为什么也要export?

总结

一句话总结,read-pkg就是通过nodejs的读到package.json的数据,然后通过parseJson库解析成json,如果需要元数据化,normalize参数也支持其功能。

通过对read-pkg源码的阅读,以后用到package.json的读取,就能想到用read-pkg库,其次,以后进行深拷贝,也不用自己封装了,structuredClone可以直接代替,最后,对于ts在项目中的应用,我也可以把Omit&以及函数声明都应用于自己的实践项目中~

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYkSVmig' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片