node全局脚本原理解析
# 脚手架实现原理
# 为什么全局安装@vue/cli后会添加vue的命令?
npm install -g @vue/cli
查看vue实际文件路径
> which vue
/usr/local/bin/vue
2
bin目录下存放的是可执行文件
> cd /usr/local/bin
> ll
lrwxr-xr-x 1 song admin 39B 12 28 21:48 vue -> ../lib/node_modules/@vue/cli/bin/vue.js
2
3
可以看到vue实际是一个软链接,指向:../lib/node_modules/@vue/cli/bin/vue.js
# 绑定管理在哪里指定的呢?
进入到@vue/cli安装目录
> cd /usr/local/lib/node_modules/@vue/cli
> ll
-rw-r--r-- 1 song admin 2.5K 12 28 21:48 package.json
2
3
在package.json中有一个bin的配置
"bin": {
"vue": "bin/vue.js"
},
2
3
这里配置了安装完之后的软链接名称,以及指向的实际文件
# 全局安装@vue/cli时发生了什么?
npm install -g @vue/cli
- 第一步:会把@vue/cli下载到node node_modules中
- 第二步:下载成功后会解析package.json 中的 bin 配置,有这个配置就会创建一个软链接
# vue执行一个js文件,为什么可以执行它?
执行vue命令时,系统会执行which vue在环境变量中找vue的注册并执行文件
# 这两条命令执行是等价的 > vue >/usr/local/bin/vue
1
2
3执行的真实文件是vue对应的软链接:../lib/node_modules/@vue/cli/bin/vue.js
直接执行一个xx.js执行不了的,vue.js又是怎么执行的呢?
js文件需要一个解释器(node)来执行 vue.js源码第一行
#!/usr/bin/env node
1自己创建一个js文件,test.js中第一行加入此代码,通过 ./test.js也能直接执行。
为什么能直接执行?
这句话的意思是,告诉系统在环境变量中去找node命令,来执行此文件
> /usr/bin/env node # 会将node命令执行起来,与直接执行node是一样的效果
1所以./test.js等于 /usr/bin/env node test.js 等于 node test.js
chmod 777 test.js 设置文件为可执行文件
# 自定义一条命令
思路:在环境变量中创建一个软链接,执行 test.js即可 (软链接可以嵌套) 在 /usr/local/bin下执行
> ln -s <路径>/test/index.js <name> #删除软链接 rm <name>
1
# 脚手架的开发流程
创建npm项目
创建脚手架入口文件,最上方添加:
#!/usr/bin/env node
1配置package.json 添加bin属性
编写脚手架代码
将脚手架发布到npm
# 脚手架开发难点
分包: 将复杂系统拆分成若干个模块
命令注册:
vue create vue add vue invoke
1
2
3参数解析
vue command [options] <params>
1options全称:--version、--help options简写: -V、-h
帮助文档
命令行交互
日志打印
命令行文字变色
网络通信:HTTP/WebSocket
文件处理 ....
理解npm link
- npm link your-lib:将当前项目中 node_modules 下指定的库文件链接到 node全局node_modules下的库文件
- npm link: 将当前项目链接到node全局node_modules中作为一个库文件,并解析bin配置创建可执行文件
理解npm unlink
- npm unlink:将当前项目从node全局node_modules中移除
- npm unlink your-li:将当前项目中的库文件依赖移除
# 创建一个脚手架
> mkdir cli-test # 创建一个文件夹
> npm init -y # 初始化
2
cli-test 目录: -- package.json -- bin
- |-- inde.js
index.js 文件:
#!/usr/bin/env node
console.log('Hello cli')
2
package.json文件:
{
...
"main": "index.js",
"bin": {
"cli-test": "bin/index.js"
}
}
2
3
4
5
6
7
将脚手架发布到npm
> npm login # 登录npm
> npm publish # 发布
2
在cli-test目录下,进行全局安装 npm install -g cli-test 就会建立一个软链接,方便进行本地调试, 通过npm link 也可。
# yargs入门
Yargs通过解析参数来帮助您构建脚手架的工具。
# 通过yargs创建一个最简单的脚手架工具
定义文件index.js
#!/usr/bin/env node
const yargs = require('yargs/yargs')
const { hideBin } = require('yargs/helpers')
const arg = hideBin(process.argv)
yargs(arg)
.argv
2
3
4
5
6
7
8
执行
> ./index.js --help
选项:
--help 显示帮助信息 [布尔]
--version 显示版本号 [布尔]
2
3
4
# 严格模式: strict
yargs(arg)
.strict()
.argv
2
3
加入strict,如果有无法识别的参数,将会给出提示
# 用法提示: usage
yargs(arg)
.usage('Usage: test [command] <options>')
.strict()
.argv
2
3
4
使用--help将会打印出使用信息
> ./index.js --help
test [command] <options>
选项:
--help 显示帮助信息 [布尔]
--version 显示版本号
2
3
4
5
6
# 最少输入的command个数: demandCommand
yargs(arg)
.usage('Usage: test [command] <options>')
.demandCommand(1, '最少输入一个参数')
.strict()
.argv
2
3
4
5
# 设置command别名: alias
yargs(arg)
.usage('Usage: test [command] <options>')
.demandCommand(1, '最少输入一个参数')
.strict()
.alias('h', 'help') //-h 和 --help 效果一样
.argv
2
3
4
5
6
# 设置输出内容的宽度: wrap
const cli = yargs(arg)
cli.usage('Usage: test [command] <options>')
.demandCommand(1, '最少输入一个参数')
.strict()
.alias('h', 'help')
.wrap(cli.terminalWidth()) // terminalWidth返回当前窗口的宽度
.argv
2
3
4
5
6
7
# 设置结尾显示的内容: epilogue
cli.usage('Usage: test [command] <options>')
.demandCommand(1, '最少输入一个参数')
.strict()
.alias('h', 'help')
.wrap(cli.terminalWidth())
.epilogue('结尾显示的话')
.argv
2
3
4
5
6
7
# 为全局command添加选项: options
cli.usage('Usage: test [command] <options>')
.alias('h', 'help')
.options({
debug: { // 添加的选项名
type: 'boolean',
describe: 'debug mode',
alias: 'd' // 别名
}
})
.argv
// options('name', {}) 一个一个设置的用法
2
3
4
5
6
7
8
9
10
11
执行效果
> ./index.js -h
Usage: test [command] <options>
选项:
--version 显示版本号 [布尔]
-d, --debug debug mode [布尔]
-h, --help 显示帮助信息 [布尔]
2
3
4
5
6
7
# 将选项分组: group
cli.usage('Usage: test [command] <options>')
.alias('h', 'help')
.options({
debug: {
type: 'boolean',
describe: 'debug mode',
alias: 'd'
}
})
.group(['debug'], 'Dev Options:')
.argv
2
3
4
5
6
7
8
9
10
11
执行效果
> ./index.js -h
Usage: test [command] <options>
Dev Options:
-d, --debug debug mode [布尔]
选项:
--version 显示版本号 [布尔]
-h, --help 显示帮助信息 [布尔]
2
3
4
5
6
7
8
9
# 命令纠错提示:recommendCommands()
会根据当前输入的command去找最相似的进行提示
# 自定义错误信息: fail((err,msg) => {...})
# dedent库
去除每行顶部空格,方便多行字符串的输出
const dedent = require('dedent')
console.log(dedent`
第一行,
第二行
`)
// 将会订购显示输出
/**
第一行,
第二行
*/
2
3
4
5
6
7
8
9
10
# 自定义命令
官方示例
#!/usr/bin/env node
const yargs = require('yargs/yargs')
const { hideBin } = require('yargs/helpers')
yargs(hideBin(process.argv))
.command(
'serve [port]', // serve 脚手架后面输入的名,[port]定义的option
'start the server', // 描述
(yargs) => { //builder,在执行这个command之前做的事情
yargs
.positional('port', {
describe: 'port to bind on',
default: 5000
})
},
(argv) => { // handler,执行comand 的行为
if (argv.verbose) console.info(`start server on :${argv.port}`)
serve(argv.port)
}
)
.option('verbose', {
alias: 'v',
type: 'boolean',
description: 'Run with verbose logging'
})
.argv
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
自定义
cli.usage('Usage: test [command] <options>')
.alias('h', 'help')
.options({
debug: {
type: 'boolean',
describe: 'debug mode',
alias: 'd'
}
})
.group(['debug'], 'Dev Options:')
.command('init [name]', '初始化的命令', (yargs) => {
yargs.option('name', {
type: 'string',
describe: 'init的option',
alias: 'n'
})
}, (argv) => {
console.log(argv)
})
.argv
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
执行效果
> ./index.js init
{ _: [ 'init' ], '$0': 'index.js' }
> ./index.js init -h
ndex.js init [name]
初始化的命令
Dev Options:
-d, --debug debug mode [布尔]
选项:
--version 显示版本号 [布尔]
-n, --name init的option [字符串]
-h, --help 显示帮助信息 [布尔]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
使用对象方式定义
...
.command({
command: 'list',
aliases: ['ls', 'la', 'll'],
describe: 'list 的描述',
builder: (yargs) => {
},
handler: (argv) => {
console.log(argv)
}
})
.argv
2
3
4
5
6
7
8
9
10
11
12
13
# parse
解析命令参数,合并传入的参数,合并完作为一个新的参数注入到脚手架中
#!/usr/bin/env node
const yargs = require('yargs/yargs')
const pkg = require('../package.json')
const argv = process.argv.splice(2)
const context = {
testVersion: pkg.version
}
yargs()
.command({
command: 'list',
aliases: ['ls', 'la', 'll'],
describe: 'list 的描述',
builder: (yargs) => {},
handler: (argv) => {
console.log(argv)
}
})
.parse(argv, context)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
执行效果
> ./index.js list
{ _: [ 'list' ], testVersion: '1.0.5', '$0': 'index.js' }
2
# Lerna初始化过程
# npm本地包引用方法
除了npm link 还可以通过file:
@lerna/global-options:"file:../global-options"
# Lerna创建发布流程
# 项目初始化
> mkdir my-cli-dev # 创建项目文件
> npm init -y # 在项目目录下初始化
> npm i -g lerna # 全局安装lerna
> lerna init # 初始化
2
3
4
# 创建package
> lerna create core # package name 为 @my-cli-dev/core
> lerna create utils # package name 为 @my-cli-dev/utils
2
package.json中name为@my-cli-dev/core这种方式,my-cli-dev则为组织名称,需要在npm上创建一个对应的组织,可以避免名字的重复。若包发布不上去检查下这个组织是否已经建立。 core/package.json 中dependencies 添加@my-cli-dev/utils的依赖。 通过lerna link链接到本地库
# 发布前的准备
1、创建git仓库
> git remote add origin https://xx/cli.git # 添加仓库的链接
# 代码提交到仓库
> git add .
> git commit -m 'init'
> git push origin master --set-upstream
2
3
4
5
2、需要npm login 3、根目录下添加LICENSE.md文件 4、package.json中添加publishConfig设置为公有库
# 发布
> lerna publish
错误问题
lerna ERR! E403 [no_perms] Private mode enable, only admin can publish this module
出现原因:使用的是淘宝源cnpm,登陆到的是cnpm 解决方法:切换到npmjs的网址,代码如下 npm config set registry http://registry.npmjs.org/ 切换过去之后记得npm login