【脚手架】从0到1搭建React18+TS4.x+Webpack5项目(四)发布脚手架(下)

简介: 【脚手架】从0到1搭建React18+TS4.x+Webpack5项目(四)发布脚手架(下)

4.6.1 其他参数配置

有少数附加功能可以直接构造Argument对象,对参数进行更详尽的配置。

program
  .addArgument(new commander.Argument('<drink-size>', 'drink cup size').choices(['small', 'medium', 'large']))
  .addArgument(new commander.Argument('[timeout]', 'timeout in seconds').default(60, 'one minute'))

4.6.2 自定义参数处理

选项的参数可以通过自定义函数来处理(与处理选项参数时类似),该函数接收两个参数:用户新输入的参数值和当前已有的参数值(即上一次调用自定义处理函数后的返回值),返回新的命令参数值。

处理后的参数值会传递给命令处理函数,同时可通过.processedArgs获取。可以在自定义函数的后面设置命令参数的默认值或初始值。

program
  .command('add')
  .argument('<first>', 'integer argument', myParseInt)
  .argument('[second]', 'integer argument', myParseInt, 1000)
  .action((first, second) => {
    console.log(`${first} + ${second} = ${first + second}`);
  })
;

4.7 处理函数

命令处理函数的参数,为该命令声明的所有参数,除此之外还会附加两个额外参数:一个是解析出的选项,另一个则是该命令对象自身

program
  .argument('<name>')
  .option('-t, --title <honorific>', 'title to use before name')
  .option('-d, --debug', 'display some debugging')
  .action((name, options, command) => {
    if (options.debug) {
      console.error('Called %s with options %o', command.name(), options);
    }
    const title = options.title ? `${options.title} ` : '';
    console.log(`Thank-you ${title}${name}`);
  });

测试:

ljy-create-react-app % ljy --title=hello kevin -d
Called ljy with options { title: 'hello', debug: true }
Thank-you hello kevin

如果你愿意,你可以跳过为处理函数声明参数直接使用 command。 this 关键字设置为运行命令,可以在函数表达式中使用(但不能从箭头函数中使用)。

program
  .command('serve')
  .argument('<script>')
  .option('-p, --port <number>', 'port number', 80)
  .action(function() {
    console.error('Run script %s on port %s', this.args[0], this.opts().port);
  });

处理函数支持async,相应的,需要使用.parseAsync代替.parse

async function run() { /* 在这里编写代码 */ }
async function main() {
  program
    .command('run')
    .action(run);
  await program.parseAsync(process.argv);
}

使用命令时,所给的选项和命令参数会被验证是否有效。凡是有未知的选项,或缺少所需的命令参数,都会报错。 如要允许使用未知的选项,可以调用.allowUnknownOption()。默认情况下,传入过多的参数并不报错,但也可以通过调用.allowExcessArguments(false)来启用过多参数的报错。

4.8 生命周期钩子

可以在命令的生命周期事件上设置回调函数。

program
  .option('-t, --trace', 'display trace statements for commands')
  .hook('preAction', (thisCommand, actionCommand) => {
    if (thisCommand.opts().trace) {
      console.log(`About to call action handler for subcommand: ${actionCommand.name()}`);
      console.log('arguments: %O', actionCommand.args);
      console.log('options: %o', actionCommand.opts());
    }
  });

钩子函数支持async,相应的,需要使用.parseAsync代替.parse。一个事件上可以添加多个钩子。

支持的事件有:

事件名称 触发时机 参数列表
preAction, postAction 本命令或其子命令的处理函数执行前/后 (thisCommand, actionCommand)
preSubcommand 在其直接子命令解析之前调用 (thisCommand, subcommand)

5、设计 create action

一般我们会将这些指令单独放在一个地方去归档,以便于以后维护,比如在根目录中新建一个lib来专门放这些指令的信息,将help指令的信息放在lib/core/help.js,创建指令的信息放在lib/core/create.js中。

5.1 helpOptions

下面实现 helpOptions

// lib/core/help.js
const program = require('commander');
const helpOptions = () => {
  // 增加自己的options
  program.option('-d --dest <dest>', 'A destination folder,例如: -d /src/home/index.js')
  program.option('-f --framework <framework>', 'Your framework,例如: React / Vue')
  // 监听指令
  program.on('--help', function(){
    console.log('')
    console.log('Others')
    console.log(' others')
  })
}
module.exports = helpOptions;

bin/index.js 中使用:

// bin/index.js
#!/usr/bin/env node
const program =  require('commander');
// 查看版本号
program.version(require('../package.json').version);
const helpOptions = require('../lib/core/help');
// 帮助和可选信息
helpOptions();
program.parse(process.argv);

测试

ljy-create-react-app % ljy --help
Usage: ljy [options]
Options:
  -V, --version               output the version number
  -d --dest <dest>            A destination folder,例如: -d /src/home/index.js
  -f --framework <framework>  Your framework,例如: React / Vue
  -h, --help                  display help for command
Others
 others

5.2 createCommands

再实现 createCommands:

const program = require('commander');
const createCommands = () => {
  program
  .command('create <project> [others...]')
  .description('clone a repo into a folder')
  .action((project, others) => {
    console.log('project', project);
    console.log('others', others)
  })
}
module.exports = createCommands;

并引入到 bin/index.js

#!/usr/bin/env node
const program =  require('commander');
// 查看版本号
program.version(require('./package.json').version);
const helpOptions = require('./lib/core/help');
const createCommands =  require('./lib/core/create');
// 帮助和可选信息
helpOptions();
// 创建指令
createCommands();
program.parse(process.argv);

测试:

ljy-create-react-app % ljy create xxx
project xxx
others []

5.3 createProjectActions

action 的回调函数就是我们脚手架的核心流程了,将其抽离到一个单独的文件 lib/core/actions.js 中:

// lib/core/actions.js
// 封装create指令的acitons
const createProjectActions = (project, others) => {
  console.log('project', project);
  console.log('others', others)
  // 1,clone项目
  // 2,运行 npm install
  // 3,运行 npm run dev
}
module.exports = createProjectActions;

lib/core/create.js 中使用:

const program = require('commander');
const createProjectActions = require('./actions');
const createCommands = () => {
  program
  .command('create <project> [others...]')
  .description('clone a repo into a folder')
  .action(createProjectActions)
}
module.exports = createCommands;

5.3.1 clone项目

clone 项目一般会用到一个工具库:download-git-repo,它是放在 npmgitlab 上的,在 github 上面没有仓库,vue-cli 用的也是这个来下载项目模板。

先下载库:

npm install download-git-repo

这个库不好的地方就是它使用的写法比较老旧:

download('flippidippi/download-git-repo-fixture', 'test/tmp', function (err) {
  console.log(err ? 'Error' : 'Success')
})

我们一般会将repo地址提取出来,方便以后进行维护:
image.png

// lib/config/repo-config.js
// github 为了政治正确 把默认分支改成了main,所以这里需要再后面带一个分支信息,否则会报错
const reactRepo =
  "direct:https://github.com/ian-kevin126/react18-ts4-webpack5-starter#main";
module.exports = {
  reactRepo,
};

我们的操作都是在回调里面去做,如果当操作很多的时候,就会造成回调地狱。所幸,node提供了一个模块可以改变这种操作—— promisify,可以将这种方法转化成 promise 形式。

// 封装create指令的acitons
const { promisify } = require('util')
const download = promisify(require('download-git-repo'))
const { reactRepo } = require('../config/repo-config')
// callback ---> promisify ---> Promise ---> async await
const createProjectActions = async (project, others) => {
  // 1,clone项目
  await download(reactRepo, project, { clone: true })
  // 2,运行npm install
  // 3,运行npm run dev
}
module.exports = createProjectActions

现在我们测试一下这个指令,新建一个测试文件夹,然后在终端运行:

ljy create demo-repo

image.png可以发现,我们刚刚配置的 github repo 里面的项目被我们clone下来了。

5.3.2  执行 npm install

接下来,我们希望在clone完项目代码之后,自动执行 package.json 中的 dependencies 依赖的安装,并且将依赖下载的信息打印在控制台里。

需要注意的是,我这里使用的是 pnpm,如果你没有安装这个工具,可能会报错。

还可以使用 chalk 对控制台信息做一些美化,先安装依赖:

npm i chalk@4.0.0

然后就可以在代码中使用了

// 封装create指令的acitons
const chalk = require("chalk");
const { promisify } = require("util");
const download = promisify(require("download-git-repo"));
const { reactRepo } = require("../config/repo-config");
const { spawnCommand } = require("../utils/terminal");
// callback ---> promisify ---> Promise ---> async await
const createProjectActions = async (project, others) => {
  console.log(chalk.green.underline.bold("> Start download repo..."));
  // 1,clone项目
  await download(reactRepo, project, { clone: true });
  console.log(chalk.green("> 模板下载完成,开始 pnpm install..."));
  // 2. 执行 npm install
  // 需要判断一下平台,window 执行 npm 命令实际执行的 npm.cmd
  const command = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
  await spawnCommand(command, ["install"], { cwd: `./${project}` });
  // 3,运行npm run dev
  await spawnCommand(command, ["run", "dev"], { cwd: `./${project}` });
};
module.exports = createProjectActions;

附终端命令相关的执行方法封装:

// lib/utils/terminal.js
/**
 * 终端命令相关
 */
const { spawn } = require("child_process");
// 创建进程执行终端命令
const spawnCommand = (command, args, options) => {
  return new Promise((resolve, reject) => {
    // 通过 spawn 创建一个子进程,并把进程返回
    const childProcess = spawn(command, args, options);
    // 将子进程输出的东西放进当前进程的全局变量 process 的 stdout 中
    // 比如说,当子进程执行 npm install,执行完的时候,会输出一些信息
    // childProcess.stdout 就是这个输出信息流,通过 pipe 将流信息存到当前进程(主进程)
    childProcess.stdout.pipe(process.stdout);
    // 将子进程错误信息放进当前进程
    childProcess.stderr.pipe(process.stderr);
    childProcess.on("close", (code) => {
      if (code === 0) {
        resolve();
      } else {
        reject(`错误:${code}`);
      }
    });
  });
};
module.exports = {
  spawnCommand,
};

执行 ljy create demo-repo

image.png就可以看到已经将代码拉到了本地了!

5.4 命令行 loading 效果

ora 使用非常简单,可以直接看下面的案例。更多使用: ora 文档,利用 ora 来实现一个简单的命令行 loading 效果。

先安装 ora:

npm i ora@5.4.0

然后在 acrtions.js 中使用:

// ...
const ora = require("ora");
// ...
// 定义一个loading
const gitRepoSpinner = ora("Downloading github repo, please wait a while...");
// callback ---> promisify ---> Promise ---> async await
const createProjectActions = async (project, others) => {
  console.log(chalk.green.underline.bold("> Start download repo..."));
  gitRepoSpinner.start();
  // 1,clone项目
  await download(reactRepo, project, { clone: true });
  gitRepoSpinner.succeed();
  // ...
};
module.exports = createProjectActions;

重新执行 ljy create demo-repo,就可以看到控制台有了 loading 效果:

image.png

5.5 ASCII 的艺术字

figlet 模块可以将 text 文本转化成生成基于 ASCII 的艺术字。

先安装依赖:

npm install figlet

然后在 actions.js 中使用:

// ...
var figlet = require("figlet");
// ...
// callback ---> promisify ---> Promise ---> async await
const createProjectActions = async (project, others) => {
  console.log(
    "\r\n" +
      figlet.textSync("LJY-CLI", {
        font: "Big",
        horizontalLayout: "default",
        verticalLayout: "default",
        width: 80,
        whitespaceBreak: true,
      }) +
      "\r\n"
  );
  // ...
};
module.exports = createProjectActions;

重新执行 ljy create demo-repo,就会看到
image.png
是不是有那味儿了!figlet 提供了多种字体,可以去官网选择你喜欢的字体。

6、发布脚手架

  1. 登录npm账号
  2. 在终端 npm login,输入账户密码
  3. npm publish

就这么简单,然后你就可以在 npm 官网看到你的 package 了!

这是我的:

end~

相关文章
|
2月前
|
前端开发 JavaScript
React项目路由懒加载lazy、Suspense,使第一次打开项目页面变快
本文介绍了在React项目中实现路由懒加载的方法,使用React提供的`lazy`和`Suspense`来优化项目首次加载的速度。通过将路由组件改为懒加载的方式,可以显著减少初始包的大小,从而加快首次加载速度。文章还展示了如何使用`Suspense`组件包裹`Switch`来实现懒加载过程中的fallback效果,并提供了使用前后的加载时间对比,说明了懒加载对性能的提升作用。
184 2
React项目路由懒加载lazy、Suspense,使第一次打开项目页面变快
|
2月前
|
前端开发 JavaScript Java
SpringBoot项目部署打包好的React、Vue项目刷新报错404
本文讨论了在SpringBoot项目中部署React或Vue打包好的前端项目时,刷新页面导致404错误的问题,并提供了两种解决方案:一是在SpringBoot启动类中配置错误页面重定向到index.html,二是将前端路由改为hash模式以避免刷新问题。
241 1
|
2月前
|
前端开发
React | 修改React脚手架的默认端口号?
React | 修改React脚手架的默认端口号?
146 64
|
1月前
|
XML 前端开发 JavaScript
react学习笔记一:入门级小白到脚手架(create-react-app)开发项目
这篇文章是React的学习笔记,覆盖了从React的基础用法到高级特性,包括组件化、状态管理、生命周期、虚拟DOM等主题,适合React初学者参考。
96 0
react学习笔记一:入门级小白到脚手架(create-react-app)开发项目
|
25天前
|
前端开发 JavaScript
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
|
2月前
|
移动开发 前端开发
react项目配合diff实现文件对比差异功能
在React项目中,可以使用`diff`库实现文件内容对比差异功能。首先安装`diff`库,然后在组件中引入并使用`Diff.diffChars`或`Diff.diffLines`方法比较文本差异。通过循环遍历`diff`结果,可以生成不同样式的HTML元素来高亮显示文本差异。
124 1
react项目配合diff实现文件对比差异功能
|
2月前
|
前端开发 算法 JavaScript
React项目input输入框输入自动失去焦点
本文讨论了在React项目中如何处理input输入框自动失去焦点的问题,特别是在移动端开发中。文章提供了一个使用React Native的TouchableWithoutFeedback组件来监听点击事件,并在事件处理函数中通过调用Keyboard.dismiss()方法使输入框失去焦点的示例代码。这种方法可以确保在用户点击页面其他区域时,键盘能够收起,输入框失去焦点。
99 1
React项目input输入框输入自动失去焦点
|
2月前
|
JavaScript 前端开发 应用服务中间件
本地运行打包好的React、Vue项目
本文讨论了如何本地运行打包好的React和Vue项目,并解决了使用React-Router时Tomcat部署刷新页面导致404的问题,提出了将请求转回index.html的解决方案。
30 1
本地运行打包好的React、Vue项目
|
1月前
|
前端开发 JavaScript 应用服务中间件
linux安装nginx和前端部署vue项目(实际测试react项目也可以)
本文是一篇详细的教程,介绍了如何在Linux系统上安装和配置nginx,以及如何将打包好的前端项目(如Vue或React)上传和部署到服务器上,包括了常见的错误处理方法。
332 0
linux安装nginx和前端部署vue项目(实际测试react项目也可以)
|
2月前
|
前端开发 NoSQL MongoDB
React技术栈-基于react脚手架编写评论管理案例
这篇文章介绍了在MongoDB中使用sort和投影来对查询结果进行排序和限制返回的字段,通过具体的命令示例展示了如何实现这些操作。
51 6
React技术栈-基于react脚手架编写评论管理案例
下一篇
无影云桌面