从 vue-cli 源码中,我发现27行读取 json 文件有趣的 npm 包

若川大约 7 分钟

从 vue-cli 源码中,我发现27行读取 json 文件有趣的 npm 包

1. 前言

大家好,我是若川open in new window为了能帮助到更多对源码感兴趣、想学会看源码、提升自己前端技术能力的同学。我倾力组织了源码共读活动open in new window,感兴趣的可以加我微信 ruochuan12open in new window 参与,或者关注我的公众号若川视野open in new window,回复“源码”参与。已进行4个月,每周大家一起学习200行左右的源码,共同进步,很多人都表示收获颇丰。

想学源码,极力推荐关注我写的专栏(目前1.8K人关注)《学习源码整体架构系列》open in new window 包含jQueryunderscorelodashvuexsentryaxiosreduxkoavue-devtoolsvuex4koa-composevue 3.2 发布vue-thiscreate-vue玩具vite等20余篇源码文章。

本文仓库 https://github.com/lxchuan12/read-pkg-analysis.git,求个star_open in new window

源码共读活动open in new window 每周一期,已进行到15期。源码群里有小伙伴提问,如何用 import 加载 json 文件。同时我之前看到了vue-cli 源码open in new window 里有 read-pkgopen in new window 这个包。源码仅27行,非常值得我们学习。

阅读本文,你将学到:

1. 如何学习调试源码
2. 学会如何获取 package.json
3. 学到 import.meta
4. 学到引入 json 文件的提案
5. JSON.parse 更友好的错误提示
6. 规范化 package 元数据
7. 等等

2. 场景

优雅的获取 package.json 文件。

read-pkgopen in new window

vue-cli 源码open in new window

const fs = require('fs')
const path = require('path')
const readPkg = require('read-pkg')

exports.resolvePkg = function (context) {
  if (fs.existsSync(path.join(context, 'package.json'))) {
    return readPkg.sync({ cwd: context })
  }
  return {}
}

封装这个函数的commit 记录open in new window

你也许会想直接 require('package.json'); 不就可以了。但在ES模块下,目前无法直接引入JSON文件。

在 stackoverflow 也有相关提问open in new window

我们接着来看 阮一峰老师的 JSON 模块open in new window

import 命令目前只能用于加载 ES 模块,现在有一个提案open in new window,允许加载 JSON 模块。 import 命令能够直接加载 JSON 模块以后,就可以像下面这样写。

import configData from './config.json' assert { type: "json" };
console.log(configData.appName);

import 命令导入 JSON 模块时,命令结尾的 assert {type: "json"} 不可缺 少。这叫做导入断言,用来告诉 JavaScript 引擎,现在加载的是 JSON 模块。

接下来我们学习 read-pkg 源码open in new window

3. 环境准备

3.1 克隆

# 推荐克隆我的项目,保证与文章同步
git clone https://github.com/lxchuan12/read-pkg-analysis.git
# npm i -g yarn
cd read-pkg && yarn
# VSCode 直接打开当前项目
# code .

# 或者克隆官方项目
git clone https://github.com/sindresorhus/read-pkg.git
# npm i -g yarn
cd read-pkg && yarn
# VSCode 直接打开当前项目
# code .

看源码一般先看 package.json,再看 script

3.2 package.json

{
	"name":
	"scripts": {
		"test": "xo && ava && tsd"
	}
}

test命令有三个包,我们一一查阅了解。

xoopen in new window

JavaScript/TypeScript linter (ESLint wrapper) with great defaults JavaScript/TypeScript linter(ESLint 包装器)具有很好的默认值

tsdopen in new window

Check TypeScript type definitions 检查 TypeScript 类型定义

nodejs 测试工具 avaopen in new window

Node.js test runner that lets you develop with confidence

3.3 调试

提前在入口测试文件 test/test.js 和入口文件 index.js 打好断点。

用最新的VSCode 打开项目,找到 package.jsonscripts 属性中的 test 命令。鼠标停留在test命令上,会出现 运行命令调试命令 的选项,选择 调试命令 即可。

调试如图所示:

debugger
debugger

更多调试细节可以看我的这篇文章:新手向:前端程序员必学基本技能——调试JS代码open in new window

我们跟着调试来看测试用例。

4. 测试用例

这个测试用例文件,主要就是主入口 index.js 导出的两个方法 readPackage, readPackageSync。异步和同步的方法。

判断读取的 package.jsonname 属性与测试用例的 name 属性是否相等。

判断读取 package.json_id 是否是真值。

同时支持指定目录。{ cwd }

// read-pkg/test/test.js
import {fileURLToPath} from 'url';
import path from 'path';
import test from 'ava';
import {readPackage, readPackageSync} from '../index.js';

const dirname = path.dirname(fileURLToPath(import.meta.url));
process.chdir(dirname);
const rootCwd = path.join(dirname, '..');

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

test('async - cwd option', async t => {
	const package_ = await readPackage({cwd: rootCwd});
	t.is(package_.name, 'read-pkg');
});

test('sync', t => {
	const package_ = readPackageSync();
	t.is(package_.name, 'unicorn');
	t.truthy(package_._id);
});

test('sync - cwd option', t => {
	const package_ = readPackageSync({cwd: rootCwd});
	t.is(package_.name, 'read-pkg');
});

这个测试用例文件,涉及到一些值得一提的知识点。接下来就简单讲述下。

4.1 url 模块

url 模块提供用于网址处理和解析的实用工具。

url 中文文档open in new window

url.fileURLToPath(url)

url <URL> | <string> 要转换为路径的文件网址字符串或网址对象。 返回: <string> 完全解析的特定于平台的 Node.js 文件路径。 此函数可确保正确解码百分比编码字符,并确保跨平台有效的绝对路径字符串。

4.2 import.meta.url

import.meta.urlopen in new window

(1)import.meta.url import.meta.url返回当前模块的 URL 路径。举例来说,当前模块主文件的路径是https://foo.com/main.js,import.meta.url就返回这个路径。如果模块里面还有一个数据文件 data.txt,那么就可以用下面的代码,获取这个数据文件的路径。 new URL('data.txt', import.meta.url) 注意,Node.js 环境中,import.meta.url 返回的总是本地路径,即是file:URL协议的字符串,比如 file:///home/user/foo.js

4.3 process.chdir

process.chdir() 方法更改 Node.js 进程的当前工作目录,如果失败则抛出异常(例如,如果指定的 directory 不存在)。

5. 27行主入口源码

导出异步和同步的两个方法,支持传递参数对象,cwd 默认是 process.cwd()normalize 默认标准化。

分别是用 fsPromises.readFile fs.readFileSync 读取 package.json 文件。

parse-jsonopen in new window 解析 json 文件。

npm 官方库 normalize-package-dataopen in new window 规范化 package 元数据。

import process from 'node:process';
import fs, {promises as fsPromises} from 'node:fs';
import path from 'node:path';
import parseJson from 'parse-json';
import normalizePackageData from 'normalize-package-data';

export async function readPackage({cwd = process.cwd(), normalize = true} = {}) {
	const filePath = path.resolve(cwd, 'package.json');
	const json = parseJson(await fsPromises.readFile(filePath, 'utf8'));

	if (normalize) {
		normalizePackageData(json);
	}

	return json;
}

export function readPackageSync({cwd = process.cwd(), normalize = true} = {}) {
	const filePath = path.resolve(cwd, 'package.json');
	const json = parseJson(fs.readFileSync(filePath, 'utf8'));

	if (normalize) {
		normalizePackageData(json);
	}

	return json;
}

5.1 process 进程模块

很常用的模块。

process 中文文档open in new window

process 对象提供有关当前 Node.js 进程的信息并对其进行控制。 虽然它作为全局可用,但是建议通过 require 或 import 显式地访问它:

import process from 'node:process';

Node 文档open in new window

也就是说引用 node 原生库可以加 node: 前缀,比如 import util from 'node:util'

5.2 path 路径模块

很常用的模块。

path 中文文档open in new window

path 模块提供了用于处理文件和目录的路径的实用工具。

5.3 fs 文件模块

很常用的模块。

fs 中文文档open in new window

5.4 parseJson 解析 JSON

parse-jsonopen in new window

文档介绍:

Parse JSON with more helpful errors

更多有用的错误提示。

// 源码有删减
const fallback = require('json-parse-even-better-errors');
const parseJson = (string, reviver, filename) => {
	if (typeof reviver === 'string') {
		filename = reviver;
		reviver = null;
	}

	try {
		try {
			return JSON.parse(string, reviver);
		} catch (error) {
			fallback(string, reviver);
			throw error;
		}
	} catch (error) {
		// 省略
	}
}

5.5 normalizePackageData 规范化包元数据

npm 官方库 normalize-package-dataopen in new window

normalizes package metadata, typically found in package.json file.

规范化包元数据

module.exports = normalize
function normalize (data, warn, strict) {
	// 省略若干代码
	data._id = data.name + '@' + data.version
}

这也就是为啥测试用例中用了t.truthy(package_._id); 来检测 _id 属性是否为真值。

6. 总结

最后总结下我们学到了如下知识:

1. 如何学习调试源码
2. 学会如何获取 package.json
3. 学到 import.meta
4. 学到引入 json 文件的提案
5. JSON.parse 更友好的错误提示
6. 规范化 package 元数据
7. 等等

read-pkg 源码open in new window 整体而言相对比较简单,但是也有很多可以学习深挖的学习的知识点。

作为一个 npm 包,拥有完善的测试用例。

Node.js 可以多找找简单的 npm 包学习。比直接看官方文档有趣多了。不懂的就去查官方文档。查的多了,自然常用的就熟练了。

建议读者克隆 我的仓库open in new window 动手实践调试源码学习。

最后可以持续关注我@若川open in new window。欢迎加我微信 ruochuan12open in new window 交流,参与 源码共读open in new window 活动,每周大家一起学习200行左右的源码,共同进步。

欢迎扫码加我微信
拉你进源码共读群
一起学习源码