• 源码地址:https://github.com/npm/validate-npm-package-name
  • 参考文章:https://zhuanlan.zhihu.com/p/362147023
  • 正则可看这本迷你书学习:https://juejin.cn/post/6844903501034684430

validate-npm-package-name 这个包主要介绍了合格的包名应该符合哪些规则,才可以通过正确的包名去创建项目,如我们常用的 vue-cli 创建项目等等都是使用了这些校验规则。

# 通过源码地址、拉取源码

  • npm install
  • npm run test

Philadelphia's Magic Gardens. This place was so cool!

将源码包跑起来,可以看到在 test 目录 index 入口文件下的测试用例均能够 passed

# 源码分析

根目录下的 index 入口文件,导出一个 validate 函数(10 ~ 91 行)

/**
* name 传入的包名
*/
var validate = module.exports = function (name) {
  //todos... 
}
var validate = module.exports = function (name) {
  //默认输出的错误信息
  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)
  }
  //必须存在长度
  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等等)
  builtins.forEach(function (builtin) { 
    //builtins是一个node的内置模块包
    if (name.toLowerCase() === builtin) {
      warnings.push(builtin + ' is a core module name')
    }
  })
  //包名不能太长
  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
    //类似包名 @package/user
    var nameMatch = name.match(scopedPackagePattern)
    if (nameMatch) {
      var user = nameMatch[1]//package
      var pkg = nameMatch[2]//user
      //对user和pkg分别进行校验
      if (encodeURIComponent(user) === user && encodeURIComponent(pkg) === pkg) {
        return done(warnings, errors)
      }
    }
    errors.push('name can only contain URL-friendly characters')
  }
  return done(warnings, errors)
}
//校验后均会执行的方法
/**
* 
* @param {*} warnings 警告的数组
* @param {*} errors 错误的数组
* @returns 
*/
var done = function (warnings, errors) {
  var result = {
    //如果没有警告和错误 为true
    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
}

# 源码阅读收获

通过这期的源码阅读,细致了解到了 npm 包命名的规范以及源码实现思路,进一步理解了包名的校验规则。