前言
validate-npm-package-name
是一个用于检测npm包是否符合标准的包,被很多脚手架创建工具使用:vue-cli
、create-react-app
源码地址:https://github.com/npm/validate-npm-package-name
一、源码阅读工具
- 本地编辑器从github上下载源码
- 在线网页版编辑器:
github.dev
(在网页端使用 VS Code 编辑 GitHub 项目——github.dev)
这里介绍下,github.dev是github官方提供的在线vscode编辑器
,将github访问URL中的com直接替换成dev,即可在网页版vscode中打开仓库代码阅读编辑。
二、阅读源码
1. 目录结构
![](https://img-blog.csdnimg.cn/bd9ebea0dee4430fa122430dffff6abd.png)
2.package.json
- 入口文件:index.js
- builtins 依赖包会列出node所有内置模块
{
"name": "validate-npm-package-name",
"version": "3.0.0",
"description": "Give me a string and I'll tell you if it's a valid npm package name",
"main": "index.js", // 入口文件
"directories": {
"test": "test"
},
"dependencies": { // 生产依赖包
"builtins": "^1.0.3"
},
"devDependencies": { // 开发依赖包
"standard": "^8.6.0",
"tap": "^10.0.0"
},
...
}
打印 builtins 模块内容:(全是node内置模块)
[
'assert', 'buffer', 'child_process',
'cluster', 'console', 'constants',
'crypto', 'dgram', 'dns',
'domain', 'events', 'fs',
'http', 'https', 'module',
'net', 'os', 'path',
'process', 'punycode', 'querystring',
'readline', 'repl', 'stream',
'string_decoder', 'timers', 'tls',
'tty', 'url', 'util',
'v8', 'vm', 'zlib'
]
3.index.js
源码如下:
'use strict'
var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')
var builtins = require('builtins') // node中所有的内置模块列表,参考上面
var blacklist = [ // 黑名单列表,数组中的不允许设置为包名,会抛出error
'node_modules',
'favicon.ico'
]
// 全局导出validate方法
var validate = module.exports = function (name) {
// 警告:用于表示过去package name允许、如今不允许的兼容error
var warnings = []
// 存储不符号合格的包名的规则
var errors = []
// 校验包名为null,结束并返回valid error结果
if (name === null) {
errors.push('name cannot be null')
return done(warnings, errors)
}
// 包名为undefined,结束并返回valid error结果
if (name === undefined) {
errors.push('name cannot be undefined')
return done(warnings, errors)
}
// 包名为非字符串,结束并返回valid error结果
if (typeof name !== 'string') {
errors.push('name must be a string')
return done(warnings, errors)
}
// 包名字符串长度为0,抛出error
if (!name.length) {
errors.push('name length must be greater than zero')
}
// 包名以.开头,抛出error
if (name.match(/^\./)) {
errors.push('name cannot start with a period')
}
// 包名为_开头,抛出error
if (name.match(/^_/)) {
errors.push('name cannot start with an underscore')
}
// 包名字符串前后有空格,抛出error
if (name.trim() !== name) {
errors.push('name cannot contain leading or trailing spaces')
}
// 包名是否包含在上面定义的blacklist黑名单列表中,若是则不被允许,抛出error
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内置模块名一致,若小写一致,则抛出warnings
builtins.forEach(function (builtin) {
if (name.toLowerCase() === builtin) {
warnings.push(builtin + ' is a core module name')
}
})
// 包名长度超过214,抛出warnings
if (name.length > 214) {
warnings.push('name can no longer contain more than 214 characters')
}
// 包名中是否有大写,若有大写字母抛出warnings(eg:mIxeD CaSe nAMEs)
if (name.toLowerCase() !== name) {
warnings.push('name can no longer contain capital letters')
}
// 包名是否特殊字符:~'!()*,若包含抛出warning
// '@babel/core'.split('/').slice(-1)[0] --> 'core'
if (/[~'!()*]/.test(name.split('/').slice(-1)[0])) {
warnings.push('name can no longer contain special characters ("~\'!()*")')
}
// 经encodeURIComponent编码后不相等, 包含non-url-safe字符
// 关于encodeURIComponent不转义哪些字符: A-Z a-z 0-9 - _ . ! ~ * ' ( )
if (encodeURIComponent(name) !== name) {
// 如上所示:var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')
// 这可能是一个局部包,例如@user/package
var nameMatch = name.match(scopedPackagePattern)
if (nameMatch) {
var user = nameMatch[1]
var pkg = nameMatch[2]
// user 和 package部分的包名 encodeURIComponent 结果都相同,都能被转义,则正常返回valid结果
if (encodeURIComponent(user) === user && encodeURIComponent(pkg) === pkg) {
return done(warnings, errors)
}
}
// 若不符合scopedPackagePattern规则,则抛出error
errors.push('name can only contain URL-friendly characters')
}
// 最终结束,返回valid结果
return done(warnings, errors)
}
validate.scopedPackagePattern = scopedPackagePattern
// 输出valid结果:reuslt
var done = function (warnings, errors) {
// 对象格式
var result = {
// 一般用该属性来判断一个包名是否合法: 有errors或者warnings抛出
validForNewPackages: errors.length === 0 && warnings.length === 0,
// 兼容最开始node package name带来的遗留问题,那个时候有些包名不规范:有error抛出
validForOldPackages: errors.length === 0,
warnings: warnings,
errors: errors
}
// 若warning,errors数组length=0,则从reuslt对象移除对应属性
if (!result.warnings.length) delete result.warnings
if (!result.errors.length) delete result.errors
return result
}
三、使用该包
1. vue-cli中使用
vue create xxx 新建项目时,就用到了该包
源码地址:https://github1s.com/vuejs/vue-cli/blob/HEAD/packages/@vue/cli/lib/create.js#L8-L30
// @vue/cli/lib/create.js
const validateProjectName = require('validate-npm-package-name')
async function create (projectName, options) {
const cwd = options.cwd || process.cwd()
const inCurrent = projectName === '.'
const name = inCurrent ? path.relative('../', cwd) : projectName
const result = validateProjectName(name) // 这里校验
// 如上源码所示
//var result = {
// validForNewPackages: errors.length === 0 && warnings.length === 0,
// validForOldPackages: errors.length === 0,
// warnings: warnings,
// errors: errors
//}
if (!result.validForNewPackages) { // 说明有抛出error或者warning
console.error(chalk.red(`Invalid project name: "${name}"`))
result.errors && result.errors.forEach(err => { // 打印所有的error
console.error(chalk.red.dim('Error: ' + err))
})
result.warnings && result.warnings.forEach(warn => { // 打印所有的warning
console.error(chalk.red.dim('Warning: ' + warn))
})
exit(1)
}
...
}
2. create-react-app 中使用
const validateProjectName = require('validate-npm-package-name');
// 创建脚手架方法
function createApp(name, verbose, version, template, useYarn, usePnp) {
...
const root = path.resolve(name);
// path.basename() 方法返回 path 的最后一部分
const appName = path.basename(root);
checkAppName(appName);
....
}
// 检测包名方法
function checkAppName(appName) {
const validationResult = validateProjectName(appName); // 这里
// 如上源码所示
//var result = {
// validForNewPackages: errors.length === 0 && warnings.length === 0,
// validForOldPackages: errors.length === 0,
// warnings: warnings,
// errors: errors
//}
if (!validationResult.validForNewPackages) { // 说明有error或者warning抛出
console.error( // 打印错误
....
);
[
...(validationResult.errors || []),
...(validationResult.warnings || []),
].forEach(error => {
console.error(chalk.red(` * ${error}`)); // 打印error和wanring
});
console.error(chalk.red('\nPlease choose a different project name.'));
// 退出进程
process.exit(1);
}
...
}
总结
validate-npm-package-name 源码
- 共100行左右
- 阅读比较简单,就是一些判断。涉及较多的正则判断
- 通过
const validateProjectName = require('validate-npm-package-name')
使用 - 使用场景:用脚手架例如
vue-cli
创建项目时,能够校验创建的包名是否符合规范