前言
本文参加了由公众号@若川视野 发起的每周源码共读活动。从简单到进阶学习源码中的巧妙之处,旨在于将学习的东西应用到实际的开发中,同时借鉴源码中的思想,规范自我开发,以及锻炼自己的开发思维。也欢迎大家加入大佬的源码共读活动。一起卷起来。
疑问
我们尝尝通过vue create 项目名称 去创建项目,有时候会因为项目名称的不规范导致 创建不成功,比如 项目名称中文 项目名称大小写问题等,比如下图:
那这是为什么呢,项目名都有哪些规则呢,带着问题我们进入接下来的学习
正文
笔者用的是vue的技术栈 因此我们从vuecli中去查找问题。
vuecli是如何进行包名的验证
我们进入vuecli 的源码,因为我们是通过vue create 的方式去创建的。
- 查找vue create的入口 github.com/vuejs/vue-c…
//pageage.json
{
....
"bin":{
"vue":"bin/vue.js" //可以看出我们的入口在这里
}
}
bin/vue.js文件我们可以查找到create 指令,在create指令中加载了一个验证包名的方法
program
.command('create <app-name>')
.description('create a new project powered by vue-cli-service')
.option('-p, --preset <presetName>', 'Skip prompts and use saved or remote preset')
.option('-d, --default', 'Skip prompts and use default preset')
.option('-i, --inlinePreset <json>', 'Skip prompts and use inline JSON string as preset')
.option('-m, --packageManager <command>', 'Use specified npm client when installing dependencies')
.option('-r, --registry <url>', 'Use specified npm registry when installing dependencies (only for npm)')
.option('-g, --git [message]', 'Force git initialization with initial commit message')
.option('-n, --no-git', 'Skip git initialization')
.option('-f, --force', 'Overwrite target directory if it exists')
.option('--merge', 'Merge target directory if it exists')
.option('-c, --clone', 'Use git clone when fetching remote preset')
.option('-x, --proxy <proxyUrl>', 'Use specified proxy when creating project')
.option('-b, --bare', 'Scaffold project without beginner instructions')
.option('--skipGetStarted', 'Skip displaying "Get started" instructions')
.action((name, options) => {
if (minimist(process.argv.slice(3))._.length > 1) {
console.log(chalk.yellow('\n Info: You provided more than one argument. The first one will be used as the app\'s name, the rest are ignored.'))
}
// --git makes commander to default git to true
if (process.argv.includes('-g') || process.argv.includes('--git')) {
options.forceGit = true
}
//这里引入了create 方法,也就是包名的验证方法
require('../lib/create')(name, options)
})
create方法它是怎样的验证包名,带着疑问继续往下
在create中我们可以看出vuecli中包名验证用到了
validate-npm-package-name
的插件进行验证的。那我们继续 validate-npm-package-name
它又是如何验证的呢?
validate-npm-package-name源码和原理
接着上述的问题我们进入validate-npm-package-name源码查看它是如何进行包名验证的.
'use strict'
//用于匹配@babel/core 格式的包
var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')
//引入node中内置的关键字
var builtins = require('builtins')
//黑名单
var blacklist = [
'node_modules',
'favicon.ico',
]
function validate (name) {
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)
}
//不能以.或者_开头
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
//包名不能是黑名单里面的名称也就是node_modules或者favicon.ico
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 中的关键字
builtins({ version: '*' }).forEach(function (builtin) {
if (name.toLowerCase() === builtin) {
warnings.push(builtin + ' is a core module name')
}
})
//报的长度不能大于214
if (name.length > 214) {
warnings.push('name can no longer contain more than 214 characters')
}
// 包名必须小写
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 ("~\'!()*")')
}
//包名不能包含任何非url 安全字符
//也就是说我们的包名只能由A-Z a-z 0-9 - 等组成并且 不能有空格存在
if (encodeURIComponent(name) !== name) {
// Maybe it's a scoped package name, like @user/package
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)
}
var done = function (warnings, errors) {
var result = {
///判断包名是否合法的参数
validForNewPackages: errors.length === 0 && warnings.length === 0,
validForOldPackages: errors.length === 0,
warnings: warnings,
errors: errors,
}
if (!result.warnings.length) {
delete result.warnings
}
if (!result.errors.length) {
delete result.errors
}
return result
}
module.exports = validate
builtins 模块的源码
'use strict'
var semver = require('semver')
module.exports = function (version) {
// 获取node版本
version = version || process.version
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'
]
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-pageage-name
源码的学习
总结
我们带着疑问从vuecli为入口 通过create指令查找到了 我们验证包的插件validate-npm-pageage-name
这个插件里面我们了解到了 包名是一个不能为空,且不能是node的保留字 以及一个安全url,长度不超过214的字符串。验证结果则通过validate-npm-pageage-name
返回,通过返回结果中的 validForNewPackages属性去判断 包名验证是否通过