源码学习目录

1. 前言

1.1 环境

  1. 操作系统: macOS 11.5.2
  2. 浏览器: Chrome 94.0.4606.81
  3. validate-npm-package-name 3.0.0

    1.2 阅读该文章可以get以下知识点

  4. 了解npm包的名称校验

    2. 开始

    2.1 如何使用

    ```javascript var validate = require(“validate-npm-package-name”)

// 正确的名称 validate(“some-package”) validate(“example.com”) validate(“under_score”) validate(“123numeric”) validate(“@npm/thingy”) validate(“@jane/foo.js”)

// All of the above names are valid, so you’ll get this object back: // 如果校验通过,返回下面的对象 { validForNewPackages: true, validForOldPackages: true }

// 非法名称 validate(“excited!”) validate(“ leading-space:and:weirdchars”)

// That was never a valid package name, so you get this: // 名称有问题,返回下面的报错信息 { validForNewPackages: false, validForOldPackages: false, errors: [ ‘name cannot contain leading or trailing spaces’, ‘name can only contain URL-friendly characters’ ] }

  1. <a name="fYtTV"></a>
  2. ## 2.2 源码
  3. ```javascript
  4. // 作用域正则,例如@vue/component,会被match拆分成vue和component
  5. var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')
  6. // 返回node内置模块列表[
  7. 'assert',
  8. 'buffer',
  9. 'child_process',
  10. 'cluster',
  11. 'console',
  12. 'constants',
  13. 'crypto',
  14. 'dgram',
  15. 'dns',
  16. 'domain',
  17. 'events',
  18. 'fs',
  19. 'http',
  20. 'https',
  21. 'module',
  22. 'net',
  23. 'os',
  24. 'path',
  25. 'punycode',
  26. 'querystring',
  27. 'readline',
  28. 'repl',
  29. 'stream',
  30. 'string_decoder',
  31. 'sys',
  32. 'timers',
  33. 'tls',
  34. 'tty',
  35. 'url',
  36. 'util',
  37. 'vm',
  38. 'zlib'
  39. ]
  40. // 里面就是一个json文件,所以不与安装的node程序有关
  41. var builtins = require('builtins')
  42. // 黑名单,不管node_modules和ico文件
  43. var blacklist = [
  44. 'node_modules',
  45. 'favicon.ico'
  46. ]
  47. var validate = module.exports = function (name) {
  48. var warnings = []
  49. var errors = []
  50. // null的判断
  51. if (name === null) {
  52. errors.push('name cannot be null')
  53. return done(warnings, errors)
  54. }
  55. // undefined判断
  56. if (name === undefined) {
  57. errors.push('name cannot be undefined')
  58. return done(warnings, errors)
  59. }
  60. // 不是字符串
  61. if (typeof name !== 'string') {
  62. errors.push('name must be a string')
  63. return done(warnings, errors)
  64. }
  65. // 上面的三个判断都直接return
  66. // 空字符串
  67. if (!name.length) {
  68. errors.push('name length must be greater than zero')
  69. }
  70. // 以英文句号开头
  71. if (name.match(/^\./)) {
  72. errors.push('name cannot start with a period')
  73. }
  74. // 以下划线开头
  75. if (name.match(/^_/)) {
  76. errors.push('name cannot start with an underscore')
  77. }
  78. // 两边有空格
  79. if (name.trim() !== name) {
  80. errors.push('name cannot contain leading or trailing spaces')
  81. }
  82. // No funny business 不用使用黑名单中的名称
  83. blacklist.forEach(function (blacklistedName) {
  84. if (name.toLowerCase() === blacklistedName) {
  85. errors.push(blacklistedName + ' is a blacklisted name')
  86. }
  87. })
  88. // Generate warnings for stuff that used to be allowed
  89. // core module names like http, events, util, etc
  90. // 不能使用node内置模块命名
  91. builtins.forEach(function (builtin) {
  92. if (name.toLowerCase() === builtin) {
  93. warnings.push(builtin + ' is a core module name')
  94. }
  95. })
  96. // really-long-package-names-------------------------------such--length-----many---wow
  97. // the thisisareallyreallylongpackagenameitshouldpublishdowenowhavealimittothelengthofpackagenames-poch.
  98. // 超过长度
  99. if (name.length > 214) {
  100. warnings.push('name can no longer contain more than 214 characters')
  101. }
  102. // mIxeD CaSe nAMEs 不能有大写
  103. if (name.toLowerCase() !== name) {
  104. warnings.push('name can no longer contain capital letters')
  105. }
  106. // 名称不能有中括号里面的字符
  107. if (/[~'!()*]/.test(name.split('/').slice(-1)[0])) {
  108. warnings.push('name can no longer contain special characters ("~\'!()*")')
  109. }
  110. // ;/?:@&=+$,# 这些用于分隔 URI 组件的标点符号,会被转义,如%26等等,目前大部分scope的包都是以@开头
  111. if (encodeURIComponent(name) !== name) {
  112. // Maybe it's a scoped package name, like @user/package
  113. var nameMatch = name.match(scopedPackagePattern)
  114. if (nameMatch) {
  115. // 前面是作用域user
  116. var user = nameMatch[1]
  117. // 后面是具体的包 package
  118. var pkg = nameMatch[2]
  119. // 相等直接返回
  120. if (encodeURIComponent(user) === user && encodeURIComponent(pkg) === pkg) {
  121. return done(warnings, errors)
  122. }
  123. }
  124. // 不相等抛出错误
  125. errors.push('name can only contain URL-friendly characters')
  126. }
  127. // 最后都到了done函数
  128. return done(warnings, errors)
  129. }
  130. validate.scopedPackagePattern = scopedPackagePattern
  131. // 清洗数据,将不存在的warngings和errors删除,返回需要的数据
  132. var done = function (warnings, errors) {
  133. var result = {
  134. validForNewPackages: errors.length === 0 && warnings.length === 0,
  135. validForOldPackages: errors.length === 0,
  136. warnings: warnings,
  137. errors: errors
  138. }
  139. if (!result.warnings.length) delete result.warnings
  140. if (!result.errors.length) delete result.errors
  141. return result
  142. }

3. 总结

源码很短就100行左右,主要用来校验npm包名是否符合标准,主要校验以下几种情况,是不合法的

  1. 直接返回的情况
    1. undefined
    2. null
    3. 不是字符串类型
  2. 放入警告和错误列表的情况

    1. 空字符串 ‘’
    2. 以下划线开头 _
    3. 以点开头 .
    4. 两边有空格
    5. 黑名单内的, nodule_modules, favicon.ico
    6. node内置模块,如http
    7. 长度超过214
    8. 大写字母
    9. 相关标点符号~’!()*
    10. 含有scope相关的,不能出现超过1个相关的字符如 @user/package ;/?:@&=+$,#

      参考文档

  3. https://github.com/npm/validate-npm-package-name