- 开发工具
【免费下载链接】just
A library of dependency-free JavaScript utilities that do just one thing.
导读
just-pluck-it是开源项目 just(just是一个由零依赖 npm 模块组成的 JavaScript 工具库,每个模块"只做一件事")中负责集合属性提取的小型工具:给定一个数组或对象集合,返回其中每个成员在指定属性上的值。本文以 packages/collection-pluck/CHANGELOG.md 为脉络,逐版本还原从2.1.1到2.3.0四次发布背后的技术决策(TypeScript 类型、package.json导出配置、exports条件顺序、CommonJS 文件命名规范),并结合仓库中的源码、构建配置与测试用例,帮助你理解一个"微型"工具库如何兼顾双模块发布(CJS/ESM)、类型完备性与工程质量。读完本文,你将能快速掌握just-pluck-it的完整用法、版本变更含义,以及其背后可复用的 npm 包工程化范式。
一、just-pluck-it 是什么:一次安装,一处调用
1.1 功能定位
在 just 仓库的模块目录中,just-pluck-it对应 packages/collection-pluck 目录,npm 包名为just-pluck-it(见 packages/collection-pluck/package.json)。其官方描述为:
Pluck a property from each member of a collection
即:从集合(collection)的每个成员中提取指定属性。所谓"集合",既可以是数组,也可以是对象(对象被视为"以键为索引的成员集合")。
1.2 安装
包支持 npm 与 yarn 两种安装方式(见 packages/collection-pluck/README.md):
npm install just-pluck-ityarn add just-pluck-it包本身零依赖(package.json中没有任何dependencies),安装后可同时以 ES Module 与 CommonJS 两种方式引入。
1.3 基本用法
import pluck from 'just-pluck-it'; // 数组:返回各成员指定属性值组成的新数组 pluck([{a: 1, b: 2}, {a: 4, b: 3}, {a: 2, b: 5}], 'a'); // [1, 4, 2] // 对象:返回以原键为键、属性值为值的新对象 pluck({x: {a: 1, b: 2}, y: {a: 4, b: 3}, z: {a: 2, b: 5}}, 'a'); // {x: 1, y: 4, z: 2}- 输入数组时,输出为数组:每个元素的值是
collection[i][propertyName]; - 输入对象时,输出为对象:每个键的值是
collection[key][propertyName],键名保持不变。
这正是本包在 just 中的定位——与just-values(取对象所有值)、just-entries(取对象键值对)不同,just-pluck-it专门解决"横向提取"同一属性的问题,广泛适用于数据表中抽列、报表字段提取等场景。
二、CHANGELOG 逐版本解读:四次发布背后的技术决策
packages/collection-pluck/CHANGELOG.md 记录了该包从2.1.1到2.3.0的完整演进。虽然条目不多,但每一条都对应着 npm 包工程化中一个真实的技术问题。整体版本时间线如下:
| 版本 | 变更类型 | 变更内容 |
|---|---|---|
| 2.1.1 | Patch Changes | 增加 TypeScript 类型(Types) |
| 2.2.0 | Minor Changes | package.json更新,修复 #467、#483 |
| 2.2.1 | Patch Changes | 修复:调整exports顺序,将default置后(#488) |
| 2.3.0 | Minor Changes | 将 Node 模块文件由.js重命名为.cjs |
需要说明的是,从仓库根目录 package.json 中@changesets/cli这一依赖可以推断,该 CHANGELOG 由 Changesets 工作流自动生成(Minor Changes/Patch Changes正是 Changesets 的版本语义标签)。因此每条记录都对应一次真实合并的 PR,下面逐条展开其含义。
2.1 v2.1.1 —— "Types":补齐 TypeScript 支持
这是该包第一次获得类型定义,对应文件为 packages/collection-pluck/index.d.ts。它定义了三个重载,覆盖数组与对象两类输入:
declare function pluck< P extends string | number | symbol, O extends Record<P, unknown>, >(collection: O[], propertyName: P): O[P][]; declare function pluck< P extends string | number | symbol, O extends Record<P, unknown> >(collection: Partial<O>[], propertyName: P): Partial<O>[P][]; declare function pluck< P extends string | number | symbol, K extends string | number | symbol, C extends {[k in K]: Partial<Record<P, unknown>>} >( collection: C, propertyName: P, ): {[k in K]: C[k][P]} export = pluck;三个重载分别处理三种情况:
- 完整数组:每个成员都拥有属性
P时,返回类型为O[P][],即属性值类型的数组; - 可缺省数组:成员可能缺少属性
P(Partial<O>)时,返回类型为Partial<O>[P][],即值中可能包含undefined; - 对象集合:利用 TypeScript 的映射类型(mapped type)
{[k in K]: C[k][P]},在保持原键K不变的前提下推导每个键对应值的类型。
值得注意的两点:
- 属性名
P被约束为string | number | symbol——这与运行时的实现完全吻合(见后文:数值属性名如pluck(arr, 1)也完全受支持); - 文件末尾使用
export = pluck(而非export default),配合package.json中的"types": "index.d.ts",可在import pluck = require('./index')与esModuleInterop两种模式下正常工作。
仓库还为类型定义配备了专门的类型测试 packages/collection-pluck/index.tests.ts,覆盖数十种场景:字符串/数值/Symbol/枚举(enum)属性名、空数组返回never[]、混合类型推导、@ts-expect-error校验错误调用等。这些类型测试由仓库根目录 package.json 中的npm run test-types(即tsc)统一执行。
2.2 v2.2.0 —— package.json 更新(#467、#483)
该版本对package.json的main/module/exports字段做了整理。当前 packages/collection-pluck/package.json 的发布入口配置如下:
{ "main": "index.cjs", "types": "index.d.ts", "module": "index.mjs", "exports": { ".": { "require": "./index.cjs", "types": "./index.d.ts", "default": "./index.mjs" }, "./package.json": "./package.json" } }这套配置解决了两个典型问题:
module字段:告知打包工具(如 Rollup、webpack)ESM 版本入口index.mjs,让打包器在保留 tree-shaking 能力的同时直接使用原生 ESM;exports字段(条件导出):精确声明不同导入环境下该解析到哪个文件——require走 CJS、types供 TypeScript 使用、default兜底走 ESM。同时通过"./package.json": "./package.json"显式放行对包内package.json的访问(否则 Node 会因exports包裹而拒绝解析该路径)。
(注:#467、#483 的具体 issue 内容不在仓库内,此处仅就其最终产物——当前package.json的导出配置——进行说明。)
2.3 v2.2.1 —— 调整 exports 顺序:default 置后(#488)
这是一个小而关键的兼容性修复:将exports对象中的"default"条件移动到最后。为什么顺序重要?因为 Node.js 解析exports条件时按对象键的书写顺序匹配,先命中的条件优先。若default被写在前面,Node 会直接命中它并返回 ESM 文件index.mjs,导致require('just-pluck-it')这类 CJS 调用在旧版 Node 下无法正确加载(甚至报错)。
修复后的顺序require→types→default(见 packages/collection-pluck/package.json),保证:
- CJS 环境优先匹配
require,加载index.cjs; - TypeScript 优先读取
types; - 仅当上述条件都不匹配时,才落入
default(ESM)。
这个顺序约定已成为社区双格式 npm 包的通用实践,just-pluck-it在 2.2.1 中将其落实。
2.4 v2.3.0 —— 重命名 Node 模块为 .cjs
这是最近一次版本变更:把 CommonJS 入口文件由index.js重命名为index.cjs,明确区分两种模块格式的文件扩展名,避免"同目录下index.js+index.mjs"共存时,工具链对模块类型产生歧义。变更后的文件布局(见 packages/collection-pluck 目录):
index.cjs——手写的 CommonJS 实现,作为main与exports.require入口;index.mjs——由 Rollup 从index.cjs构建生成的 ESM 版本;index.d.ts——TypeScript 类型定义。
从仓库现状可以观察到,这一.cjs重命名并非一次性全量完成:例如 packages/function-pipe/package.json 的main仍是index.js。这印证了 just 仓库的模块改造是逐包推进的,而just-pluck-it在 2.3.0 率先完成了向.cjs的迁移。
三、双模块架构:从手写 CJS 到构建生成的 ESM
3.1 index.cjs:核心实现全解
packages/collection-pluck/index.cjs 是整个包的逻辑本体,完整实现如下:
function pluck(collection, propertyName) { if (!collection || typeof collection != 'object') { return new Error('expected first argument to be an object or array'); } var result, len, i, keys, key; if (Array.isArray(collection)) { result = []; len = collection.length; for (i = 0; i < len; i++) { result.push(collection[i][propertyName]); } } else { result = {}; keys = Object.keys(collection); len = keys.length; for (i = 0; i < len; i++) { key = keys[i]; result[key] = collection[key][propertyName]; } } return result; } module.exports = pluck;几个实现要点:
- 参数校验:
!collection || typeof collection != 'object'会拦截null、undefined、字符串、数字等非法输入,返回new Error('expected first argument to be an object or array')。注意这里用的是返回 Error 对象而非throw——这是 just 系列工具一贯的"宽容"风格,由调用方自行决定是否检查返回值; - 数组分支:
Array.isArray(collection)走索引循环,逐个push属性值,返回新数组,不改动原数组; - 对象分支:通过
Object.keys枚举自有可枚举键,保持键序与输入一致,返回新对象,同样不修改输入; - 数值属性名:由于
collection[i][propertyName]直接以方括号取值,propertyName完全支持数字与 Symbol,运行时并不区分属性名类型(这与 2.1.1 引入的类型约束string | number | symbol一一对应); - 缺省属性:当某成员缺少目标属性时,取到的值是
undefined(对象分支下该键的值即为undefined),运行时不做默认值填充。
3.2 构建链路:Rollup 如何产出 index.mjs
index.mjs并非手写,而是通过构建脚本自动生成的。每个包内的 packages/collection-pluck/rollup.config.js 仅三行:
const createRollupConfig = require('../../config/createRollupConfig'); module.exports = createRollupConfig(__dirname);真正的构建逻辑集中在 config/createRollupConfig.js:
module.exports = function createRollupConfig(path) { return { input: resolve(path, './index.cjs'), output: { file: resolve(path, './index.mjs'), format: 'esm', }, plugins: [cjs()], }; };即:以index.cjs为输入,借助@rollup/plugin-commonjs将 CommonJS 转译为 ESM,输出为index.mjs。这也解释了为什么 packages/collection-pluck/index.mjs 的开头会出现var collectionPluck = pluck;、结尾出现export {collectionPluck as default};——这是 commonjs 插件对module.exports的标准互操作改写。仓库根目录通过npm run build(lerna run build --stream,见 package.json)即可一键重建所有包的 ESM 产物。
四、运行时行为验证:tape 测试用例剖析
just 仓库为每个工具包都提供了基于 tape 的运行时测试,just-pluck-it的测试位于 test/collection-pluck/index.cjs,通过 test/util/test.js 包装为带模块名的测试用例([collection-pluck] 用例名)。仓库根目录的npm test会统一执行tape ./test/**/index.cjs | tap-spec(见 package.json)。
六个测试用例覆盖了包的全部核心行为:
1. 数组提取(test/collection-pluck/index.cjs):pluck([{a:1,b:2},{a:4,b:3},{a:2,b:5}], 'a')得到[1, 4, 2];属性值可以是任意类型——数组、正则、对象均被原样取出。
2. 对象提取(L14-L23):pluck({x:{a:1,b:2}, y:{a:4,b:3}, z:{a:2,b:5}}, 'a')得到{x:1, y:4, z:2};若某成员缺少该属性(如y: {a:4}取'b'),对应键的值为undefined。
3-4. 空输入(L25-L35):pluck({}, 'a')返回{},pluck([], 'a')返回[]——空集合返回同类型的空结果。
5-6. 数值属性名(L37-L53):无论是"数组的数组"(pluck([['a',0],[100,1,'xyz'],[{},4]], 1)得到[0, 1, 4])还是"对象值为数组"(pluck({x:[0,1,2], y:[null,-1]}, 1)得到{x:1, y:-1}),数字下标都能被正确提取——因为数组的"属性名"本质就是数字索引,验证了propertyName支持number类型的运行时行为。
这些测试与 packages/collection-pluck/index.tests.ts 的类型测试互为印证:运行时测试保证行为正确,类型测试保证推导精确,二者共同构成了这个"微型"工具包的质量防线。
五、小结:从一份 CHANGELOG 看微型 npm 包的工程化样本
just-pluck-it的 CHANGELOG 虽然只有四条记录,却浓缩了一个 npm 包走向成熟的完整轨迹:
- v2.1.1引入类型定义,满足 TypeScript 用户的类型安全诉求;
- v2.2.0完善
package.json导出元数据,适配现代打包器与 Node 条件导出; - v2.2.1修正
exports条件顺序,消除 CJS/ESM 双格式共存的加载歧义; - v2.3.0规范
.cjs命名,让模块格式一目了然。
而其功能本身——约 20 行、零依赖、不改动输入、同时支持数组与对象、兼容任意属性名类型——恰好体现了 just 项目"do just one thing"的核心理念。对读者而言,这套"手写 CJS 源码 + Rollup 产出 ESM + 类型定义 + 运行时/类型双层测试 + Changesets 变更记录"的组合,是一份可以直接借鉴的轻量 npm 工具包发布范式。若要深入实践,建议从 packages/collection-pluck 目录入手,对照 index.cjs、index.d.ts 与 test/collection-pluck/index.cjs 三者逐一阅读。
- 开发工具
【免费下载链接】just
A library of dependency-free JavaScript utilities that do just one thing.
相关推荐
just-flush 版本演进解读:从 CHANGELOG 看一个零依赖工具库的模块化与类型设计
just flush 版本演进解读:从 CHANGELOG 看一个零依赖工具库的模块化与类型设计 导读 本文以 just 项目( packages/collec
开发工具just-flatten-it 版本演进全解析:从 CHANGELOG 到零依赖数组扁平化的源码实现
just flatten it 版本演进全解析:从 CHANGELOG 到零依赖数组扁平化的源码实现 本文以 packages/array flatten/CH
开发工具从 CHANGELOG 解读 just-clone:一款零依赖深度克隆工具的实现与版本演进
从 CHANGELOG 解读 just clone:一款零依赖深度克隆工具的实现与版本演进 本文以 just clone https://link.gitcod
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考