用 nodejs 写一个命令行东西 :建立 react 组件的命令行东西

用 nodejs 写一个敕令行东西 :建立 react 组件的敕令行东西

媒介

上周,同事埋怨说 react 怎样不能像 angular 那样,运用敕令行东西来天生一个组件。对呀,日常平凡事情时,想要建立一个 react 的组件,都是直接 copy 一个组件,然后做一些修正。为何不能将这个历程交给顺序去做呢?当天晚上,我就模仿 angular-cli 的 api,写了一个天生 react 组件的敕令行东西 rcli。在这里纪录一下完成的历程。

api 设想

0.1.0 版本的 rcli 参照 angular-cli 的设想,有两个功用:

  1. 运用 rcli new PROJECT-NAME 敕令,建立一个 react 项目,个中天生项目标脚手架当然是 create-react-app
  2. 运用 rcli g component MyComponent 敕令, 建立一个 MyComponent 组件, 这个组件是一个文件夹,在文件夹中包括 index.jsMyComponent.jsMyComponent.css 三个文件

厥后发明 rcli g component MyComponent 敕令在 日常平凡开辟历程当中是不够用的,由于这个敕令只是建立了一个类组件,且继续自 React.Component

在日常平凡开辟 历程当中,我们会用到这三类组件:

  1. 继续自 React.Component 的类组件
  2. 继续自 React.PureComponent 的类组件
  3. 函数组件(无状况组件)

注: 未来能够运用 Hooks 来替代之前的类组件

因而就有了 0.2.0 版本的 rcli

0.2.0 版本的 rcli

用法

Usage: rcli [command] [options]

Commands:
  new <appName>
  g <componentName>

`new` command options:
  -n, --use-npm                    Whether to use npm to download dependencies

`g` command options:
  -c, --component <componentName>  The name of the component
  --no-folder                      Whether the component have not it's own folder
  -p, --pure-component             Whether the component is a extend from PureComponent
  -s, --stateless                  Whether the component is a stateless component
运用 create-react-app 来建立一个运用
rcli new PROJECT-NAME
cd PROJECT-NAME
yarn start

或许你能够运用 npm 装置依靠

rcli new PROJECT-NAME --use-npm
cd PROJECT-NAME
npm start
天生纯组件(继续自 PureComponent,以进步机能)
rcli g -c MyNewComponent -p
天生类组件(有状况组件)
rcli g -c MyNewComponent

即是:

rcli g -c ./MyNewComponent
天生函数组件(无状况组件)
rcli g -c MyNewComponent -s
天生组件不在文件夹内(也不包括 css 文件和 index.js 文件)
# 默许天生的组件都邑都包括在文件夹中的,若不想天生的组件被文件夹包括,则加上 --no-folder 选项
rcli g -c MyNewComponent --no-folder

完成历程

1. 建立项目

  • 建立名为 hileix-rcli 的项目
  • 在项目根目次运用 npm init -y 初始化一个 npm package 的基本信息(即天生 package.json 文件)
  • 在项目根建立 index.js 文件,用来写用户输入敕令后的主要逻辑代码
  • 编辑 package.json 文件,增加 bin 字段:
{
  "name": "hileix-rcli",
  "version": "0.2.0",
  "description": "",
  "main": "index.js",
  "bin": {
    "rcli": "./index.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/hileix/rcli.git"
  },
  "keywords": [],
  "author": "hileix <304192604@qq.com> (https://github.com/hileix)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/hileix/rcli/issues"
  },
  "homepage": "https://github.com/hileix/rcli#readme",
  "dependencies": {
    "chalk": "^2.4.1",
    "commander": "^2.19.0",
    "cross-spawn": "^6.0.5",
    "fs-extra": "^7.0.1"
  }
}
  • 在项目根目次下,运用 npm link 敕令,建立软链接指向到本项目标 index.js 文件。如许,就可以再开辟的时刻,直接运用 rcli 敕令直接进行测试 ~

2. rcli 会依靠一些包:

  • commander:tj 大神写的一款特地处置惩罚敕令行的东西。主要用来剖析用户输入的敕令、选项
  • cross-spawn:nodejs spawn 的跨平台的版本。主要用来建立子历程实行一些敕令
  • chalk:给敕令行中的笔墨增加款式。
  • path:nodejs path 模块
  • fs-extra:供应对文件操纵的要领

3.完成 rcli new PROJECT-NAME

#!/usr/bin/env node

'use strict';

const program = require('commander');
const log = console.log;

// new command
program
  // 定义 new 敕令,且背面跟一个必选的 projectName 参数
  .command('new <projectName>')
  // 对 new 敕令的形貌
  .description('use create-react-app create a app')
  // 定义运用 new 敕令以后能够运用的选项 -n(运用 npm 来装置依靠)
  // 在运用 create-react-app 中,我们能够能够增加 --use-npm 选项,来运用 npm 装置依靠(默许运用 yarn 装置依靠)
  // 所以,我将这个选项增加到了 rcli 中
  .option('-n, --use-npm', 'Whether to use npm to download dependencies')
  // 定义实行 new 敕令后挪用的回调函数
  // 第一个参数就是在定义 new 敕令时的必选参数 projectName
  // cmd 中包括了敕令中选项的值,当我们在 new 敕令中运用了 --use-npm 选项时,cmd 中的 useNpm 属性就会为 true,否则为 undefined
  .action(function(projectName, cmd) {
    const isUseNpm = cmd.useNpm ? true : false;
    // 建立 react app
    createReactApp(projectName, isUseNpm);
  });

program.parse(process.argv);

/**
 * 运用 create-react-app 建立项目
 * @param {string} projectName 项目称号
 * @param {boolean} isUseNpm 是不是运用 npm 装置依靠
 */
function createReactApp(projectName, isUseNpm) {
  let args = ['create-react-app', projectName];
  if (isUseNpm) {
    args.push('--use-npm');
  }
  // 建立子历程,实行 npx create-react-app PROJECT-NAME [--use-npm] 敕令
  spawn.sync('npx', args, { stdio: 'inherit' });
}

上面的代码边完成了 rcli new PROJECT-NAME 的功用:

  • #!/usr/bin/env node 示意运用 node 实行本剧本

4.完成 rcli g [options]

#!/usr/bin/env node

'use strict';
const program = require('commander');
const spawn = require('cross-spawn');
const chalk = require('chalk');
const path = require('path');
const fs = require('fs-extra');

const log = console.log;

program
  // 定义 g 敕令
  .command('g')
  // 敕令 g 的形貌
  .description('Generate a component')
  // 定义 -c 选项,接收一个必选参数 componentName:组件称号
  .option('-c, --component-name <componentName>', 'The name of the component')
  // 定义 --no-folder 选项:示意当运用该选项时,组件不会被文件夹包裹
  .option('--no-folder', 'Whether the component have not it is own folder')
  // 定义 -p 选项:示意当运用该选项时,组件为继续自 React.PureComponent 的类组件
  .option(
    '-p, --pure-component',
    'Whether the component is a extend from PureComponent'
  )
  // 定义 -s 选项:示意当运用该选项时,组件为无状况的函数组件
  .option(
    '-s, --stateless',
    'Whether the component is a extend from PureComponent'
  )
  // 定义实行 g 敕令后挪用的回调函数
  .action(function(cmd) {
    // 当 -c 选项没有传参数进来时,报错、退出
    if (!cmd.componentName) {
      log(chalk.red('error: missing required argument `componentName`'));
      process.exit(1);
    }
    // 建立组件
    createComponent(
      cmd.componentName,
      cmd.folder,
      cmd.stateless,
      cmd.pureComponent
    );
  });

program.parse(process.argv);

/**
 * 建立组件
 * @param {string} componentName 组件称号
 * @param {boolean} hasFolder 是不是含有文件夹
 * @param {boolean} isStateless 是不是是无状况组件
 * @param {boolean} isPureComponent 是不是是纯组件
 */
function createComponent(
  componentName,
  hasFolder,
  isStateless = false,
  isPureComponent = false
) {
  let dirPath = path.join(process.cwd());
  // 组件在文件夹中
  if (hasFolder) {
    dirPath = path.join(dirPath, componentName);

    const result = fs.ensureDirSync(dirPath);
    // 目次已存在
    if (!result) {
      log(chalk.red(`${dirPath} already exists`));
      process.exit(1);
    }
    const indexJs = getIndexJs(componentName);
    const css = '';
    fs.writeFileSync(path.join(dirPath, `index.js`), indexJs);
    fs.writeFileSync(path.join(dirPath, `${componentName}.css`), css);
  }
  let component;
  // 无状况组件
  if (isStateless) {
    component = getStatelessComponent(componentName, hasFolder);
  } else {
    // 有状况组件
    component = getClassComponent(
      componentName,
      isPureComponent ? 'React.PureComponent' : 'React.Component',
      hasFolder
    );
  }

  fs.writeFileSync(path.join(dirPath, `${componentName}.js`), component);
  log(
    chalk.green(`The ${componentName} component was successfully generated!`)
  );
  process.exit(1);
}

/**
 * 猎取类组件字符串
 * @param {string} componentName 组件称号
 * @param {string} extendFrom 继续自:'React.Component' | 'React.PureComponent'
 * @param {boolean} hasFolder 组件是不是在文件夹中(在文件夹中的话,就会自动加载 css 文件)
 */
function getClassComponent(componentName, extendFrom, hasFolder) {
  return '省略...';
}

/**
 * 猎取无状况组件字符串
 * @param {string} componentName 组件称号
 * @param {boolean} hasFolder 组件是不是在文件夹中(在文件夹中的话,就会自动加载 css 文件)
 */
function getStatelessComponent(componentName, hasFolder) {
  return '省略...';
}

/**
 * 猎取 index.js 文件内容
 * @param {string} componentName 组件称号
 */
function getIndexJs(componentName) {
  return `import ${componentName} from './${componentName}';
export default ${componentName};
`;
}
  • 如许就完成了 rcli g [options] 敕令的功用

总结

  • api 设想是很主要的:好的 api 设想能让运用者越发方便地运用,且更改少
  • 当本身想不到该怎样设想 api 时,能够参考他人的 api,看看他人是怎样设想的好用的
    原文作者:hileix
    原文地址: https://segmentfault.com/a/1190000017392058
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞