每天一個npm包:validate-npm-package-name
“ 當(dāng)你熱愛生活,你才會變得快樂。”
validate-npm-package-name這個包相信大家都不陌生,存在于大多數(shù)CLI腳手架類工具中被使用。比如在create-react-app[1]中。
function checkAppName(appName) {const validationResult = validateProjectName(appName);// 根據(jù)validForNewPackages字段判斷是否是合法的包名if (!validationResult.validForNewPackages) {console.error(chalk.red(`Cannot create a project named ${chalk.green(`"${appName}"`)} because of npm naming restrictions:\n`));// 打印錯誤、警告[...(validationResult.errors || []),...(validationResult.warnings || []),].forEach(error => {console.error(chalk.red(` * ${error}`));});console.error(chalk.red('\nPlease choose a different project name.'));// 退出進(jìn)程process.exit(1);}...}
那么接下來跟我一起翻一番源碼吧。
由于源碼不多,直接就貼注釋了,通過源碼可以學(xué)習(xí)到一個合格的包名應(yīng)該符合哪些規(guī)則,以及里面有一小段代碼還是挺有意思的。
builtins模塊
'use strict'var semver = require('semver')module.exports = function (version) {// 獲取node版本version = version || process.versionvar 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']if (semver.lt(version, '6.0.0')) coreModules.push('freelist')if (semver.gte(version, '1.0.0')) coreModules.push('v8')if (semver.gte(version, '1.1.0')) coreModules.push('process')if (semver.gte(version, '8.1.0')) coreModules.push('async_hooks')if (semver.gte(version, '8.4.0')) coreModules.push('http2')if (semver.gte(version, '8.5.0')) coreModules.push('perf_hooks')return coreModules}
validate-npm-package-name
'use strict'var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')// 這個包是包括node內(nèi)置 module的列表var builtins = require('builtins')// 保留名(黑名單)var blacklist = ['node_modules','favicon.ico']var validate = module.exports = function (name) {// 警告:用于表示過去package name允許、如今不允許的兼容errorvar warnings = []// 存儲不符號合格的包名的規(guī)則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)}// 校驗包名長度必須大于0if (!name.length) {errors.push('name length must be greater than zero')}// 校驗包名不能以.開頭if (name.match(/^\./)) {errors.push('name cannot start with a period')}// 校驗包名不能以_開頭、這里復(fù)習(xí)了下match用法// '.'.match(/^_/) === nullif (name.match(/^_/)) {errors.push('name cannot start with an underscore')}// 校驗包名不能包含任何的前導(dǎo)、后導(dǎo)空格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')}})// Generate warnings for stuff that used to be allowed// core module names like http, events, util, etc// 校驗包名是否是node 內(nèi)置module名、給予警告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')}// 校驗包名不能包含特殊字段 ~'!()*// name.split('/').slice(-1)[0] => 獲取包名、之所以要這樣處理是因為// name.split('/') 處理 npm package scope場景// slice(-1)[0] 保證永遠(yuǎn)截取包名正確// '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)) // trueif (/[~'!()*]/.test(name.split('/').slice(-1)[0])) {warnings.push('name can no longer contain special characters ("~\'!()*")')}// 包名不能包含non-url-safe字符// 關(guān)于encodeURIComponent不轉(zhuǎn)義哪些字符// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponentif (encodeURIComponent(name) !== name) {// 這里主要處理 scope package name 比如 @babel/corevar nameMatch = name.match(scopedPackagePattern)if (nameMatch) {var user = nameMatch[1] // 比如 bebelvar 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// 返回結(jié)果的util方法var done = function (warnings, errors) {var result = {// 我們一般用該屬性來判斷一個包名是否合法validForNewPackages: errors.length === 0 && warnings.length === 0,// 這個屬性是用于兼容最開始node package name帶來的遺留問題,那個時候有些包名不規(guī)范validForOldPackages: errors.length === 0,warnings: warnings,errors: errors}if (!result.warnings.length) delete result.warningsif (!result.errors.length) delete result.errorsreturn result}
好了,今天就到這結(jié)束了,下期見。
References
[1] create-react-app: https://link.zhihu.com/?target=https%3A//github.com/facebook/create-react-app/blob/2d1829eaf6ff1308da00720fa9984620dd0fb296/packages/create-react-app/createReactApp.js%23L850
結(jié)尾
如果覺得這篇文章還不錯,來個分享、點贊、在看三連吧,讓更多小伙伴看到~
評論
圖片
表情
