粥里有勺糖

vuePress-theme-reco 粥里有勺糖    2018 - 2023
粥里有勺糖 粥里有勺糖

Choose mode

  • dark
  • auto
  • light
关于我
备战春秋
  • 心得总结
  • 校招考点汇总
  • 面经汇总
  • 复习自查
技术笔记
  • 技术教程
  • 模板工程
  • 源码学习
  • 技术概念
  • 个人作品
  • 学习笔记
计算机基础
  • 算法与数据结构
  • 操作系统
  • 计算机网络
  • 设计模式
  • 剑指offer
大前端
  • javascript
  • vue
  • html
  • css
  • 🌏浏览器专题
  • Web性能优化
  • regexp
  • node
面试
  • 问解
  • javascript
  • css
  • 手撕代码
  • 性能优化
  • 综合问题
  • 面经汇总
  • 小程序
手撕代码
  • 数据结构与算法
  • javascript
  • css
个人站点
  • GitHub (opens new window)
  • 博客园 (opens new window)
  • 掘金 (opens new window)
线上作品
  • 轻取(文件收集) (opens new window)
  • 个人图床 (opens new window)
  • 考勤小程序 (opens new window)
  • 时光恋人 (opens new window)
  • 在线简历生成 (opens new window)
留言板
Github (opens new window)
author-avatar

粥里有勺糖

285

文章

40

标签

关于我
备战春秋
  • 心得总结
  • 校招考点汇总
  • 面经汇总
  • 复习自查
技术笔记
  • 技术教程
  • 模板工程
  • 源码学习
  • 技术概念
  • 个人作品
  • 学习笔记
计算机基础
  • 算法与数据结构
  • 操作系统
  • 计算机网络
  • 设计模式
  • 剑指offer
大前端
  • javascript
  • vue
  • html
  • css
  • 🌏浏览器专题
  • Web性能优化
  • regexp
  • node
面试
  • 问解
  • javascript
  • css
  • 手撕代码
  • 性能优化
  • 综合问题
  • 面经汇总
  • 小程序
手撕代码
  • 数据结构与算法
  • javascript
  • css
个人站点
  • GitHub (opens new window)
  • 博客园 (opens new window)
  • 掘金 (opens new window)
线上作品
  • 轻取(文件收集) (opens new window)
  • 个人图床 (opens new window)
  • 考勤小程序 (opens new window)
  • 时光恋人 (opens new window)
  • 在线简历生成 (opens new window)
留言板
Github (opens new window)
  • wheel

    • 个人作品
    • 时间管理CLI工具
    • 组装个支持记笔记的CodePen
    • ESCheck工具原理解析及增强实现
    • 一款检测代码中TODO的eslint插件
    • 实现一个Web UI检测(视觉走查)工具ing
    • 从0-1实现文件下载CLI工具
    • 内联JS处理(ES语法降级&内容压缩)
    • Node CLI工具原理解析
    • 我打造的在线简历生成应用
    • 助你轻松编写与分享snippet的VsCode插件
    • SourceMap解析CLI工具实现
    • 一个通过NPM包共(分)享代码块的解决方案
    • 实践:给女朋友个性化定制应用-体重记录(一)
    • 实践:给女朋友个性化定制应用-体重记录(二)
    • 实践:给女朋友个性化定制应用-体重记录(三)

NodeCLI工具原理解析

vuePress-theme-reco 粥里有勺糖    2018 - 2023

NodeCLI工具原理解析

粥里有勺糖 2022-10-17 技术笔记个人作品

# Node CLI工具原理解析

本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

本文将主要介绍CLI相关周边知识,通过本文读者可以了解到CLI的基本工作原理,注册全局指令的几种方式、Node CLI的基本工作原理。

# 前言

CLI(Command-Line Interface) 命令行界面

搞开发的同学,或多或少的都会接触到许多的命令行工具。

有生产力工具,也有有意思的小玩意、自动化任务处理等等。

命令行工具的安装方式就很多了。

win上大部分是通过软件安装包安装,安装同时会通过环境变量配置相关指令。

linux和mac上就比较丰富了,前者常用yum和api-get、mac 上就brew。

也有使用wget和curl拉取相关工具的shell脚本执行安装。

说了这么多工具,都不是本文要讲的工具,前端搬砖当然首选node,然后基于npm做包的分发。

PS:文中的示例都以mac为主

# 可执行shell

unix系上大部分可执行文件都是基于shell的脚本。

比如随手写个hello world

文件名hello,内容如下

echo "Hello world"
1

图片

此时我们直接执行是会提醒没有执行权限,我们为当前用户加1个可执行权限

chmod u+x hello
1

然后再当前目录执行,就看到输出结果了

./hello
1

图片

# 注册全局指令

为了使“指令”在全局任意位置都能被使用,就需要做相关操作了。

# 环境变量

相信大多数首次接触这个词的朋友都在win上深有体会。装JDK、MySQL时都避免不了有配置的操作。

如果想在其它目录直接执行hello就生效呢?那这就离不开环境变量配置了

咱们先看终端用的shell工具是什么。

echo $0
1

我这里使用的是zsh,其它的常见的还有bash

图片

相应的配置文件分别是.zshrc和.bashrc

图片

# alias指令

使用 alias指令设置别名

指令格式

alias <别名>=<指令或可执行文件路径>
1

添加内容如下

alias hello=/Users/sugar/Documents/diy-cli/hello
1

立即生效配置

source ~/.zshrc
1

# export指令

使用export命令添加添加相关目录

指令格式

export PATH=$PATH:<路径 1>:<路径 2>:<路径 N>
1

添加内容如下

export PATH=$PATH:/Users/sugar/Documents/diy-cli
1

以上2种方案都能达到目标的效果

图片

如果每个工具都单独配一条规则。那会导致相关配置文件非常的庞大,也不方便维护。

实际上我们在用npm i -g安装的全局包的时候,并没有手动配置。那么这个是如何做到的呢。这个就离不开下面将要说到的符号链接了

# 符号链接

软链接类似于快捷方式,它可以指向任意文件系统中的一个文件或目录;硬链接也可以看作是文件或目录的快捷方式,但源文件删除了也不影响硬链接。

先通过which npm看一下npm所在位置

打印一下$PATH的值,可以看到npm指令对应文件所在目录就在其中

图片

展开目录内容可以看到文件类型都是l(软连接)

图片

因此咱们可以小结出来 通过向已添加到$PATH变量中的目录,直接创建短链可以实现指令的自动注册全局

下面实践演示一下

# ln指令

指令格式

# 硬链接
ln source target
# 软连接
ln -s source target
1
2
3
4

接着上面之前的例子,再使用export完成对目录的添加后。咱们再随便建立个文件hello2.sh进行操作

内容如下

echo "Hello world2"
1

创建一个软链

ln -s <source>/hello.sh <target>/hello2
1

操作结果如下

图片

前面代码都是简单的写的shell脚本

前端当然是羧js,咱们把代码改成js。

hello.js

console.log('hello js')
1

按照前面的步骤,完成可执行权限添加和软链的创建。

图片

结果可以预测是会报错的,默认会被当做shell脚本进行执行。

那么如何指定为使用node去执行这个文件?

这就是我们下文要说到的hashbang

# Hashbang

Hashbang(也称为Shebang)是一个由井号和叹号构成的字符序列 #!,通常出现在文件开头,例如 #!/usr/bin/env bash

用于指定脚本的运行环境

于是,我们给前面的hello.js头部加上#!/usr/bin/env node 再次运行就成了

图片

至此基本清楚了,如何将1个js脚本便捷的注册为1个全局可执行指令

# Node CLI

node官配包管理工具npm,通常每个项目中有一个package.json文件,用于描述项目的一些信息或者包含项目相关的配置内容

# 指令注册

其中bin属性用于设置指令名称和执行脚本所在位置

{
    "name":"pkgName",
    "bin": {
        "command": "exec/filepath.js"
    }
}
1
2
3
4
5
6

使用npm install安装依赖,会根据bin中的描述,创建1个command到exec/filepath.js的软链

软链所在目录区别于是否是global安装

这个目录可以通过npm bin指令查看

图片

全局路径和前面使用 which npm获取的一致,当前项目的路径即在node_modules/.bin中

如果是本地开发CLI时,可以使用npm link指令根据bin描述信息,自动创建软链到npm bin所示的目录中,通过-g参数区别是否是全局

# 项目工作目录下执行
npm link
# or
npm link -g
1
2
3
4

# 命令行参数

前面主要都在围绕命令展开介绍。要实现工具的丰富功能离不开参数的组合,本小节就介绍下Node里如何处理Command与Options。

我们可以通过process.argv方法获取到运行时的 命令行入参

console.log(process.argv);
1

图片

各位置参数释义

  • 0:Node可执行文件所在路径
  • 1:执行的js脚本路径
  • >1:用户运行时传入的参数

通过这些参数,就能区分出用户要执行的行为

当然在实际开发中大部分场景下,都会使用第三方库去解析命令行参数,来降低代码的复杂度,提高可读性。

下面是一个使用commander的例子

#!/usr/bin/env node

const { Command } = require('commander')
const pkg = require('./package.json')

const program = new Command()
program.version(pkg.version)

program
    .command('hello [paths...]')
    .description('hello world demo')
    .alias('h')
    .option('-p, --pkg <path>', 'set package.json path')
    .action((paths, options) => {
        console.log('😄😄😄');
        console.log(paths);
        console.log(options);
    })

program.parse(process.argv)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

图片

可以看到使用第三方库辅助处理参数,已经非常完善了

除了老牌的commander (opens new window)之外还有其它的相同作用的库,这里就不展开介绍了。

# 彩色打印

这个大家都不陌生了,大部分CLI打印结果都是五颜六色的

比如下面的例子

echo 'hello  world'
1

图片

相关知识点是ANSI Escape code,这里就不展开说明了。

实际开发中,也很少直接写这种原始的数值。通常会使用chalk (opens new window)这个库辅助,比如上面这个颜色对应代码如下。

const Chalk = require('chalk');

console.log(Chalk.cyan('hello world'));
1
2
3

渐变色打印就常用gradient-string (opens new window)这个库

const gradient = require('gradient-string');

console.log(gradient('cyan', 'pink')('Hello world!'));
1
2
3

图片

简单两行代码效果就出来了

# 终端交互

在使用 例如Vue CLI 此类工具进行项目初始化的时候,会有输入,单选,多选等交互操作。

相关原理涉及内容太“抽象”,篇幅较大,后续通俗精简了再做分享

常用的第三方库就是inquirer (opens new window)这个库

下面是简单checkbox示例

const inquirer = require('inquirer');

inquirer
    .prompt([
        {
            type: 'checkbox',
            message: '水果选择',
            name: 'fruits',
            choices: [
                {
                    name: '🍌',
                },
                {
                    name: '🍉',
                },
                {
                    name: '🍇',
                },
            ]
        },
    ])
    .then((answers) => {
        console.log(answers);
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

图片

# 最后

本文没有阐述非常深奥的知识点,只涉及日常的一些基操,有助于读者了解Node CLI 背后的工作原理。

如内容有不妥之处,可以评论区交流;有感兴趣希望深入了解的知识点也可评论区@。

完整示例代码移步=>Github (opens new window)

Edit this page (opens new window)
Last Updated: 2022/10/17 23:45:00