本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
源码地址:github1s.com/npm/validat…
validate-npm-package-name依赖了builtins用来判断是否是node原生模块的名字。是其中的一个判断条件。builtins依赖了semver用来判断当前的node版本里面都有哪些模块。
Builtins
'use strict'
var semver = require('semver')
module.exports = function ({
version = process.version,
experimental = false
} = {}) {
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'
]
if (semver.lt(version, '6.0.0')) coreModules.push('freelist') // 小于某版本才有,应该是被node删掉了
// 大于等于某版本
if (semver.gte(version, '1.0.0')) coreModules.push('v8')
if (semver.gte(version, '1.1.0')) coreModules.push('process')
if (semver.gte(version, '8.0.0')) coreModules.push('inspector')
if (semver.gte(version, '8.1.0')) coreModules.push('async_hooks')
if (semver.gte(version, '8.4.0')) coreModules.push('http2')
if (semver.gte(version, '8.5.0')) coreModules.push('perf_hooks')
if (semver.gte(version, '10.0.0')) coreModules.push('trace_events')
if (
semver.gte(version, '10.5.0') &&
(experimental || semver.gte(version, '12.0.0'))
) {
coreModules.push('worker_threads')
}
if (semver.gte(version, '12.16.0') && experimental) {
coreModules.push('wasi')
}
return coreModules
}
// lt 和 gte 的含义
/*
gt(v1, v2): v1 > v2
gte(v1, v2): v1 >= v2
lt(v1, v2): v1 < v2
lte(v1, v2): v1 <= v2
*/
validate-npm-package-name
'use strict'
// 这个正则可以分为^(?:@([^/]+?)[/])? 和 ([^/]+?)$ 两部分看
// ^(?:@([^/]+?)[/])? "@antd/"匹配这个部分,但是捕获的只有([^/]+?),是antd; 最后的?代表可选。没有匹配到返回undefined
// ([^/]+?)$ "icons" 没有/的结尾部分
var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')
var builtins = require('builtins') // 判断是否是node原生包
// 黑名单
var blacklist = [
'node_modules',
'favicon.ico'
]
var validate = module.exports = function (name) {
var warnings = [] // 存warning的数组
var errors = [] // 存error的数组
if (name === null) { // 没传,肯定不行
errors.push('name cannot be null')
return done(warnings, errors)
}
if (name === undefined) { // 传了个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(/^_/)) { 不能以_/开头, why?
errors.push('name cannot start with an underscore')
}
if (name.trim() !== name) { // 前后去空格 和 name不符,也就是多加空格不行
errors.push('name cannot contain leading or trailing spaces')
}
blacklist.forEach(function (blacklistedName) {
// 传入的name小写之后 和 黑名单里面的名字一样了,不行
/*
好像也可以写成:blacklist.includes(name.toLowerCase())
*/
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
builtins.forEach(function (builtin) {
// 如果是node原来就有的包名字,不行
/*
好像也可以写成:builtins.includes(name.toLowerCase())
*/
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) { // 文件名不能大于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')
}
// "@antd/icons".split('/').slice(-1)[0] 是 icons,也就是文件名不能有特殊符号。
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 "@antd/icons"
var nameMatch = name.match(scopedPackagePattern)
if (nameMatch) {
var user = nameMatch[1] // antd
var pkg = nameMatch[2] // icons
// 如果解码之后和原来一致可以,不一致,就可能有中文空格什么之类的吧,数字和字母不会有问题
if (encodeURIComponent(user) === user && encodeURIComponent(pkg) === pkg) {
return done(warnings, errors)
}
}
errors.push('name can only contain URL-friendly characters')
}
return done(warnings, errors) // 前面很多内容都是只push没有返回,在这里统一返回
}
// 在暴露出的函数上绑了个属性,不知道干嘛用的。
validate.scopedPackagePattern = scopedPackagePattern
/**
* @param {Array<string>} warnings 警告数组
* @param {Array<string>} errors 错误数组
* @returns {} boolean:新的包规范 boolean: 老的包规范 传入的两个数组?可选,如果没有就不返回数组
*/
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
}
这个包就是检查起的名字对不对的。一般用在脚手架里面,我们会create-react-app xxx, 这个xxx就是文件的名字,需要校验的对象,用户传入的总是不让人相信,就需要校验。
一些想说的话
一开始我也很抵触看源码,感觉非常难,有幸遇到了川哥。川哥举办的这个活动,我也是在群里混了半年之后,才看了一个源码。其实就像川哥讲的,真的不难,读好的源码,就像读一本书一样,你在跟作者对话,学习和看到更好的实现方式。如果大家不急着背八股文,就来一起读源码吧!
给源码写注释,大概就和看书的时候写写画画一样的吧。