自主搭建脚手架,前端提效

如何快速学会脚手架开发?

先回答以下两个问题

问题1:为什么要用Node.js开发脚手架?

  1. JavaScript和TypeScript强大的语法特性
  2. Node.js具有强大的生态能够支持CLI的快速开发 内置库,如:fs、path、os、child_process……
  3. Node.js具有强大的npm和yarn等包管理系统,可以快速完成CLI的发布和更新 创建,npm init 开发,npm scripts 发布,npm publish 应用,npm instal

问题2:如何快速学会脚手架开发?

  1. 找到脚手架的使用场景,树立应用脚手架的信心
  2. 学习基础知识:学习Shell、Bash、CLI等操作系统的基础知识,牢固地掌握脚手架开发的底层原理
  3. 掌握Node开发:根据脚手架开发需求,学习Node.js内置库、常用库、脚手架框架及各种命令行特有能力的开发方法(键盘输入、键盘监听、文本颜色、命令行交互等) 开发提效工具:将学到的知识进行综合应用,开发脚手架工具,解决实际项目开发过程中的具体问题

什么是Bash和CLI

Bash和Shell是什么?它们有什么用?

什么是Shell?

Shell是计算机提供给用户与其他程序进行交互的接口

Shell是一个命令解释器,当你输入命令后,由Shell进行解释后交给操作系统内核(OSKernel进行处理)

图形操作系统是Shell吗?是的!图形操作系统属于CUI Shell

什么是Bash?

简单地说:Bash是一种程序,它的职责是用来进行人机交互的

Bash和其他程序最大的区别在于,它不是用来完成特定任务(如计算器、文件管理等),我们通过bash shell来执行程序

Bash有什么用?

绝大部分人都习惯使用可视化的图形界面操作系统,但是Bash使用了一种与图形界面完全相反的方案:通过纯文本的控制台进行控制,它的主要交互方式通过键盘输入文本,文字反馈来实现人机交互

那么有一个关键的问题:在GUI(图形界面系统)发展如火如荼的今天,Bash过时了吗?不!恰恰相反Bash在开发领域应用越来越广泛

Bash最大的优势就是简单易用,虽然它的显示效果不如GUI(图形界面系统),但一旦熟练后其操作效率远远大于GUI(图形界面系统)!

什么是CLI?

命令行界面(CLI)是一种基于文本界面(类似:MacOS终端、Windows cmd.exe),用于运行程序

CLI接受键盘输入,在命令符号提示处输入命令,然后由计算机执行并返回结果

今天大部分操作系统都会以GUI作为基础,但是基于Unix的系统都会同时提供CLI和GUI

总结

Shell是操作系统提供的接口程序,用于接受用户输入的命令,交给操作系统内核执行并接受响应结果

Bash是Shell的一个实现,用于执行用户输入的命令

CLI是Bash的运行环境,CLI接收用户键盘输入,交给Bash执行,并将程序处理结果以文本形式进行显示

从使用角度理解什么是脚手架?

脚手架简介

脚手架本质是一个操作系统的客户端,它通过命令行执行比如:

vue create vue-test-app

上面这条命令由3个部分组成:

主命令:vue

command:create

command的param:vue-test-app

它表示创建一个vue项目,项目的名称为vue-test-app,以上是一个较为简单的脚手架命令,但实际场景往往更加复杂,比如:

当前目录已经有了文件了,我们需要覆盖当前目录下的文件,强制进行安装vue项目,此时我们就可以输入:

vue create vue-test-app --force

这里的--force 叫做option(选项的意思),用来辅助脚手架确认在特定场景下用户的选择(可以理解为配置)

还有一种场景:

通过“vue create”创建项目时,会自动执行npm install 帮用户安装依赖,如果我们希望使用淘宝源来安装,可以输入命令:

vue create vue-test-app --force -r https://registry.npm.taobao.org

这里的-r也叫做option,它与--force不同的是它使用-,并且使用简写,这里的-r也可以更换成--registry,有的人可能要问了,为什么我会知道这个命令,其实我们输入下面的命令就可以看到vue create支持所有的options:

vue create --help

 -r https://registry.npm.taobao.org 后面的 https://registry.npm.taobao.org 成为option的param,其实--force可以理解为:--force true ,简写为:--force或-f

脚手的执行原理

  • 1、在终端输入命令 vue create vue-test-app
  • 2、终端解析出vue命令
  • 3、终端在环境变量中找到vue命令
  • 4、终端根据vue命令链接到实际文件vue.js
  • 5、终端利用node执行vue.js
  • 6、vue.js解析command/options
  • 7、vue.js执行command
  • 8、执行完毕,退出执行

从应用的角度看如何开发一个脚手架

这里以vue-cli为例

  • 1、开发npm项目,该项目中应包含一个bin/vue.js文件,并将这个项目发布到npm
  • 2、将npm项目安装到node的lib/node_modules
  • 3、在node的bin目录下配置vue软连接指向lib/node_modules/@vue/cli/bin/vue.js 这样我们在执行vue命令的时候就可以找到vue.js进行执

还有很多疑问需要解答

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

2、全局安装@vue/cli时发生了什么?

3、为什么vue指向一个js文件,我们却可以直接通过vue命令直接去执行它?

4、扩展一下,两种写法的区别 #!/usr/bin/env node #!/usr/bin/node

第一种是在环境变量中查找node (环境变量中找寻node在任何机器都可以使用)

第二种是直接执行/usr/bin/目录下的node (缺点:固定路径,换机器极大可能无法使用)

 以上问题在下面都可以得到解答,请耐心看下去

脚手架执行全过程

  1. 脚手架创建
  2. npm init
  3. 脚手架开发
  4. 分包
  5. 参数解析
  6. 脚手架调试
  7. npm link
  8. 脚手架发布
  9. npm publish

框架搭建脚手架

为什么需要脚手架框架?

提升脚手架开发效率、大幅度提升脚手架命令创建、修改的速度

简化脚手架开发过程,大幅度提升代码的可读性和可维护性

常用脚手架框架

yargs 周下载量6000w+ 案例:gulp-cli

commander周下载量8000w+ 案例: vue-cli webpack-cli create-react-app

oclif 脚手架生成器

脚手架开发流程详解

  • 开发流程
  • 创建npm项目
  • 创建脚手架入口文件,最上方添加:
  • #!/usr/bin/env node 意思是从当前电脑的环境变量中寻找node
  • 配置package.json,添加bin属性
  • 编写脚手架代码
  • 将脚手架发布到npm
  • 使用流程
  • 安装脚手架
  • npm install -g your-own-cli
  • 使用脚手架
  • your-own-cli
  • 脚手架开发难点解析
  • 分包:如何将将复杂的系统拆分为若干个模块?
  • 命令注册:
  • vue create --name VueTest
  • vue add
  • vue invoke
  • 参数解析:
  • vue command [options] <params>  //命令
  • vue对应的是vue命令
  • command对应的是create
  • [options]对应的是--name
  • <params>对应的是VueTest
  • options全称:--version、--help
  • options简写:-v、-h
  • 带params的options:--path/Users/sam/Desktop/vue-test
  • 帮助文档:
  • global help
  • Usage
  • Options
  • Commands
  • 示例:vue的帮助信息:
  • Usage: vue <command> [options]
  • Options:
  •   -V, --version  output the version number
  •   -h, --help     output usage information
  • Commands:
  •   init           generate a new project from a template
  •   list           list available official templates
  •   build          prototype a new project
  •   create         (for v3 warning only)
  •   help [cmd]     display help for [cmd]
  • 还有很多,比如:
  • 命令行交互
  • 日志打印
  • 命令行文字变色
  • 网络通信:HTTP/WebSocket
  • 文件处理
  • 等等……
     

开始第一个脚手架

1、创建一个文件夹
2、npm init -y 进行初始化
3、在创建的文件夹中新增一个index.js文件
4、创建一个文件夹bin,将index.js文件拖入bin文件夹中
5、在index.js中最上方添加#!/usr/bin/env node 找到node所在文件路径
6、在packag.json中配置bin

{
  "name": "bzw-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin": {
    "bzw-cli": "bin/index.js"  //这里是最重要的
    //表示当前根路径下bin文件夹中的index.js文件
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}


7、发布到npm上,npm login 进行登录
npm install -g nrm安装
nrm use npm 切换到官方源
nrm use taobao发布完成后切换到淘宝源
注意如果遇到以下错误,执行npm config set registry https://registry.npmjs.org 
npm ERR! code E301
npm ERR! Registry returned 301 for POST on https://registry.npm.taobao.org/-/v1/login

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\yangbin\AppData\Roaming\npm-cache\_logs\2023-03-04T08_50_44_889Z-debug.log
然后重新npm login 就会让你输入username 和password了
在输入password的时候鼠标指针会被隐藏,不用害怕,直接输入完然后回车就行了
输入完邮箱之后会让你输入验证码:
Enter one-time password from your authenticator app:
去你邮箱找验证码输入进去就行了
8、输入发布命令 npm publish
当出现了你的脚手架name 和版本号代表成功+ bzw-cli@1.0.0
注意:如果你更改了东西,那么一定要到package.json文件中更改版本号要不然再次更新会报错
 

安装和使用你创建的脚手架

 1、npm i -g 脚手架名称

2、windows系统通过where 脚手架名称 查找脚手架安装路径

mac系统通过which 脚手架名称 查找脚手架安装路径

终端增删文件

md 文件目录名 新增文件夹

rd 文件目录或文件夹名 删除文件夹或目录

cd . > 文件名.js 新增.js文件

分包功能,在本地调试

1、创建两个文件夹
2、两个文件夹中都初始化 npm init -y
3、本地库文件包导出方法或者属性
4、脚手架文件需要在index.js中添加#!/usr/bin/env node
5、到本地库文件的包终端输入npm link添加软链接到本地node上
6、回到脚手架文件的终端输入npm link 被调试包的名称
7、将node_modules包下面的本地库文件的package.json文件中的main改为正确的文件路径
8、在脚手架文件的index.js文件中通过require引入被调试包
9、在脚手架文件夹下的package.json文件中需要添加
"dependencies": {
    "bzw-cli":"^1.0.0" 脚手架的名称跟版本号,否则上传到npm无法正常使用分包
  }
例如:const lib=require("被调试包名称")之后就可以获取被调试包的所有导出的属性好方法了
通过node执行调试包的index.js文件即可
问题报错:
Error: Cannot find module 'D:\CLI\bzw-cli\node_modules\bzw-cli-lib\index.js'. Please verify that the package.json has a valid "main" entry
解决方案:将node_modules文件夹下面的package.json文件中的main入口文件路径设置为文件路径
例如:lib文件夹下面的index.js文件作为入口文件,那么main:"lib/index.js"就不会报错了
 

脚手架本地link标准流程

your-cli-dir:代表脚手架文件
your-lib:代表本地库文件

cd your-cli-dir  
npm link
链接本地库文件:
cd your-cli-dir
npm link
cd your-cli-dir
npm link your-lib  
取消链接本地库文件:
cd your-cli-dir
npm unlink your-lib
cd your-cli-dir
npm unlink your-lib

取消链接本地库报错
npm ERR! code ENOVERSIONS
npm ERR! No valid versions available for bzw-cli
找不到该文件

理解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-lib:将当前项目中的库文件依赖移除

cli命令注册参数解析

1、首先在分包中导出init方法
module.exports = {
  init({ option, param }) {
    console.log("执行init流程", option, param);
  },
};
2、分包中npm link创建软链接
3、在脚手架中npm link 分包名称 创建链接
4、注册一个命令 bzw-cli init 初始化
#!/usr/bin/env node
const lib = require("bzw-cli-lib");
//注册一个bzw-cli init命令
const argv = require("process").argv;
const command = argv[2];
//解析options参数
const options = argv.slice(3); //获取第三个元素和之后的元素
let [option, param] = options; //解构
if (option.length) {
  option = option.replace("--", ""); //将--转化为""
  if (command) {
    if (lib[command]) {
      lib[command]({ option, param }); //将options参数传递到方法中处理
    } else {
      console.log("无效的命令");
    }
  } else {
    console.log("请输入命令");
  }
}

执行node .\index.js init --name vue-test  //执行init流程 name vue-test
执行node .\index.js publish //无效的命令
执行node .\index.js //请输入命令

5、//实现参数解析 --version 和init --name
if (command.startsWith("--") || command.startsWith("-")) {
  const globalOption = command.replace(/--|-/g, "");
  if (globalOption === "version" || globalOption === "V") {
    console.log("1.0.0");
  }
} else {
  console.log("无效命令");
}
执行node .\index.js --version  //1.0.0
执行node .\index.js -V //1.0.0
执行node .\index.js V//无效命令

 分包发布脚手架

1、cd到脚手架包目录下
2、通过npm unlink 分包名称 解除分包链接
3、cd到分包目录下 通过 npm unlink 解除node全局链接
4、在分包目录下 通过 npm publish 发布到npm上
5、cd到脚手架目录下更改package.json文件中的
"dependencies": {
    "bzw-cli-lib":"^1.0.0" //版本号需要和分包版本号同步
}
6、将脚手架node_modules文件夹删除
7、在脚手架目录下执行 npm unlink
8、在脚手架目录的package.json文件中添加
"bin":{
    "bzw-cli":"bin/index.js"
},
9、发布脚手架npm publish

yargs脚手架框架

#!/usr/bin/env node
//安装yargs
npm i yargs
//安装dedent
npm i -S dedent
//引入yargs
const yargs = require("yargs/yargs");
//引入解析器
const { hideBin } = require("yargs/helpers");
//引入dedent
const dedent = require("dedent");
//执行node .\bin\index.js --help 打印['--help']
const argv = hideBin(process.argv);
//定义yargs
const cli = yargs(argv);
//调用yargs

cli
  //alias代表别名,第一个参数是简称,第二个是全称
  .alias("h", "help")
  .alias("v", "version")
  //wrap可以控制脚手架宽度变化,cli.terminalWidth()最大宽度
  .wrap(cli.terminalWidth())
  //demandCommand中第一个参数表示最少输入命令个数,第二个参数是提示语
  .demandCommand(
    1,
    "A command is required. Pass --help to see all available commands and options."
  )
  //usage用来规定用法
  .usage("Usage: bzw-cli [command] <options>")
  //strict代表严格模式
  .strict()
  //recommendCommands作用:当输入命令错误,那么会在最后一行提示像似命令
  .recommendCommands()
  //自定义错误信息
  .fail((err, msg) => {
    //err表示错误内容
    console.log("err", err);
    //msg表示提示
    console.log("msg", msg);
  })
  //epilogue表示可以在结尾添加一句话
  .epilogue(
    dedent`   111
  111`
  )
  //options可以增加全局的多个选项
  .options({
    debug: {
      //选项类型
      type: "boolean",
      //选项描述
      describe: "日志断点",
      //选项别名
      alias: "d",
      hidden: false,
    },
  })
  //option代表单个选项
  .option("registry", {
    //选项类型
    type: "string",
    //选项是否隐藏true/false(适用于内部开发)
    hidden: false,
    //选项描述
    describe: "日志记录",
    alias: "r",
  })
  //group可以将不同的command分组
  .group(["debug"], "Dev Options")
  .group(["registry"], "Serve Options")
  //command自定义命令
  .command(
    "init [name]",
    "初始化项目",
    (yargs) => {
      //init [name] 代表命令格式
      //初始化项目 表示命令提示
      //(yargs)函数定义执行方法
      yargs.option("name", {
        //选项类型
        type: "string",
        //选项描述
        describe: "项目名称",
        //选项别名
        alias: "n",
      });
    },
    (argv) => {
      //argv函数代表解析的参数
      console.log(argv);
    }
  )
  .command({
    //自定义list命令
    command: "list",
    //命令别名,可以使用ls,la,ll
    aliases: ["ls", "la", "ll"],
    //选项描述
    describe: "List local packages",
    //处理方法
    builder: (yargs) => {},
    //解析参数
    handler: (argv) => {
      console.log(argv);
    },
  }).argv;
//argv是Options参数

commander脚手架框架

//安装commander
npm i commander
代码:
#!/usr/bin/env node
//引入commander
const commander = require("commander");
//引入package文件
const pkg = require("../package.json");
//引入脚手架,获取commander单例
// const { program } = commander;
//手动实例化Commander实例
const program = new commander.Command();
//定义version方法
program.version(pkg.version).parse(process.argv); 

 未完待续……

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值