likes
comments
collection
share

脚手架开发的一些细节(一)

作者站长头像
站长
· 阅读数 12

脚手架的一些名词

vue create vue-demo --force -r https://registry.npm.taobao.org
  • 主命令 vue
  • 子命令 create
  • 命令参数 vue-demo
  • 配置选项 force / r
  • 配置选项的参数(如果为给定默认为true)https://registry.npm.taobao.org

脚手架开发的一些细节(一)

分析vue-cli脚手架执行原理

输入vue create vue-demo

  • 解析vue命令
  • 在环境变量中查找vue命令。which vue
  • 根据查找到的vue路径连接到实际的vue.js文件。
  • node执行vue.js文件
  • vue.js中解析command / options
  • vue.js执行command

分析vue-cli安装三连问

  1. 为啥全局安装@vue/cli后会添加的命令为vue?

因为@vue/cli项目的package.json中的bin属性下指定了脚本命令名称。安装@vue/cli后,会分析该文件,并在node/bin目录下创建对应命令的软链接。

  1. 全局安装@vue/cli时发生了啥?

将项目下载到node/lib/node_modules中,如果有脚本命令将在node/bin目录下创建对应的软链接。

  1. 执行 vue 命令时发生了啥?为啥 vue 指向一个 js 文件,我们却可以直接通过vue命令执行它?

在环境变量中查找是否有该命令,没有则报错。在js文件开头加上#!/usr/bin/env node即可直接通过命令执行js文件。他主要是告诉终端,去/usr/bin/env(环境变量目录)中查找node命令去执行js文件。

npm包下载细节

如果在当前本地包目录下全局下载该包,那么下载的包在lib/node_modules下会软连接到本地包项目。而不是下载的npm仓库的包。就类似于执行了npm link

脚手架本地调试流程

将本地开发库连接到全局node/lib/node_modules下。

npm link

在当前项目中引入本地开发库。

npm link your-lib

如果直接link本地包到该项目,将会报错,所以需要先全局link。

脚手架开发的一些细节(一) 将本地开发库从全局中移除

npm unlink -g your-lib

开发流程需要注意的是,我们在本地测试完毕后,需要将本地link的全局包删除,这样才能下载发布后的包进行测试。

项目管理工具lerna

lerna 是一个优化基于git + npm 的多package项目的管理工具。

优势

  • 大幅减少重复操作。
  • 提升操作的标准化。

lerna是架构优化的产物,他揭示了一个架构真理:项目复杂度提升后,就需要对项目进行架构优化。架构优化的主要目标往往就是以效能为核心。

lerna创建脚手架项目

lerna init

创建子包,注意为了防止包名被注册,我们可以先在npm中创建一个组织。包名命名成@mh-cli/core

lerna create core

为所有pacakge安装依赖,并在对应的package中的package.json中写入依赖的版本号。

lerna add <libname>

删除包中的node_modules。但是packages.json中的依赖版本没有删除,我们需要手动删除。

lerna clean

脚手架开发的一些细节(一) 为packages中的包安装相互依赖,而不需要进入对应的包目录进行npm link <libname>但是需要先在dependencies中添加本地包依赖。

lerna link

如果需要重新link,需要先通过lerna clean删除node_modules,然后在执行lerna link

lerna clean
lerna link

执行每个包中脚本命令。

lerna exec <任何命令>
lerna run <packages中定义的命令>

changed与diff

lerna changed // 查看哪些包发生变更,然后需要发布
lerna diff // 查看两次commit的差异

修改版本号。当项目提交仓库后,发布前,需要更改version

lerna version

发布出现402错误,我们需要在package.json中加入。因为加了@前缀的包默认是private的,要在package.json中改变一下publishConfig。 脚手架开发的一些细节(一)


publishConfig: {
    access: "public"
}

如果我们未发布成功,我们需要手动改动版本号

脚手架开发的一些细节(一)

yargs,解析命令行参数工具

#!/usr/bin/env node

const yargs = require("yargs")
// 解析参数
const {hideBin} = require("yargs/helpers")
/**
 *   ./src/index.js  zh llm
 *   [ 'zh', 'llm' ]
 */
const argv = hideBin(process.argv) // 解析参数,只保留控制台输入的参数
const cli = yargs(arg)

// 处理命令提示

cli
// 脚手架通用模板格式。$0表示脚手架名称
.usage("Usage: $0 <command> [options]")
.argv

脚手架开发的一些细节(一) 如果不知道严格模式,输入错误的命令将没有任何错误反馈。

// 严格模式。让输入不正确的命令或者参数出现错误提示。而不是没有任何反馈。
.strict()

脚手架开发的一些细节(一) 约束最少输入的命令

// 约束最少输入的命令
.demandCommand(1, "A command is required. Pass --help to see all available commands and options.")

脚手架开发的一些细节(一) 配置option别名

// 别名
.alias("h", "help")
.alias("v", "version")

脚手架开发的一些细节(一)

脚手架开发的一些细节(一) 修改终端信息展示宽度,让其有更好看的样式。

// 修改终端信息展示宽度
.wrap(cli.terminalWidth())

脚手架开发的一些细节(一)

help中结尾内容的定义

.epilogue(`
When a command fails, all logs are written to lerna-debug.log in the current working directory.

For more information, check out the docs at https://lerna.js.org/docs/introduction
`)

增加全局的option选项,对所有命令都生效。

// 配置全局option(配置多个)
.options({
  debug: {
    type: "boolean",
    describe: "dev debug",
    default: true,
    alias: "d",
    // 表示隐藏命令,用于内部开发使用。他不会显示在options列表中,但是可以被使用。
    hidden: true
  }
})
// 配置全局option(配置一个)
.option("name", {
  type: "string",
  describe: "project name",
  alias: "n"
})

进行options分组。

.group(["debug", "name"], "dev options")
.group(['version', 'help'], "global options")

脚手架开发的一些细节(一) 定义子命令。两种方式

.command({
  command: "init [name]",
  aliases: ["i"],
  describe: "List local packages",
  // 执行这个命令之前回调
  builder(yargs) {
    yargs.option("name", {
      type: "string",
      describe: "a project name",
      // 脚手架中的别名不能重复
      alias: "n1"
    })
  },
  handler(argv) {
    console.log("argv", argv)
  },
})
// 定义命令 方式二
.command('serve [port]', 'start the server', (yargs) => {
  return yargs
    .positional('port', {
      describe: 'port to bind on',
      default: 5000
    })
}, (argv) => {
  if (argv.verbose) console.info(`start server on :${argv.port}`)
  serve(argv.port)
})

给出错误提示,匹配相近的命令提示用户。

// 输入错误命令,提示用户
.recommendCommands()
// 定制自己的错误提示信息
.fail((msg, err) => {
  console.log(msg)
})

脚手架开发的一些细节(一)

脚手架开发的一些细节(一) 向参数列表中注入参数。我们不再通过hideBin来解析参数。

const { version } = require("../package.json")
const context = {
  yargsStudy: version
}
const cli = yargs()

cli
.parse(process.argv.slice(2), context)

脚手架开发的一些细节(一) 完整的yargs demo代码

#!/usr/bin/env node

const yargs = require("yargs")
// 解析参数
// const {hideBin} = require("yargs/helpers")
// 移除文本首行空格
// const dedent = require("dedent")
/**
 *   ./src/index.js  zh llm
 *   [ 'zh', 'llm' ]
 */
// const arg = hideBin(process.argv) // 解析参数,只保留控制台输入的参数
// const cli = yargs(arg)

const { version } = require("../package.json")
const context = {
  yargsStudy: version
}
const cli = yargs()

cli
// 脚手架通用模板格式。$0表示脚手架名称
.usage("Usage: $0 <command> [options]")
// 输入错误命令,提示用户
.recommendCommands()
// 定制自己的错误提示信息
.fail((msg, err) => {
  console.log(msg)
})
// 严格模式。让输入不正确的命令或者参数出现错误提示。而不是没有任何反馈。
.strict()
// 约束最少输入的命令
.demandCommand(1, "A command is required. Pass --help to see all available commands and options.")
// 别名
.alias("h", "help")
.alias("v", "version")
// 修改终端信息展示宽度
.wrap(cli.terminalWidth())
// 结尾输出内容
.epilogue(`
When a command fails, all logs are written to lerna-debug.log in the current working directory.

For more information, check out the docs at https://lerna.js.org/docs/introduction
`)
// 配置全局option(配置多个)
.options({
  debug: {
    type: "boolean",
    describe: "dev debug",
    default: true,
    alias: "d",
    // 表示隐藏命令,用于内部开发使用
    // hidden: true
  }
})
// 配置全局option(配置一个)
.option("name", {
  type: "string",
  describe: "project name",
  alias: "n"
})
.group(["debug", "name"], "dev options")
.group(['version', 'help'], "global options")
.command({
  command: "init [name]",
  aliases: ["i"],
  describe: "List local packages",
  // 执行这个命令之前回调,一般定义一些options
  builder(yargs) {
    yargs.option("name", {
      type: "string",
      describe: "a project name",
      // 别名不能重复
      alias: "n1"
    })
  },
  handler(argv) {
    console.log("argv", argv)
  },
})
// 定义命令 方式二
.command('serve [port]', 'start the server', (yargs) => {
  return yargs
    .positional('port', {
      describe: 'port to bind on',
      default: 5000
    })
}, (argv) => {
  if (argv.verbose) console.info(`start server on :${argv.port}`)
  // serve(argv.port)
})
// .argv
.parse(process.argv.slice(2), context)

总结

Yargs常用API

-   Yargs.usage(提示脚手架用法)
-   Yargs.strict(开启以后可以报错提示)
-   Yargs.demandCommand(规定最少传几个command)
-   Yargs.recommendCommands(在输入错误command以后可以给你推荐最接近的正确的command)
-   Yargs.alias(起别名)
-   Yargs.options(定义多个option)
-   Yargs.option(定义option)
-   Yargs.fail(错误处理方法)
-   Yargs.group(给option分组)
-   Yargs.wrap(命令行工具的宽度)
-   Yargs.epilogue(命令行工具底部的提示)

Yargs开发流程

-   脚手架初始化(将process.argv当参数传递给Yargs())
-   脚手架命令注册(Yargs.command)
-   脚手架参数解析(Yargs.parse)