validate-npm-package-name 源码阅读

  1. 前言

首先, 很感谢「 若川 」 大佬 提供的 一个「 源码共读 」 平台, 其实我很早就进入了,但是因为做为一个奶爸, 下班后马不停蹄地回家带娃, 所以很少有时间参与 源码共读 的活动来。

今天正好抽出时间,参与一下大佬的新一期的活动, 这一期项目是 validate-npm-package-name, 它是检验一个字符串是否是一个 有效的 包命名

  1. 学习目标

  • 了解 validate-npm-package-name 的作用和使用场景
  1. 工具介绍

Give me a string and I’ll tell you if it’s a valid npm package name.
This package exports a single synchronous function that takes a string as input and returns an object with two properties:
validForNewPackages :: Boolean
validForOldPackages :: Boolean

接受一个字符串参数, 检验该字符串是否是一个有效的包命名,
该工具 提供一个 接受字符串的函数, 并且返回 一个拥有2个属性的对象
validForNewPackages :: Boolean
validForOldPackages :: Boolean

  1. 包命名规则

  1. 包名不能是空字符串;
  2. 所有的字符串必须小写;
  3. 可以包含 连字符 - ;
  4. 包名不得包含任何非 url 安全字符;
  5. 包名不得以 . 或者 _ 开头;
  6. 包名首尾不得包含空格;
  7. 包名不得包含 ~)(‘!* 任意一个字符串;
  8. 包名不得与node.js/io.js 的核心模块 或者 保留名 以及 黑名单相同;
  9. 包名的长度不得超过 214;
  1. 示例

5.1 有效的包名

1
2
3
4
5
6
7
js复制代码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")

5.2 无效的包名

1
2
3
js复制代码// 包含 不符合 第6条 规则
validate("excited!")
validate(" leading-space:and:weirdchars")
  1. 源码阅读

6.1 代码结构

1
2
3
4
5
6
7
8
9
10
js复制代码var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$') 
var builtins = require('builtins')
var blacklist = ['node_modules','favicon.ico']
var validate = module.exports = function(name) {
// .... 检验 参数 name 是否 规范
}
validate.scopedPackagePattern = scopedPackagePattern
var done = function (warning, erros) {
// .... 返回 处理结果
}

6.1.1 scopedPackagePattern 正则表达式

var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$'

正则表达式可视化, 链接地址:jex.im/regulex/

下载.png
匹配以下字符串

  • @user 以@开头
  • @user/test
  • 非‘/’的字符串

image.png

builtins:列出了 node 所有的内置模块

6.1.2 validate函数, 结合 [包命名规则]

(1)检测传入的参数是否是字符串;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
js复制代码if (name === null) {
errors.push('name cannot be null')
return done(warnings, errors)
}

if (name === undefined) {
errors.push('name cannot be undefined')
return done(warnings, errors)
}

if (typeof name !== 'string') {
errors.push('name must be a string')
return done(warnings, errors)
}
(2)包名不能是空字符串
1
2
3
js复制代码if (!name.length) {
errors.push('name length must be greater than zero')
}
(3)包名不得以 . 或者 _ 开头
1
2
3
4
5
6
js复制代码if (name.match(/^\./)) {
errors.push('name cannot start with a period')
}
if (name.match(/^_/)) {
errors.push('name cannot start with an underscore')
}
(4) 包名首尾不得包含空格
1
2
3
4
js复制代码// trim 方法 可以去掉 字符串 两边的 空白
if (name.trim() !== name) {
errors.push('name cannot contain leading or trailing spaces')
}
(5) 包名不得与node.js/io.js 的核心模块 或者 保留名 以及 黑名单相同
1
2
3
4
5
6
7
8
9
10
11
12
13
js复制代码// No funny business
blacklist.forEach(function (blacklistedName) {
if (name.toLowerCase() === blacklistedName) {
errors.push(blacklistedName + ' is a blacklisted name')
}
})

// core module names like http, events, util, etc
builtins.forEach(function (builtin) {
if (name.toLowerCase() === builtin) {
warnings.push(builtin + ' is a core module name')
}
})
(6)包名的长度不得超过 214
1
2
3
4
5
js复制代码// really-long-package-names-------------------------------such--length-----many---wow
// the thisisareallyreallylongpackagenameitshouldpublishdowenowhavealimittothelengthofpackagenames-poch.
if (name.length > 214) {
warnings.push('name can no longer contain more than 214 characters')
}
(7)所有的字符串必须小写
1
2
3
4
js复制代码// mIxeD CaSe nAMEs
if (name.toLowerCase() !== name) {
warnings.push('name can no longer contain capital letters')
}
(8)包名不得包含 ~)(‘!* 任意一个字符串
1
2
3
js复制代码if (/[~'!()*]/.test(name.split('/').slice(-1)[0])) {
warnings.push('name can no longer contain special characters ("~\'!()*")')
}
(9)包名不得包含任何非 url 安全字符
1
2
3
4
js复制代码const regx = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$') 
const name = '@user/package'
// ?: 会忽略分组
// ['@user/package', 'user', 'package', index: 0, input: '@user/package', groups: undefined]
1
2
3
4
5
6
7
8
9
10
11
12
13
js复制代码if (encodeURIComponent(name) !== name) {
// Maybe it's a scoped package name, like @user/package
var nameMatch = name.match(scopedPackagePattern)
if (nameMatch) {
var user = nameMatch[1]
var pkg = nameMatch[2]
if (encodeURIComponent(user) === user && encodeURIComponent(pkg) === pkg) {
return done(warnings, errors)
}
}

errors.push('name can only contain URL-friendly characters')
}

6.2 done 函数

1
2
3
4
5
6
7
8
9
10
11
js复制代码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
}
  1. 总结

整个项目并不是太难, 都是一些基本的字符串判断,以及正则匹配,但是值得每个人去学习, 在编写一个工具的时候, 保证代码的逻辑清晰, 代码的书写规范。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%