HOW - 编写并发布一个 npm 包模块(含 CLI 工具包开发)

一、介绍:npm 包模块

npm 包模块是指通过 npm(Node Package Manager)管理的可重用的代码包。这些包可以包含 JavaScript 代码、样式表、图像、文档等资源,以及配置文件和其他必要的内容。npm 包模块使开发者能够轻松地共享和重用代码,从而加快了软件开发的速度并提高了代码质量。

npm 包模块通常具有以下特点:

  1. 独立性: 每个 npm 包模块都是独立的,可以独立安装和使用,避免了代码之间的冲突。

  2. 模块化: npm 包模块通常遵循模块化的原则,提供了清晰的接口和功能封装,使得开发者能够更容易地使用和理解代码。

  3. 依赖管理: npm 包模块可以依赖其他的 npm 包模块,通过在 package.json 文件中声明依赖关系,可以确保项目依赖的包都能被正确安装。

  4. 版本控制: npm 包模块使用语义化版本控制规范,开发者可以指定所需的版本范围,确保项目在更新包时不会因为不兼容的变化而出现问题。

  5. 社区支持: npm 是一个庞大的开源社区,开发者可以从中获取大量的 npm 包模块,也可以通过提交自己的包模块来贡献给社区。

总的来说,npm 包模块是 Node.js 生态系统中的重要组成部分,它们极大地促进了代码共享和重用,为开发者提供了更加高效和便捷的开发体验。

二、简单步骤指南

编写和发布一个 npm 模块相对简单,下面是一个基本的步骤指南:

步骤 1:创建项目目录和文件

  1. 在你的本地计算机上创建一个新的文件夹,用于你的 npm 模块。比如创建一个新的文件夹,命名为 @pharaoh/my-npm-module-1

步骤 2:编写代码 & 添加注释

  1. 在项目文件夹中编写你的代码,确保它按照 npm 模块的要求进行了组织。
// index.js
function greet(name) {
  return `Hello, ${name}!`;
}
module.exports = greet;

这里明显使用 CommonJS 模块规范。但对于新项目来说,直接使用 ES 模块是非常理想的。在后文我们也将介绍两者的区别。

  1. 添加注释

可以采用 jsdoc 或者 Typescript。前者相比后者是在满足基本类型校验体验的基础上,不需要在项目中引入 Typescript 的构建步骤,上手门槛低。不过现在也有很多 npm 包模板构建工具能帮我们提供开箱即用的 Typescript。

步骤 3:初始化 npm 包

在命令行中,进入你的项目目录,并执行 npm init 命令,按照提示填写信息,创建 package.json 文件。

{
  "name": "@pharaoh/my-npm-module-1",
  "version": "1.0.0",
  "description": "一个示例的 npm 模块",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": ["npm", "module", "example"],
  "author": "Your Name",
  "license": "MIT"
}

步骤 4:添加文档

  1. 编写清晰的文档,说明你的模块的用法、API、示例等。
  2. 在根目录下添加 README.md 文件,写入模块的说明文档。

一个 npm 模块的说明文档应该包含以下内容:

  1. 模块名称和描述: 简要介绍模块的名称和描述,让用户了解这个模块的作用和功能。
  2. 安装: 提供模块安装的指令,包括使用 npm 或 yarn 进行安装的命令。
  3. 使用方法: 提供模块的基本使用方法,包括 API 的调用方式、参数说明等。
  4. 示例: 提供一些示例代码,演示模块的基本用法,让用户更容易理解如何使用模块。
  5. API 文档: 如果模块提供了公开的 API,应该提供相应的 API 文档,包括每个函数或方法的参数说明、返回值说明等。
  6. 配置选项: 如果模块有可配置的选项,应该说明各个选项的含义和如何配置。
  7. 常见问题解答 (FAQ): 可以列出一些常见的问题和解答,帮助用户快速解决问题。
  8. 贡献指南: 如果允许其他开发者贡献代码或提出改进建议,应该提供贡献指南,说明如何进行贡献。
  9. 版本历史: 可以列出每个版本的变化记录,包括新增功能、修复的 bug、不兼容的变化等。
  10. 许可证信息: 明确指明模块的许可证信息,告知用户在何种条件下可以使用模块。

这些内容可以以文本形式编写在 README.md 文件中,也可以在模块的网站或文档页面中展示。良好的文档能够提高模块的可用性和用户体验,促进模块的使用和贡献。

步骤 5:测试

  1. 编写单元测试确保模块的功能性和稳定性。
  2. 在根目录下创建 test 文件夹,并将测试文件放入其中。

注意,很多人会使用 jest 或 mocha 作为测试框架。但从 node18(也向后移植到了 16)开始,node 内置了运行测试用例的能力。它的 api 类似于 mocha 和 jest,支持 it、test、describe、before 等关键字。要执行测试,只需运行 node --test 指令即可。无需额外配置,它将自动扫描遵循规范的目录和单测文件。

步骤 6:发布到 npm

  1. 如果没有 npm 账号,首先需要在 npm 官网上注册一个账号。
  2. 登录 npm 账号,运行 npm login 命令登录。
  3. 运行 npm publish 命令,将你的包发布到 npm 上。

步骤 7:维护和更新

  1. 定期更新你的模块,修复 bug,添加新功能。
  2. 响应用户的反馈和贡献。

注意事项

  • 在发布前仔细检查你的代码和文档,确保它们符合最佳实践和质量标准。
  • 遵循语义化版本控制规范,即 MAJOR.MINOR.PATCH 的版本号格式。
  • 确保你的模块名称在 npm 上是唯一的,以避免命名冲突。

三、模块规范选择

对于前端开发者使用的 npm 包,常见的模块规范选择包括 CommonJS、ES Modules 和 UMD(Universal Module Definition)。这些规范各有优缺点,你可以根据你的包的特点和目标受众选择适合的规范。

下面我们分别学习三种模块规范的特点。

3.1 CommonJS

  • 输出格式: 使用 module.exports 导出模块,在 Node.js 环境中广泛使用。
  • 优点: 兼容性好,在 Node.js 环境下无需额外配置即可直接使用。
  • 缺点: 在浏览器端需要使用打包工具(如Webpack、Browserify)进行转换才能使用,不能直接在浏览器环境下使用。

Webpack 对 CommonJS 模块进行了以下转换:

  1. 识别模块依赖关系: Webpack 首先会识别项目中的所有 CommonJS 模块,然后分析它们之间的依赖关系,构建模块之间的依赖图。
  2. 路径解析: Webpack 会解析模块的路径,包括相对路径和绝对路径,以确定模块的位置。
  3. 模块标识符处理: Webpack 对模块标识符将进行处理,将模块路径映射为模块 ID,以减少模块标识符的长度和提高查找效率。
  4. 转换为闭包函数: 对于每个 CommonJS 模块,Webpack 会将其包装在一个闭包函数中,该闭包函数接收几个参数,包括 moduleexports__webpack_require__ 等,以模拟 Node.js 环境中的模块加载行为。
  5. 模块打包: Webpack 将所有的 CommonJS 模块及其依赖打包成一个或多个 bundle 文件,这些文件可以在浏览器中直接执行。

更多细节可以阅读 WHAT - Webpack 详解系列(三)

总的来说,Webpack 对 CommonJS 模块进行了转换和打包,使得它们能够在浏览器环境中直接执行,同时保留了模块之间的依赖关系和加载行为。

3.2 ES Modules

  • 输出格式: 使用 exportimport 关键字导出和导入模块,在现代浏览器和支持 ES Modules 的环境中使用。
  • 优点: 原生支持浏览器环境,可以直接在浏览器中使用,不需要额外的打包工具。
  • 缺点: 兼容性较差,在某些老旧的浏览器中无法直接使用,需要使用转换工具(如Babel)进行转换。

目前越来越多的开发者和工具都在转向使用 ES Modules,以追求更现代化的开发方式和更优越的性能:

1. 性能优势

ES Modules 在浏览器端的加载和解析速度通常比 CommonJS 模块更快,因为它们可以进行静态分析和异步加载,从而提高了页面加载性能。

当谈到 ES Modules 在浏览器端加载和解析速度更快时,主要有以下几个原因:

  1. 静态分析:ES Modules 的导入语句是静态的,意味着它们可以在代码解析阶段被解析和优化,而不需要等到运行时才能确定模块的依赖关系。这使得浏览器可以在加载过程中更早地知道需要加载哪些模块,从而提高了加载效率。

  2. 异步加载:ES Modules 支持异步加载,可以在需要的时候动态地加载模块,而不是一次性加载所有模块。这使得浏览器可以在页面加载完成后再去加载和执行模块,从而减少了页面加载时间。

我们知道,CommonJS 约定的是以同步的方式加载模块,因为 Node.js 执行机制是在启动时加载模块,执行过程中只是使用模块,所以这种方式不会有问题。但是如果要在浏览器端使用同步的加载模式,就会引起大量的同步模式请求,导致应用运行效率低下。

下面是一个具体的例子来说明 ES Modules 在浏览器端加载和解析速度更快的情况:

假设有两个模块 module1.jsmodule2.js,它们分别导入了一些其他模块,并且存在一些耗时的操作。这里假设 module1.jsmodule2.js 都是 ES Modules,并且它们使用了异步加载。

// module1.js
import { func1 } from './utils.js';
// some code here
console.log('Module 1');

// 异步加载 module2.js
import('./module2.js').then(module2 => {
  module2.func2();
});
// some more code here
// module2.js
import { func3 } from './utils.js';
// some code here
console.log('Module 2');
export function func2() {
  // some code here
}

在这个例子中,浏览器在解析 module1.js 时,可以发现它导入了 module2.js,因此可以开始异步加载 module2.js。这样,在加载 module1.js 的同时,浏览器可以并行地加载和解析 module2.js,而不需要等待 module1.js 加载完成后再去加载 module2.js。这样就提高了页面加载的效率,尤其在大型项目中,可以明显地减少页面加载时间。总的来说,ES Modules 在浏览器端的静态分析和异步加载机制,使得它们的加载和解析速度通常比 CommonJS 模块更快,从而提高了页面加载性能。

2. 标准化

ES Modules 是 JavaScript 的官方标准模块系统,在现代浏览器和 Node.js 环境中得到了广泛支持。因此,使用 ES Modules 可以使代码更具标准化和未来性。

3. Babel 转换

尽管 ES Modules 在现代开发中具有诸多优势,但若是需要在旧版浏览器中运行的项目或者需要与现有的 CommonJS 代码库进行交互的项目。在这些情况下,可能需要使用工具(如 Babel)将 ES Modules 转换为 CommonJS 模块,以确保兼容性。

Babel 是一个 JavaScript 编译器,主要用于将现代 JavaScript 代码转换为向后兼容的 JavaScript 代码,以便在不同环境中运行。Babel 转换实际上包括以下几个方面的工作:语法转换、API 转换、模块转换等。

4. .mjs vs .js

.mjs.js 文件扩展名在 JavaScript 中的用途不同,具体如下:

  1. .js 文件扩展名

    • .js 文件扩展名是 JavaScript 中最常见的文件扩展名,用于普通的 JavaScript 文件。
    • 在 Node.js 中,默认情况下,.js 文件被视为 CommonJS 模块,但可以通过在 package.json 中指定 “type” 字段为 “module” 来将其视为 ES 模块。
    • 在浏览器中,.js 文件可以直接通过 <script> 标签引入,并且可以包含普通的 JavaScript 代码。
  2. .mjs 文件扩展名

    • .mjs 文件扩展名是用于标识 ES 模块的文件。
    • 在 Node.js 中,默认情况下,.mjs 文件被视为 ES 模块,不需要额外的配置。
    • 在浏览器中,.mjs 文件也可以通过 <script type="module"> 标签引入,作为 ES 模块加载和解析。

总的来说,.mjs 文件扩展名更直观地表示它是一个 ES 模块,而 .js 文件扩展名则更通用,可以用于普通的 JavaScript 文件,也可以用于在 Node.js 中将文件视为 ES 模块。选择使用哪种文件扩展名取决于你的项目需求以及个人偏好。

当你决定选择开发 ES 模块时,建议将文件保存为 .mjs,它是一种遵循 ECMAScript 模块规范的 JavaScript 源代码文件。

3.3 UMD (Universal Module Definition)

  • 输出格式: 兼容 CommonJS、AMD 和全局变量(即在浏览器中通过<script>标签引入)的模块格式。
  • 优点: 兼容性强,在各种环境中都可以使用。
  • 缺点: 代码相对冗余,文件大小可能会比较大。

3.4 总结 & 从使用者角度

如果你的包主要是为了在浏览器中使用,并且你想要原生支持 ES Modules,那么可以选择 ES Modules 规范来编写你的包模块。如果你的包主要是为了在 Node.js 环境中使用,或者需要兼容各种环境,那么可以选择 CommonJS 或 UMD 规范。

当然,从使用者角度,可以根据以下几个方面进行分析:

  1. 目标环境:首先要考虑你的项目运行的目标环境是什么。如果你的项目是在浏览器端运行,并且你想要原生支持 ES Modules,那么选择一个支持 ES Modules 的 npm 包会更加方便。如果你的项目是在 Node.js 环境中运行,或者需要在不支持 ES Modules 的浏览器环境中使用,那么选择一个支持 CommonJS 或 UMD 规范的 npm 包可能更合适。

  2. 兼容性需求:如果你的项目需要兼容旧版本的浏览器或 Node.js,那么你可能需要选择一个支持 CommonJS 或 UMD 规范的 npm 包,因为这些规范在更早的环境中具有更好的兼容性。如果你的项目不需要考虑兼容性问题,那么可以优先选择支持 ES Modules 的 npm 包,以便利用现代化的开发特性和性能优势。

  3. 依赖关系:如果你的项目已经使用了大量的 CommonJS 模块或 UMD 模块,并且需要与现有的模块进行交互,那么选择一个支持相同模块规范的 npm 包可能更加方便,可以减少转换和适配的工作量。如果你的项目是全新的,或者可以独立使用 ES Modules 的 npm 包,那么选择一个支持 ES Modules 的 npm 包可能更具有未来性。

  4. 社区支持和生态系统:最后,你还可以考虑 npm 包的社区支持和生态系统。通常来说,支持 ES Modules 的 npm 包可能会更容易集成到现有的工具链和生态系统中,因为越来越多的开发者和工具都在逐渐转向使用 ES Modules。

另外,你也可以查看 npm 包的文档、社区活跃度、更新频率等指标,来评估其质量和可靠性。

四、语义化版本控制规范:SemVer

语义化版本(Semantic Versioning),通常简称为 SemVer,是一种用于标识软件版本号的规范化方法。在 npm 发布和后续更新维护时需要遵循 SemVer 进行版本号更新。

它的目标是为了让开发者和用户能够更清晰地理解软件版本之间的变化,以及这些变化对于他们的项目可能造成的影响。SemVer 规范了版本号的格式和意义,由三部分组成:主版本号、次版本号和修订版本号,格式为 MAJOR.MINOR.PATCH

具体来说,语义化版本的版本号规则如下:

  1. 主版本号(MAJOR): 当你做了不兼容的 API 修改时(比如删除了已经存在的 API、改变了 API 的调用方式等),你应该升级主版本号。这意味着这个新版本不再向后兼容旧版本,可能会导致现有的代码无法工作。

  2. 次版本号(MINOR): 当你做了向后兼容的功能性新增或变化时(比如新增了 API、增加了功能、改进了现有功能等),你应该升级次版本号。这意味着这个新版本向后兼容旧版本,现有的代码可以继续工作,但可能需要修改以使用新功能。

  3. 修订版本号(PATCH): 当你做了向后兼容的 bug 修复时,你应该升级修订版本号。这意味着这个新版本只是对旧版本的一些错误进行了修复,不会引入新功能,现有的代码应该能够继续工作。

  4. 预发布标识符和构建标识符: 此外,SemVer 还允许在版本号之后添加预发布标识符(pre-release identifier)和构建标识符(build metadata)。预发布标识符可以用来标识开发阶段的版本,如 alpha、beta 或 rc(Release Candidate),而构建标识符则用来标识构建版本或元数据,比如构建号或提交哈希值。假设你的 npm 包名称是 my-package,版本号是 1.0.0,你想要发布一个预发布版本并标识为 beta,同时添加构建标识符来表示构建号。你可以将版本号设置为 1.0.0-beta.1+20220424。主版本号为 1,次版本号为 0,修订版本号为 0,预发布标识符为 beta.1,表示这是一个 beta 预发布版本,是对 1.0.0 版本的测试版本。构建标识符为 20220424,表示这个版本是在 2022 年 4 月 24 日构建的。这样的版本号可以让用户清楚地知道这个版本是一个预发布版本,而且还可以了解到它的构建日期。发布后的用户可以选择是否使用这个预发布版本,或者等待稳定版本的发布。

通过遵循语义化版本规范,开发者和用户可以更好地理解软件版本之间的变化,并更有效地管理软件的依赖关系,从而降低升级软件版本所带来的风险。

五、配置文件

5.1 package.json

示例:

 {
   // 包的名称
   "name": "changelog-tool",

   // 包的版本号
   "version": "0.5.0",

   // 这将显示在NPM搜索结果中
   "description": "A CLI tool for manipulating changelogs",

   // 这告诉Node这是一个ESM包
   // 当然不是严格需要的,如果我们在每个地方都是使用 .mjs
   "type": "module",

   // 如果需要在编码的时候使用此包中的方法(不是 CLI 中),则需要在这里指定导出的模块入口文件
   "main": "index.mjs",

   // 将必须的文件才发布到 npm
   "files": {
      "dist",
      "types",
      //...
   }

   "scripts": {
     // 运行测试用例
     "test": "node --test",
   },

   // 方便更好的在 npmjs.org 上发现此包
   "keywords": [
     "changelog",
     "markdown"
   ],

   // 作者信息
   "author": "Evert Pot (https://evertpot.com/)",

   // 做任何你想做的事(MIT协议基本没有约束)
   "license": "MIT",

   "engine": {
     // 警告尚未升级的用户
     "node": ">16"
   },

   "bin": {
     // 指定执行文件,当人们安装这个包时,可以通过 `npx changelog` 执行
     // 如果全局安装了这个包,就会有一个 `changelog` 命令
     "changelog": "./cli.mjs"
   },
   "devDependencies": {
     "@types/node": "^18.11.19",
   }
 }

MIT协议:MIT 许可证是一种开源软件许可证,它允许被许可人自由地使用、修改、分发和商用软件。以下是 MIT 许可证的主要特点和要点:1. 免责声明:MIT 许可证包含了一份免责声明,指明软件是按“原样”提供,没有任何明示或暗示的担保或条件。2. 许可权限:MIT 许可证授予被许可人在几乎所有情况下无限制地使用、复制、修改、合并、出版、分发、再许可和/或销售软件及其副本。3. 版权声明:MIT 许可证要求在软件的所有副本或实质性部分中包含版权声明和许可声明。这通常是通过在源代码文件顶部包含一份包含许可声明的注释来实现的。4. 无保留许可:MIT 许可证允许将软件用于专有软件中,即使修改了原始软件,也不需要将修改后的代码公开。5. 适用范围:MIT 许可证对商业和非商业使用都是兼容的,并且不包含任何针对特定用途或领域的限制。
总的来说,MIT 许可证是一种非常宽松和灵活的开源许可证,它促进了自由开发和创新,并为开发者和用户提供了极大的自由度和灵活性。

5.2 .gitignore & .npmignore

.gitignore.npmignore 文件都是用来指定在版本控制系统中忽略的文件和文件夹的配置文件。虽然它们的作用有些相似,但它们在使用场景和生态系统中的位置略有不同。

  1. .gitignore

    • .gitignore 文件用于指定在 Git 版本控制中应该被忽略的文件和文件夹。
    • 在这个文件中列出的文件和文件夹将不会被 Git 跟踪或提交到版本库中。
    • 通常,你会在 .gitignore 文件中包含一些与开发环境、操作系统、编辑器临时文件等相关的配置,如 node_modules、构建输出等,以确保它们不会被提交到版本库中。
  2. .npmignore

    • .npmignore 文件用于指定在发布 npm 包时应该被忽略的文件和文件夹。
    • .gitignore 类似,列出的文件和文件夹将不会被包含在发布的 npm 包中。
    • 不同之处在于,.npmignore 文件主要用于 npm 包发布时,用来过滤掉一些不必要的文件,例如测试代码、文档源文件、示例等,以减小包的大小和提高发布效率。

虽然它们在使用场景和目的上有所不同,但在实际应用中通常会同时使用 .gitignore.npmignore 文件来管理项目中的忽略文件。这样可以确保在开发和发布过程中都能够正确地过滤掉不必要的文件,使得版本控制和包发布更加清晰和高效。

另外,如果在发布 npm 包时没有提供 .npmignore 文件,npm 将会使用 .gitignore 文件中列出的规则来确定哪些文件应该被忽略,不包含在发布的 npm 包中。这种行为是 npm 的默认行为,它会首先查找 .npmignore 文件,如果找不到则会使用 .gitignore 文件。这样设计的目的是为了提供一种方便的方式,让开发者可以在项目中只维护一个忽略文件,并在发布 npm 包时自动使用相同的规则

5.3 package.json files vs .npmignore

如果你在 package.json 文件中指定了 files 属性,那么 npm 在发布包时将会根据该属性指定的文件和文件夹列表来确定应该包含在发布的 npm 包中的内容。这意味着只有在 files 属性中列出的文件和文件夹才会被包含在 npm 包中,而其他文件将被忽略。

在这种情况下,.npmignore 文件通常不是必需的,因为 npm 会优先使用 files 属性指定的内容来决定包的内容。

然而,如果你希望在发布时排除 files 属性中列出的某些文件或文件夹,那么你仍然可以创建 .npmignore 文件,并在其中列出要排除的内容。

六、CLI 工具包开发必备要点

上述示例仅仅是一个可执行函数的模块封装,我们有时候也需要提供一个可执行 CLI 工具。

什么是 CLI

1. 介绍

命令行界面:https://zh.wikipedia.org/wiki/CLI

CLI(Command Line Interface,命令行界面)工具包是一种用于在命令行界面执行各种任务和操作的软件包或工具集合。这些工具包通常由开发人员创建,旨在简化开发、部署、测试等工作流程,并提供一种灵活、高效的方式来执行各种任务。

2. 发展历史

通过 CLI 工具的发展历史,可以亏弹出前端工程化的发展史。
请添加图片描述

3. 特点

CLI 工具包通常具有以下特点:

  1. 命令行界面:CLI 工具包提供了一组命令行命令或选项,用户可以通过在终端或命令提示符中输入这些命令来执行相应的操作。

  2. 任务自动化:CLI 工具包可以用于自动化各种常见任务,如构建项目、运行测试、部署应用程序等。通过简单的命令或选项,可以执行复杂的任务并减少手动操作。

  3. 配置文件支持:CLI 工具包通常支持使用配置文件来定义和定制任务的行为。通过配置文件,用户可以指定各种选项、参数和设置,以满足其特定的需求。

  4. 插件系统:一些 CLI 工具包支持插件系统,允许用户通过安装和配置插件来扩展工具包的功能。这样可以使工具包更加灵活和可扩展。

  5. 跨平台性:CLI 工具包通常具有跨平台性,可以在不同的操作系统上运行,如 Windows、Linux、macOS 等。

一些常见的 CLI 工具包包括 webpackgulpgruntcreate-react-app 等,它们广泛用于前端开发、构建和部署等场景。这些工具包提供了丰富的功能和选项,帮助开发人员更高效地进行项目开发和管理。

6.1 命令行参数解析方法

CLI 工具包提供了一组命令行命令或选项,用户可以通过在终端或命令提示符中输入这些命令来执行相应的操作。

HOW - 命令行命令或选项解析方式 中我们详细介绍过相关内容。

示例:

import { parseArgs } from 'node:util'
cpmst { positionals, valuse } = parseArgs({
    options: {
        help: { // --help
            type: 'boolean',
            short: 'h', // -h
            default: false,
        },
        all: { // --all
            type: 'boolean',
            default: false,
        },
        message: { // --message "hello"
            type: 'string',
            short: 'm', // --m "hello"
        },
        patch: {
            type: 'boolean'
        },
        minor: {
            type: 'boolean'
        },
        major: {
            type: 'boolean'
        }
    },
    allowPositionals: true,
})

这段代码使用了 Node.js 内置模块 util 中的 parseArgs 函数,该函数用于解析命令行参数,并返回一个对象,该对象包含解析后的参数和选项。

const { positionals, values } = parseArgs({ ... })positionals 属性包含了解析后的位置参数(positional arguments),即不带前缀 --- 的参数。values 属性包含了解析后的带有前缀 --- 的参数和它们的值(即选项)。

parseArgs({ ... }):这是调用 parseArgs 函数并传递参数的语法。在这个参数对象中,包含了需要解析的选项(options)以及一些其他的配置信息。options 对象定义了可接受的选项以及它们的类型、短选项(short)、默认值等信息。allowPositionals: true 表示允许解析位置参数。

总的来说,这段代码的目的是使用 util 模块中的 parseArgs 函数解析命令行参数,并将解析后的位置参数和选项存储在 positionalsvalues 变量中,以供后续代码使用。

6.2 确定输入参数及其实现功能

// 以 vue 为例

// 使用开源的第三方的一个命令管理的 library
const program = require('commander')

// 支持
// 展示版本
// 接收命令和描述

program
    .version(`@vue/cli ${require('../package').version}`)
    .usage('<command> [options]')
program
    .command('create <app-name>')
    .descriptioin('create a new project powered by vue-cli-service')
    .option('-p --preset <presetName>', 'xxx')
    .option('...')
    .action(name, options) => {
        if (minimist(process.argv.slice(3))._.length > 1) {
            console.log(chalk.yellow('\n Info: You provided more than one argument. The first one will be used as the app\'s name, the rest are ignored.'))
        }
        // ...

        require('../lib/create')(name, options)
    }
// vue -h 会显示上述信息


// minimist 解析命令参数示例
  // const args = process.argv.slice(2);
  // 1.
  // node app.js joe
  // console.log('user argvs: ', args[0]);
  // 2.
  // node app.js --name=joe
  // node app.js --name joe
  // const argsMap = require('minimist')(args)
  // console.log('user argvs: ', argsMap['name']);
  
// chalk 着色

6.2 提供帮助信息

--help,越详细越好。

6.4 指定执行在文件头部指定解析方式

因为工具是用 JavaScript 写的,系统终端要执行,需要指定解析方式,比如 node

// bin/vue.js 第一行代码会指定解析方式
#!/usr/bin/env node

6.5 可扩展、可升级

避免 CLI 工具包含功能过多,包体积会越来越庞大,不太好维护,一般通过提供一个配置文件,比如 vite.config.js,支持动态配置插件或 option 来扩展。

// 以 vue-cli-service 为例
// 会有一个 loadFileConfig.js
moudle.exports = function loadFileConfig(context) {

    let fileConfigPath;
    const possibleConfigPaths = [
        process.env.VUE_CLI_SERVICE_CONFIG_PATH,
        './vue.config.js',
        './vue.config.cjs',
        './vue.config.mjs'
    ]
    for (const p of possibleConfigPaths) {
        const resolvePath = p && path.resolve(context, p)
        if (resolvePath && fs.existSync(resolvePath)) {
            fileConfigPath = resolvePath;
            break;
        }
    }
    
    if (fileConfigPath) {
        //...
    }
}

另外,当用户安装了 1.0 版本,基本不太可能再去升级,那么 CLI 需要主动调用 npm 接口获取包的最新版本,与当前本地的版本进行比较,提示用户可以升级到最新版本。

具体来说,实现 CLI 主动检查并提示用户升级到最新版本的功能通常可以分为以下几个步骤:

  1. 获取本地已安装版本:在 CLI 中,你需要获取当前用户已经安装的你的包的版本信息。可以通过读取 package.json 文件或者调用相应的 Node.js API 来获取当前安装的版本号。

  2. 调用 npm Registry API:CLI 需要向 npm Registry 发送请求,获取你的包在 npm Registry 上的最新版本信息。你可以使用 npm 的官方 Registry API 来获取包的元数据,其中包括最新版本号等信息。

  3. 比较版本号:将本地已安装的版本号与从 npm Registry 获取的最新版本号进行比较,判断是否需要提示用户升级。通常,可以使用版本号比较工具库来处理版本号比较,例如 semver

  4. 提示用户升级:如果从 npm Registry 获取的最新版本号高于本地已安装的版本号,则向用户显示升级提示。提示信息可以包括新版本的特性、修复的 bug、安全性更新等信息,以便用户做出决定。

  5. 提供升级命令:在提示用户升级时,最好提供一个简单的命令或者指引,让用户可以方便地升级到最新版本。可以直接使用 npm 提供的升级命令,例如 npm update your-package

  6. 可选的自动升级功能:如果你希望用户能够自动升级到最新版本,你可以提供一个选项或者配置来启用自动升级功能。用户在启用后,CLI 将会在后台自动检查并升级到最新版本。

需要注意的是,尽管提示用户升级到最新版本是一个有益的功能,但应该尊重用户的偏好和隐私。不应该强制用户升级,而应该给予用户选择的权利,并尽可能提供明确的信息和选项。

6.6 本地缓存

CLI 工具包应该支持存储一些使用记录和日志(一般存在一个特殊日志名字的文件里,比如 npm 的就叫 npm.rc)。

  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值