脚手架实现原理

为什么全局安装@vue/cli后会添加vue的命令?
npm install -g @vue/cli

查看vue实际文件路径

> which vue
/usr/local/bin/vue

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

可以看到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

在package.json中有一个bin的配置

 "bin": {
    "vue": "bin/vue.js"
  },

这里配置了安装完之后的软链接名称,以及指向的实际文件

全局安装@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
    
  • 执行的真实文件是vue对应的软链接:../lib/node_modules/@vue/cli/bin/vue.js 直接执行一个xx.js执行不了的,vue.js又是怎么执行的呢? js文件需要一个解释器(node)来执行 vue.js源码第一行
    #!/usr/bin/env node
    
    自己创建一个js文件,test.js中第一行加入此代码,通过 ./test.js也能直接执行。 为什么能直接执行? 这句话的意思是,告诉系统在环境变量中去找node命令,来执行此文件
    > /usr/bin/env node # 会将node命令执行起来,与直接执行node是一样的效果
    
    所以./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>
    
    脚手架命令执行流程

脚手架的开发流程

  • 创建npm项目
  • 创建脚手架入口文件,最上方添加:
    #!/usr/bin/env node
    
  • 配置package.json 添加bin属性
  • 编写脚手架代码
  • 将脚手架发布到npm

脚手架开发难点

  • 分包: 将复杂系统拆分成若干个模块
  • 命令注册:
    vue create
    vue add 
    vue invoke
    
  • 参数解析
    vue command [options] <params>
    
    options全称:--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 # 初始化

cli-test 目录: -- package.json -- bin

  • |-- inde.js

index.js 文件:

#!/usr/bin/env node
console.log('Hello cli')

package.json文件:

{
  ...
  "main": "index.js",
  "bin":  {
        "cli-test": "bin/index.js"
    }
}

将脚手架发布到npm

> npm login # 登录npm
> npm publish # 发布

在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

执行

> ./index.js --help
选项:
  --help     显示帮助信息                                                 [布尔]
  --version  显示版本号                                                   [布尔]

严格模式: strict

yargs(arg)
    .strict()
    .argv

加入strict,如果有无法识别的参数,将会给出提示

用法提示: usage

yargs(arg)
    .usage('Usage: test [command] <options>')
    .strict()
    .argv

使用--help将会打印出使用信息

> ./index.js --help
test [command] <options>

选项:
  --help     显示帮助信息                                                 [布尔]
  --version  显示版本号

最少输入的command个数: demandCommand

yargs(arg)
    .usage('Usage: test [command] <options>')
    .demandCommand(1, '最少输入一个参数')
    .strict()
    .argv

设置command别名: alias

yargs(arg)
    .usage('Usage: test [command] <options>')
    .demandCommand(1, '最少输入一个参数')
    .strict()
    .alias('h', 'help') //-h 和  --help 效果一样
    .argv

设置输出内容的宽度: wrap

const cli = yargs(arg)
cli.usage('Usage: test [command] <options>')
    .demandCommand(1, '最少输入一个参数')
    .strict()
    .alias('h', 'help')
    .wrap(cli.terminalWidth()) // terminalWidth返回当前窗口的宽度
    .argv

设置结尾显示的内容: epilogue

cli.usage('Usage: test [command] <options>')
    .demandCommand(1, '最少输入一个参数')
    .strict()
    .alias('h', 'help')
    .wrap(cli.terminalWidth())
    .epilogue('结尾显示的话')
    .argv

为全局command添加选项: options

cli.usage('Usage: test [command] <options>')
    .alias('h', 'help')
    .options({
        debug: { // 添加的选项名
            type: 'boolean',
            describe: 'debug mode',
            alias: 'd'  // 别名
        }
    })
    .argv
// options('name', {}) 一个一个设置的用法

执行效果

> ./index.js -h
Usage: test [command] <options>

选项:
      --version  显示版本号                                               [布尔]
  -d, --debug    debug mode                                              [布尔]
  -h, --help     显示帮助信息                                             [布尔]

将选项分组: group

cli.usage('Usage: test [command] <options>')
    .alias('h', 'help')
    .options({
        debug: {
            type: 'boolean',
            describe: 'debug mode',
            alias: 'd'
        }
    })
    .group(['debug'], 'Dev Options:')
    .argv

执行效果

> ./index.js -h
Usage: test [command] <options>

Dev Options:
  -d, --debug  debug mode                                                [布尔]

选项:
      --version  显示版本号                                               [布尔]
  -h, --help     显示帮助信息                                             [布尔]

命令纠错提示:recommendCommands()

会根据当前输入的command去找最相似的进行提示

自定义错误信息: fail((err,msg) => {...})

dedent库

去除每行顶部空格,方便多行字符串的输出

const dedent = require('dedent')
console.log(dedent`
    第一行,
    第二行
`)
// 将会订购显示输出
/**
第一行,
第二行
*/

自定义命令

官方示例

#!/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

自定义

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

执行效果

> ./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     显示帮助信息                                             [布尔]

使用对象方式定义

    ...
    .command({
        command: 'list',
        aliases: ['ls', 'la', 'll'],
        describe: 'list 的描述',
        builder: (yargs) => {

        },
        handler: (argv) => {
            console.log(argv)
        }
    })
    .argv
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)

执行效果

> ./index.js list
{ _: [ 'list' ], testVersion: '1.0.5', '$0': 'index.js' }

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 # 初始化

创建package

> lerna create core # package name 为 @my-cli-dev/core
> lerna create utils # package name 为 @my-cli-dev/utils

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、需要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

import-lcoal执行流程

Copyright © imooc-lego (2020 - present) all right reserved,powered by GitbookFile Modify: 2021-06-27 08:04:57

results matching ""

    No results matching ""