源码学习 validate-npm-package-name

389 阅读5分钟

一、validate-npm-package-name 作用

顾名思义 validate-npm-package-name 验证包名称,那到底是不是呢?去Github看一下。

Github 地址:validate-npm-package-name

果然是的,接下来看下怎么使用。

1.1 内置规则

  1. 包名长度大于零
  2. 包名中的所有字符都必须是小写的,即不允许使用大写或混合大写的名称
  1. 包名可以由连字符(也就是-)组成
  2. 包名不能包含任何非url安全字符(因为名称最终是url的一部分)
  1. 包名不能以 . 或者 _ 开头
  2. 包名称首尾不能有 空格
  1. 包名称不应包含以下任何字符: ~)('!*
  2. 包名称不能与 node 核心模块名称相同 ,如
    • http
    • stream
    • fs
  1. 包名不能在黑名单内 黑名单:node_modules favicon.ico
  2. 包名称长度不能超过214

1.2 简单尝试下功能

验证名称

var validate = require("validate-npm-package-name")

validate("some-package")
validate("example.com")
validate("under_score")
validate("123numeric")
validate("@npm/thingy")
validate("@jane/foo.js")

上边的名称都是有效的,所以会返回以下对象

{
  validForNewPackages: true,
  validForOldPackages: true
}

试试规则不允许的名称

validate("excited!") // ! 不允许
validate(" leading-space:and:weirdchars")  // url 处理 : 时不友好

可以看到返回值发生了变化,并且给出了原因。

{
  validForNewPackages: false,
  validForOldPackages: false,
  errors: [
    'name cannot contain leading or trailing spaces',
    'name can only contain URL-friendly characters'
  ]
}

接下来走进源码的世界。

二、源码

2.1 怎么看?

看源码首先是要找到入口文件,一般都是根据 package.json文件的相关字段来找。

对于 package.json 文件其实有很多的字段配置,实在太多了,我也记不住,常用的也就那几个,这里没有找到 官方的文件规范, 贴个掘金的讲解:关于前端大管家 package.json,你知道多少?

1. main 字段

main 字段用来指定加载的入口文件,在 browser 和 Node 环境中都可以使用。

如果我们将项目发布为npm包,那么当使用 require 导入npm包时,返回的就是main字段所列出的文件的module.exports 属性。

如果不指定该字段,默认是项目根目录下的index.js。如果没找到,就会报错。

"main": "./src/index.js",

2 bin 字段

bin字段用来指定各个内部命令对应的可执行文件的位置,比如

"bin": {
  "someTool": "./bin/someTool.js"
}

3 files 字段

files配置是一个数组,用来描述当把npm包作为依赖包安装时需要说明的文件列表。

当npm包发布时,files指定的文件会被推送到npm服务器中,如果指定的是文件夹,那么该文件夹下面所有的文件都会被提交。

"files": [
    "LICENSE",
    "Readme.md",
    "index.js",
    "lib/"
 ]

这里我们看看主角 validate-npm-package-namepackage.json

源码已经找到,接下来一头扎进去看看。

2.2 分析源码

还记得当初使用这个包的时候是这个使用的

var validate = require("validate-npm-package-name")

validate("some-package")

从使用来看,我们需要找到默认导出

主角就是它了,点开发现里边都是一些判断,有些写的挺巧妙的,有些写的我有点看不懂(正则),看来以后还需要加强学习。

贴部分判断

var validate = module.exports = function (name) {
  var warnings = []
  var errors = []

  // 名称不能为空
  if (!name.length) {
      errors.push('name length must be greater than zero')
  }
  // 名称首尾不能包含空格
  if (name.trim() !== name) {
      errors.push('name cannot contain leading or trailing spaces')
  }
  // 名称不能有大写
  if (name.toLowerCase() !== name) {
    warnings.push('name can no longer contain capital letters')
  }

  // 名称不能在黑名单内
  blacklist.forEach(function (blacklistedName) {
    if (name.toLowerCase() === blacklistedName) {
      errors.push(blacklistedName + ' is a blacklisted name')
    }
  })

  // 不能和 核心模块名称 冲突
  builtins.forEach(function (builtin) {
    if (name.toLowerCase() === builtin) {
      warnings.push(builtin + ' is a core module name')
    }
  })
  
  // ... 其他的一些判断
  
  return done(warnings, errors)
}

var done = function (warnings, errors) {
  var result = {
    validForNewPackages: errors.length === 0 && warnings.length === 0,
    validForOldPackages: errors.length === 0,
    warnings: warnings,
    errors: errors
  }
  if (!result.warnings.length) delete result.warnings
  if (!result.errors.length) delete result.errors
  return result
}

发现该库还是挺简单的(除了正则),从中学到了很多判断的技巧,例如:!name.length 以后在开发过程中应该也用得上

值得注意的是 引入了另外一个库 builtins 看看他是干嘛的

2.3 builtins

Github地址:builtins

使用示例:

获取当前 Node.js 版本的核心模块列表:

var builtins = require('builtins')()
assert(builtins.indexOf('http') > -1)

获取特定 Node.js 版本的核心模块列表:

var builtins = require('builtins')({ version: '6.0.0' })
assert(builtins.indexOf('http') > -1)
```js

将实验模块添加到列表中:

```js
var builtins = require('builtins')({ experimental: true })
assert(builtins.indexOf('wasi') > -1)

好奇它是怎么做到的? 源码拉下来看看(虽然实力菜,可能看不懂,但是挡不住好奇心)

老规矩,去 package.json 里找入口文件

点开一看,好家伙,又引入了另外一个库,并且还发现了一个奇怪的写法。

先来看看这个奇怪的写法:

function Foo({
  version = process.version,
  experimental = false
} = {}) {
  // do Something...  
}

说实话,刚开始看不懂,群里问了问,突然顿悟,这不就是 函数的参数解构吗,如果没有传参数的话,就给解构的值设置默认值。

跑起来一看就明白了:

function Foo({
  version = '1.0.0',
  experimental = false
} = {}) {
  console.log(version)
  console.log('experimental: ', experimental);
}

Foo()
Foo({
  version: '1.6.0',
  experimental: true
})

学到了!666~

接下来回头看看 在 builtins 中引入的 semver 是啥东东。

GIthub地址:semver

npm 的语义化版本管理工具?然后就没了?没太明白继续查查资料。

网上找啊找,找到了一个应该是官方文档

地址:语义化版本控制规范 2.0.0

简单来说就是一套规范,用于规范 项目的版本号的。

这个库 实现了跟版本控制有关的一些方法,如:

  1. 比较两个版本号的大小
  2. 验证某个版本号是否合法
  3. 提取版本号,例如从“=v1.2.1”体取出"1.2.1"
  4. 分析版本号是否属于某个范围或符合一系列条件

嗯,本次学习源码就差不多了,接下来做个总结

三、总结

  1. 复习了 package.json 各个配置字段的作用
  2. 学到了很多实用的判断方法
  3. 知道了 通过 builtins 库可以获取到指定版本的Node核心模块列表
  4. 知道了 semver 语义化版本控制规范,以及里边的一些细节,目前还记不住,需要下来多看看尽量能把一些赏识性的规则记下来。

四、 需要帮助

  1. 目前没找到 package.json 的官方文档,求助!

文中提到的仓库及相关参考: