源码阅读——validate-npm-package-name


前言

validate-npm-package-name 是一个用于检测npm包是否符合标准的包,被很多脚手架创建工具使用:vue-clicreate-react-app
源码地址:https://github.com/npm/validate-npm-package-name


一、源码阅读工具

  1. 本地编辑器从github上下载源码
  2. 在线网页版编辑器:github.dev在网页端使用 VS Code 编辑 GitHub 项目——github.dev
    这里介绍下,github.dev是github官方提供的在线vscode编辑器,将github访问URL中的com直接替换成dev,即可在网页版vscode中打开仓库代码阅读编辑。
    在这里插入图片描述

二、阅读源码

1. 目录结构

2.package.json

  • 入口文件:index.js
  • builtins 依赖包会列出node所有内置模块
{
  "name": "validate-npm-package-name",
  "version": "3.0.0",
  "description": "Give me a string and I'll tell you if it's a valid npm package name",
  "main": "index.js",   // 入口文件
  "directories": {
    "test": "test"
  },
  "dependencies": {    // 生产依赖包
    "builtins": "^1.0.3"  
  },
  "devDependencies": { // 开发依赖包
    "standard": "^8.6.0",
    "tap": "^10.0.0"
  },
  ...
}

打印 builtins 模块内容:(全是node内置模块)

[
  'assert',         'buffer',   'child_process',
  'cluster',        'console',  'constants',
  'crypto',         'dgram',    'dns',
  'domain',         'events',   'fs',
  'http',           'https',    'module',
  'net',            'os',       'path',
  'process',        'punycode', 'querystring',
  'readline',       'repl',     'stream',
  'string_decoder', 'timers',   'tls',
  'tty',            'url',      'util',
  'v8',             'vm',       'zlib'
]

3.index.js

源码如下:

'use strict'

var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')
var builtins = require('builtins')  // node中所有的内置模块列表,参考上面
var blacklist = [   // 黑名单列表,数组中的不允许设置为包名,会抛出error
  'node_modules',
  'favicon.ico'
]
// 全局导出validate方法
var validate = module.exports = function (name) {

  // 警告:用于表示过去package name允许、如今不允许的兼容error
  var warnings = []
  // 存储不符号合格的包名的规则
  var errors = []
  
  // 校验包名为null,结束并返回valid error结果
  if (name === null) {
    errors.push('name cannot be null')
    return done(warnings, errors)
  }
  // 包名为undefined,结束并返回valid error结果
  if (name === undefined) {
    errors.push('name cannot be undefined')
    return done(warnings, errors)
  }
  // 包名为非字符串,结束并返回valid error结果
  if (typeof name !== 'string') {
    errors.push('name must be a string')
    return done(warnings, errors)
  }
  // 包名字符串长度为0,抛出error
  if (!name.length) {
    errors.push('name length must be greater than zero')
  }
  // 包名以.开头,抛出error
  if (name.match(/^\./)) {
    errors.push('name cannot start with a period')
  }
  // 包名为_开头,抛出error
  if (name.match(/^_/)) {
    errors.push('name cannot start with an underscore')
  }
  // 包名字符串前后有空格,抛出error
  if (name.trim() !== name) {
    errors.push('name cannot contain leading or trailing spaces')
  }

  // 包名是否包含在上面定义的blacklist黑名单列表中,若是则不被允许,抛出error
  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内置模块名一致,若小写一致,则抛出warnings
  builtins.forEach(function (builtin) {
    if (name.toLowerCase() === builtin) {
      warnings.push(builtin + ' is a core module name')
    }
  })

  // 包名长度超过214,抛出warnings
  if (name.length > 214) {
    warnings.push('name can no longer contain more than 214 characters')
  }

  // 包名中是否有大写,若有大写字母抛出warnings(eg:mIxeD CaSe nAMEs)
  if (name.toLowerCase() !== name) {
    warnings.push('name can no longer contain capital letters')
  }
  // 包名是否特殊字符:~'!()*,若包含抛出warning
  // '@babel/core'.split('/').slice(-1)[0] --> 'core'
  if (/[~'!()*]/.test(name.split('/').slice(-1)[0])) {
    warnings.push('name can no longer contain special characters ("~\'!()*")')
  }
  
  // 经encodeURIComponent编码后不相等, 包含non-url-safe字符
  // 关于encodeURIComponent不转义哪些字符: A-Z a-z 0-9 - _ . ! ~ * ' ( )
  if (encodeURIComponent(name) !== name) {
    // 如上所示:var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')
    // 这可能是一个局部包,例如@user/package
    var nameMatch = name.match(scopedPackagePattern)
    if (nameMatch) {
      var user = nameMatch[1]
      var pkg = nameMatch[2]
      // user 和 package部分的包名 encodeURIComponent 结果都相同,都能被转义,则正常返回valid结果
      if (encodeURIComponent(user) === user && encodeURIComponent(pkg) === pkg) {
        return done(warnings, errors)
      }
    }
    // 若不符合scopedPackagePattern规则,则抛出error
    errors.push('name can only contain URL-friendly characters')
  }
  // 最终结束,返回valid结果
  return done(warnings, errors)
}

validate.scopedPackagePattern = scopedPackagePattern
// 输出valid结果:reuslt
var done = function (warnings, errors) {
 // 对象格式
  var result = {
    // 一般用该属性来判断一个包名是否合法: 有errors或者warnings抛出
    validForNewPackages: errors.length === 0 && warnings.length === 0,
    // 兼容最开始node package name带来的遗留问题,那个时候有些包名不规范:有error抛出
    validForOldPackages: errors.length === 0,
    warnings: warnings,
    errors: errors
  }
  // 若warning,errors数组length=0,则从reuslt对象移除对应属性
  if (!result.warnings.length) delete result.warnings
  if (!result.errors.length) delete result.errors
  return result
}

三、使用该包

1. vue-cli中使用

vue create xxx 新建项目时,就用到了该包
源码地址:https://github1s.com/vuejs/vue-cli/blob/HEAD/packages/@vue/cli/lib/create.js#L8-L30

// @vue/cli/lib/create.js
const validateProjectName = require('validate-npm-package-name')

async function create (projectName, options) {

  const cwd = options.cwd || process.cwd()
  const inCurrent = projectName === '.'
  const name = inCurrent ? path.relative('../', cwd) : projectName

  const result = validateProjectName(name)  // 这里校验
  // 如上源码所示
  //var result = {
  //  validForNewPackages: errors.length === 0 && warnings.length === 0,
  //  validForOldPackages: errors.length === 0,
  //  warnings: warnings,
  //  errors: errors
  //}
  if (!result.validForNewPackages) { // 说明有抛出error或者warning
    console.error(chalk.red(`Invalid project name: "${name}"`))
    result.errors && result.errors.forEach(err => { // 打印所有的error
      console.error(chalk.red.dim('Error: ' + err))
    })
    result.warnings && result.warnings.forEach(warn => { // 打印所有的warning
      console.error(chalk.red.dim('Warning: ' + warn))
    })
    exit(1)
  }
  ...
}

2. create-react-app 中使用

源码地址:https://github1s.com/facebook/create-react-app/blob/04482a6c2c6639c19deb330c48e4fa5573a1654e/packages/create-react-app/createReactApp.js

const validateProjectName = require('validate-npm-package-name');

// 创建脚手架方法
function createApp(name, verbose, version, template, useYarn, usePnp) {
  ...
  const root = path.resolve(name);
  // path.basename() 方法返回 path 的最后一部分
  const appName = path.basename(root);

  checkAppName(appName);
  ....
}

// 检测包名方法
function checkAppName(appName) {
  const validationResult = validateProjectName(appName);  // 这里
  // 如上源码所示
  //var result = {
  //  validForNewPackages: errors.length === 0 && warnings.length === 0,
  //  validForOldPackages: errors.length === 0,
  //  warnings: warnings,
  //  errors: errors
  //}
  if (!validationResult.validForNewPackages) {  // 说明有error或者warning抛出
    console.error(   // 打印错误
      ....
    );
    [
      ...(validationResult.errors || []),
      ...(validationResult.warnings || []),
    ].forEach(error => {
      console.error(chalk.red(`  * ${error}`));  // 打印error和wanring
    });
    console.error(chalk.red('\nPlease choose a different project name.'));
    // 退出进程
    process.exit(1);
  }
  ...
}

总结

validate-npm-package-name 源码

  • 共100行左右
  • 阅读比较简单,就是一些判断。涉及较多的正则判断
  • 通过const validateProjectName = require('validate-npm-package-name')使用
  • 使用场景:用脚手架例如vue-cli创建项目时,能够校验创建的包名是否符合规范
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值