【源码阅读 | 02】validate-npm-package-name 校验包名是否规范

1. 场景

  在使用如脚手架 创建项目 的时候,为了 规范包名,会对包名进行校验,此时就用到了题目中的 validate-npm-package-name 包。

2. 使用

打开 cmd,创建 vue 项目试一下(以 @vue/cli 4.5.9 为例)

1)使用 保留字 作为项目名
img

2)使用 _ 下划线作为开头
img

3)使用 大写字母 作为项目名
img

细心的人可能会发现,报错信息中,有的是 Error,有的是 Warning,区别在于:

  • Warning :用于表示过去 package name 允许,而如今不允许的
  • Error :不符合规范的包名

3. 实现

  源码较简单,主要过程是对各种情况进行 校验,并分别将错误信息添加进 ErrorsWarnings 数组,最终将数组返回进行报错提示。

  于我个人而言,该源码也帮助我复习了几个知识点,见 第四点。

'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 = []

  // 校验格式
  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')
  }

  // 校验包名不能以_开头
  // '.'.match(/^_/) === null
  if (name.match(/^_/)) {
    errors.push('name cannot start with an underscore')
  }

  // 校验包名不能包含任何的前导、后导空格
  if (name.trim() !== name) {
    errors.push('name cannot contain leading or trailing spaces')
  }

  // 校验包名不能为保留字
  blacklist.forEach(function (blacklistedName) {
    if (name.toLowerCase() === blacklistedName) {
      errors.push(blacklistedName + ' is a blacklisted name')
    }
  })
  
  // 校验包名是否是node 内置module名、给予警告
  builtins.forEach(function (builtin) {
    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')
  }

  // 校验包名不能包含特殊字段 ~'!()*
  // name.split('/').slice(-1)[0] => 获取包名、之所以要这样处理是因为
  // name.split('/') 处理 npm package scope场景
  // slice(-1)[0] 保证永远截取包名正确
  // 'koa'.split('/').slice(-1)[0] // 'koa'
  // '@babel/core'.split('/').slice(-1)[0] // 'core'
  // /[~'!()*]/.test('@babel/core'.split('/').slice(-1)) // false
  // /[~'!()*]/.test('@babel/co*re'.split('/').slice(-1)) // true
  if (/[~'!()*]/.test(name.split('/').slice(-1)[0])) {
    warnings.push('name can no longer contain special characters ("~\'!()*")')
  }

  // 包名不能包含non-url-safe字符
  // 关于encodeURIComponent不转义哪些字符
  // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
  if (encodeURIComponent(name) !== name) {
    // 这里主要处理 scope package name 比如 @babel/core
    var nameMatch = name.match(scopedPackagePattern)
    if (nameMatch) {
      var user = nameMatch[1] // 比如 bebel
      var pkg = nameMatch[2] // 比如得到 core
      // 如果没有异常 直接返回
      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,
    // 这个属性是用于兼容最开始node 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
}

4. 补充

1)slice(-1)

  slice(-1) 表示从尾部开始,如下

let arr = [1, 2, 3, 4]

arr.slice(-1)  // [4]
arr.slice(-2)  // [3, 4]

2)match(reg)

  match(/^_/) 表示匹配下划线开头

  • 匹配到:返回 _
  • 匹配不到:返回 null

3)delete result.warnings

  联想到 【源码阅读 | 01】Promisify 函数Promisify 提到的 Reflect,可以用 Reflect 来对该语句替换为

// use Object method
delete result.warnings

// use Reflect
Reflect.deleteProperty(result, "warnings")
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值