一、validate-npm-package-name 作用和使用场景
作用:
检查npm包名是否符合标准,该工具提供一个接受字符串的函数,并且返回一个拥有2个属性的对象
使用场景示例:
const 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("https")
// 返回结果
{
validForNewPackages: false,
validForOldPackages: false,
errors: [ 'name can only contain URL-friendly characters' ]
}
复制代码
二、源码分析
源码也就100行左右,可以简单阅读一下
'use strict'
// 特殊字符开头正则
var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')
// node核心模块数组,例如http、fs、path、utils等
// 不同node版本会增加新的模块,例如8.4.0 新增 http2
var builtins = require('builtins')
// 黑名单
var blacklist = [
'node_modules',
'favicon.ico'
]
// 导出一个方法来做包名的校验,入参是包名
var validate = module.exports = function (name) {
// 收集的警告集合
var warnings = []
// 收集的错误集合
var errors = []
// 不能为null
if (name === null) {
errors.push('name cannot be null')
return done(warnings, errors)
}
// 不能为undefined
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)
}
// 不能为空
if (!name.length) {
errors.push('name length must be greater than zero')
}
// 不能以.开头
if (name.match(/^./)) {
errors.push('name cannot start with a period')
}
// 不能以_开头
if (name.match(/^_/)) {
errors.push('name cannot start with an underscore')
}
// 首尾空格
if (name.trim() !== name) {
errors.push('name cannot contain leading or trailing spaces')
}
// 不能使用黑名单里的名字(不区分大小写)
blacklist.forEach(function (blacklistedName) {
if (name.toLowerCase() === blacklistedName) {
errors.push(blacklistedName + ' is a blacklisted name')
}
})
// Generate warnings for stuff that used to be allowed
// core module names like http, events, util, etc
// 不能和node内置模块命名相同
builtins.forEach(function (builtin) {
if (name.toLowerCase() === builtin) {
warnings.push(builtin + ' is a core module name')
}
})
// really-long-package-names-------------------------------such--length-----many---wow
// the thisisareallyreallylongpackagenameitshouldpublishdowenowhavealimittothelengthofpackagenames-poch.
// 名称length不能超过214
if (name.length > 214) {
warnings.push('name can no longer contain more than 214 characters')
}
// mIxeD CaSe nAMEs
// 不能含有大写字母
if (name.toLowerCase() !== name) {
warnings.push('name can no longer contain capital letters')
}
// 不能包含特殊字符~'!()*
if (/[~'!()*]/.test(name.split('/').slice(-1)[0])) {
warnings.push('name can no longer contain special characters ("~'!()*")")
}
// 如果包名含有可以被转义符
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]
// 类似包名 @user/package 时要对 package 和 user 分别校验 url 安全字符
if (encodeURIComponent(user) === user && encodeURIComponent(pkg) === pkg) {
return done(warnings, errors)
}
}
// 包名不得包含任何非 url 安全字符
errors.push('name can only contain URL-friendly characters')
}
return done(warnings, errors)
}
// 这行代码有点不理解,为啥把正则挂载方法的属性上?
validate.scopedPackagePattern = scopedPackagePattern
// 执行函数,返回一个对象,包含验证信息
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
}
复制代码
三、总结
代码很简单,都是一些简单的字符串校验以及正则匹配,代码逻辑也很清晰,平铺直叙容易理解。第一次阅读源码,出乎我的意料...... 消除了我心中对于源码的敬畏感,再接再厉!