1. 是什么?
是用来校验依赖包名是否合格的工具
2. 使用场景?
大多数CLI脚手架类工具中,比如create-react-app、vue-cli等。
3. 校验规则?
a.包名不能为null、空,必须是字符串格式
b.包名长度得大于0,并且不能以.和_开头
c.包名前后不能有空格
d.包名不能是保留字
e.包名不能是node的内置模块, 类似http, events, util, etc等
f.包名长度不能大于214
g.包名不能包含大写字母 类似:mIxeD CaSe nAMEs
h.包名不能包含任意一个特殊字符~'!()*
i.包名不得包含任何非 url 安全字符,例如@user/package
4. 如何使用?
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
}
//失败返回
{
validForNewPackages: false,
validForOldPackages: false,
errors: [
'name cannot contain leading or trailing spaces',
'name can only contain URL-friendly characters'
]
}
5. 源码分析
'use strict'
var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')
// node内置 module的列表
var builtins = require('builtins')
// 保留名(黑名单)
var blacklist = [
'node_modules',
'favicon.ico'
]
var validate = module.exports = function (name) {
// 警告:用于表示过去package name允许、如今不允许的兼容error
var warnings = []
// 存储不符合合格的包名的规则
var errors = []
// 包名不能为null
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)
}
// 包名长度得大于0
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')
}
// No funny business
// 包名不能是保留字
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的内置模块, 类似http, events, util, etc等
var coreModules = ["assert", "buffer", "child_process", "cluster", "console", "constants", "crypto", "dgram", "dns", "domain", "events", "fs", "http", "https", "module", "net", "os", "path", "punycode", "querystring", "readline", "repl", "stream", "string_decoder", "sys", "timers", "tls", "tty", "url", "util", "vm", "zlib"];
*/
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.
// 包名不能过长
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
//包名不得包含任何非 url 安全字符,例如@user/package
// @user/package'.match(/^(?:@([^/]+?)[/])?([^/]+?)$/) => ['@user/package', 'user', 'package', index: 0, input: '@user/package', groups: undefined]
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')
}
return done(warnings, errors)
}
// 暴露出去规则,方便使用者外部使用
validate.scopedPackagePattern = scopedPackagePattern
// 返回结果的util方法
var done = function (warnings, errors) {
var result = {
// 用来做判断包名是否符合规格
validForNewPackages: errors.length === 0 && warnings.length === 0,
// 用于表示过去package name允许、如今不允许的兼容
validForOldPackages: errors.length === 0,
warnings: warnings,
errors: errors
}
if (!result.warnings.length) delete result.warnings
if (!result.errors.length) delete result.errors
return result
}