validate-npm-package-name源码阅读

183 阅读1分钟

1. 是什么?

是用来校验依赖包名是否合格的工具

2. 使用场景?

大多数CLI脚手架类工具中,比如create-react-app、vue-cli等。

3. 校验规则?

 a.包名不能为null、空,必须是字符串格式
 b.包名长度得大于0,并且不能以.和_开头
 c.包名前后不能有空格
 d.包名不能是保留字
 e.包名不能是node的内置模块, 类似http, events, util, etc等
 f.包名长度不能大于214
 g.包名不能包含大写字母 类似:mIxeD CaSe nAMEs
 h.包名不能包含任意一个特殊字符~'!()*
 i.包名不得包含任何非 url 安全字符,例如@user/package

4. 如何使用?

 const validate = require("validate-npm-package-name")

 validate("some-package")
 validate("example.com")
 validate("under_score")
 validate("123numeric")
 validate("@npm/thingy")
 validate("@jane/foo.js")
 //成功返回
 {
   validForNewPackages: true,
   validForOldPackages: true
 }
 //失败返回
  {
   validForNewPackages: false,
   validForOldPackages: false,
   errors: [
     'name cannot contain leading or trailing spaces',
     'name can only contain URL-friendly characters'
   ]
 } 

5. 源码分析

'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 = []
    //  包名不能为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)
    }
    // 包名长度得大于0
    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, etc等
   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"];
    */

    builtins.forEach(function (builtin) {
      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) {
      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
      //包名不得包含任何非 url 安全字符,例如@user/package
      // @user/package'.match(/^(?:@([^/]+?)[/])?([^/]+?)$/) => ['@user/package', 'user', 'package', index: 0, input: '@user/package', groups: undefined]
      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')
    }

    return done(warnings, errors)
  }
  // 暴露出去规则,方便使用者外部使用
  validate.scopedPackagePattern = scopedPackagePattern
  // 返回结果的util方法
  var done = function (warnings, errors) {
    var result = {
      // 用来做判断包名是否符合规格
      validForNewPackages: errors.length === 0 && warnings.length === 0,
      // 用于表示过去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
  }