1. 前言
有幸在若川大佬的公众号《若川视野》中看到源码共读的活动,于是果断加入了这个活动。希望通过参加这个活动,一方面让自己坚持去看源码,另一方面让自己养成写文章的习惯。本篇文章是参加第七期活动后写的。
2. 整体分析
2.1 功能
用户输入一个字符串,然后这个库就会判断该名称是否为一个有效的 npm 包名
2.2 输入和输出示例
如下所示,当用户引用该库,然后输入有效的包名,最后会得出该包名是否符合标准的结果
var validate = require("validate-npm-package-name")
validate("some-package")
{
validForNewPackages: true,
validForOldPackages: true
}
2.3 运行官方测试用例
按照官方步骤,下载源码后,安装依赖,运行测试命令,即可看到测试结果。
npm install
npm test
通过测试结果可以看到,官方的test/index.js中含有22条测试用例且全部验证通过
3. 源码分析
3.1 整体结构
主要是有 validate 和 done 这两个函数。前一个函数是用来判断包名的主函数,还附带了相关的正则表达式 scopedPackagePattern,而 done 函数是辅助函数,可以通过传入的 warnings 和 errors 来决定最后返回的 result
var validate = module.exports = function (name) {
...
return done(warnings, errors)
}
validate.scopedPackagePattern = scopedPackagePattern
var done = function (warnings, errors) {
...
}
3.2 包名的长度需要大于0
包名不应该是 null 或者是 undefined,必须是一个长度不为0的字符串,
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);
}
if (!name.length) {
errors.push("name length must be greater than zero");
}
3.3 包名不应该是.或者_开始
if (name.match(/^\./)) {
errors.push("name cannot start with a period");
}
if (name.match(/^_/)) {
errors.push("name cannot start with an underscore");
}
3.4 包名前后不能含有空格
if (name.trim() !== name) {
errors.push("name cannot contain leading or trailing spaces");
}
3.5 包名不能和 node.js 的核心模块名称相同,也不能是黑名单里面的名称
blacklist 包含两个名称,而引入的 builtins 依赖包则是包含所有 node.js 内嵌模块的清单
var builtins = require('builtins')
var blacklist = [
'node_modules',
'favicon.ico'
]
blacklist.forEach(function (blacklistedName) {
if (name.toLowerCase() === blacklistedName) {
errors.push(blacklistedName + " is a blacklisted name");
}
});
builtins.forEach(function (builtin) {
if (name.toLowerCase() === builtin) {
warnings.push(builtin + " is a core module name");
}
});
3.6 包名的长度不能超过214
if (name.length > 214) {
warnings.push("name can no longer contain more than 214 characters");
}
3.7 包名必须是小写字母
if (name.toLowerCase() !== name) {
warnings.push("name can no longer contain capital letters");
}
3.8 包名不能含有字符~)('!*,不能包含任何非 URL 安全的字符
这个两个校验放在了一起进行分析,前一个校验说明在官方文档上的介绍是不严谨的,像@!~npm/thing这种特殊的包名包含了~是可以通过校验的,因为它符合最后一个/后面不含~)('!*的规则,而且符合不包含非 URL 安全字符这个规则。
正则表达式 scopedPackagePattern 可以参考网站 进行分析,不过觉得自己还是没有能够完整理解,所以准备看书《正则表达式必知必会》
if (/[~'!()*]/.test(name.split("/").slice(-1)[0])) {
warnings.push('name can no longer contain special characters ("~\'!()*")');
}
var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')
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");
}
4. 总结
- 正则表达式在做校验的时候方便且常用,所以学好这个技巧很重要
- 单单一个包名校验就有9条校验规则,所以写代码和文档的时候需要严谨
- 分析源代码的时候需要提高效率,现在看源码加上写文章超过了3个小时