作为一个JavaScript开发人员,创建一个新项目通常是一个重复而乏味的过程。对于每个新项目,我们通常首先创建一个package.json文件,然后加入各种依赖项并配置好它们,还要创建项目目录结构,添加各种其他文件…

如果对JavaScript足够了解,上面所有步骤都可以通过编写脚本帮我们完成。

我将使用Node.js构建一个跨平台命令行界面(CLI):允许我们使用一组内置模板快速构建一个新项目。它是完全可扩展的,可以轻松地根据自己的需求对其进行调整,将我们从繁琐的流程中解放出来。

为什么要自己写?

尽管有很多工具(例如Yeoman)可以完成这些任务,但是通过编写自己的构建工具,首先我们可以学到很多知识和技巧。其次在我们碰到项目中一些特定问题时,现有工具可能并不能很好的满足我们的需求。

有人把不要重复造轮子奉为圣经,但是在某些情况下,实现您自己的工具可能是非常有益的。不但能学习很多的知识,也可以针对您的需求提出高度个性化和高效的工具。何乐而不为呢?

我们不需要重复造轮子,使用一个名为capora .js的库来构建CLI命令行,它使用了两个模块:prompt 和 shellJS

  • prompt来请求用户数据
  • shellJS为我们的Node.js环境提供了一些Unix命令。

选择它们主要是因为它们易于使用,但是在完成本教程之后,您将能够将它们替换为最适合您需要的替代方案。

完整代码下载地址:GitHub。

废话说的够多了,下面进入正题。

运行Capora.js

首先,创建一个新目录,并新建package.json:

{
  "name": "scaffold",
  "version": "1.0.0",
  "main": "index.js",
  "bin": {
    "scaffold": "index.js"
  },
  "dependencies": {
    "caporal": "^0.3.0",
    "colors": "^1.1.2",
    "prompt": "^1.0.0",
    "shelljs": "^0.7.7"
  }
}
如果依赖项有更新,请注意更新相关API

注意bin中的scaffold值:它表示每次在终端(index.js)中输入该命令时调用的文件,可以根据需要改变这个值。

程序入口点

CLI工具的第一个组件是index.js文件,包含了命令列表,选项和以及常用的功能。但是在编写此文件之前,让我们先定义CLI将执行的操作。

  • create命令允许我们创建我们选择的项目样板。
  • create命令接受一个必选模板参数,该参数代表了使用的模板。
  • 采用——variant选项,允许我们选择模板的特定变量。
  • 如果没有提供特定的变量,它将使用默认的变量(稍后定义)。

让我们为index.js文件添加以下内容:

#!/usr/bin/env node

const prog = require('caporal');

prog
  .version('1.0.0')
  .command('create', 'Create a new application')
  .argument('<template>', 'Template to use')
  .option('--variant <variant>', 'Which <variant> of the template is going to be created')
  .action((args, options, logger) => {
    console.log({
      args: args,
      options: options
    });
  });

prog.parse(process.argv);

CLI全局可用

现在我们可以测试应用程序。要做到这一点,我们需要使用npm的link命令将其全局可用。在项目根目录执行以下操作:

npm link

处理完成后,我们将能够在任何目录的终端中执行scaffold,而不必显式引用index.js文件:

scaffold create node --variant mvc

输出结果:

{ args: { template: 'node' }, options: { variant: 'mvc' } }

这是我们接下来将使用模板创建项目。

创建模板

我们的模板将由文件和目录组成,这些文件和目录结构用于启动和运行特定类型的项目。每个模板都有一个packge.json文件中包含一些占位符值,我们可以用实际数据填充这些值。

首先,在项目中创建模板目录,并在其中创建node目录。在node目录中,创建一个default目录(如果不提供variant,将使用该目录)和另一个名为mvc的目录(使用MVC架构创建Node.js项目)。

.
└── templates
    └── node
        ├── default
        └── mvc

现在我们需要填充node和default目录。

接下来,我们可以继续在需要动态值的地方放置变量。每个模板文件夹应该包含一个package.json文件。打开这些文件,在大写字母(没有空格)和方括号中包含任何变量。

default 目录下package.json:

{
  "name": "[NAME]",
  "version": "[VERSION]",
  "description": "[DESCRIPTION]",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js",
    "start:dev": "nodemon server.js"
  },
  "author": "[AUTHOR]",
  "license": "[LICENSE]",
  "dependencies": {
    "dotenv": "^2.0.0",
    "hapi": "^16.1.0",
    "hoek": "^4.1.0"
  },
  "devDependencies": {
    "nodemon": "^1.11.0"
  }
}

变量创建之后,将它们放入同一目录下的_variables.js文件:

/*
 * Variables to replace
 * --------------------
 * They are asked to the user as they appear here.
 * User input will replace the placeholder  values
 * in the template files
 */

module.exports = [
  'name',
  'version',
  'description',
  'author',
  'license'
];

导出数组中的名称在文件中是一致的,都是小写的,并且没有方括号。我们将使用这个文件来请求CLI中的每个值。

现在,我们可以继续为create命令创建函数。

创建Create函数

index.js文件中,传递一个简单的函数给action(),该函数记录CLI接收到的值。现在我们要用一个新的函数替换这个函数,这个函数将模板文件复制到执行scaffold命令的目录中。我们还将用通过用户输入获得的值替换占位符变量。

在lib目录中(保证代码组织),添加一个create.js文件,并加入内容:

module.exports = (args, options, logger) => {

};

我们将把应用程序的所有逻辑放在这个函数中,这意味着我们需要改变index.js文件相应的代码:

#!/usr/bin/env node

const prog = require('caporal');
const createCmd = require('./lib/create');

prog
  .version('1.0.0')
  .command('create', 'Create a new application')
  .argument('<template>', 'Template to use')
  .option('--variant <variant>', 'Which <variant> of the template is going to be created')
  .action(createCmd);

prog.parse(process.argv);

导入依赖项和变量赋值

create.js内容:

const prompt = require('prompt');
const shell = require('shelljs');
const fs = require('fs');
const colors = require("colors/safe");

// Set prompt as green and use the "Replace" text
prompt.message = colors.green("Replace");

让我们添加一些变量:

const variant = options.variant || 'default';
const templatePath = `${__dirname}/../templates/${args.template}/${variant}`;
const localPath = process.cwd();

如您所见,我们正在获取传递给scaffold命令的variant选项,如果省略了该选项,则将其设置为“default”。变量templatePath包含指定模板的完整路径,localPath包含对执行命令的目录的引用。

拷贝Template

使用来自shellJS的cp函数复制文件的过程非常简单。继续添加以下变量:

if (fs.existsSync(templatePath)) {
  logger.info('Copying files…');
  shell.cp('-R', `${templatePath}/*`, localPath);
  logger.info(' The files have been copied!');
} else {
  logger.error(`The requested template for ${args.template} wasn't found.`)
  process.exit(1);
}

首先,我们确保模板存在,如果不存在,我们将使用capora .js中的log .error()函数退出显示错误消息的进程。如果模板存在,我们将使用log .info()显示一条通知消息,并使用shell.cp()复制文件。r选项表示它应该递归地将文件从模板路径复制到执行命令的路径。复制文件后,我们将显示一条确认消息。而且由于shellJS函数是同步的,我们不需要使用回调、Promise或类似的东西——我们只需要以一种命令式方式编写代码。

变量替换

虽然替换文件中的变量听起来是一件复杂的事情,但是如果我们使用正确的工具,这是非常简单的。其中之一是Unix系统中的经典sed编辑器,它可以动态转换文本。ShellJS为我们提供了这个实用程序,它可以在Unix系统(Linux和MacOS)以及Windows上工作。

要进行所有替换,请在我们之前编写的代码文件中添加以下代码:

const variables = require(`${templatePath}/_variables`);

if (fs.existsSync(`${localPath}/_variables.js`)) {
  shell.rm(`${localPath}/_variables.js`);
}

logger.info('Please fill the following values…');

// Ask for variable values
prompt.start().get(variables, (err, result) => {

  // Remove MIT License file if another is selected
  // Omit this code if you have used your own template
  if (result.license !== 'MIT') {
    shell.rm(`${localPath}/LICENSE`);
  }

  // Replace variable values in all files
  shell.ls('-Rl', '.').forEach(entry => {
    if (entry.isFile()) {
      // Replace '[VARIABLE]` with the corresponding variable value from the prompt
      variables.forEach(variable => {
        shell.sed('-i', `\\[${variable.toUpperCase()}\\]`, result[variable], entry.name);
      });

      // Insert current year in files
      shell.sed('-i', '\\[YEAR\\]', new Date().getFullYear(), entry.name);
    }
  });

  logger.info(' Success!');
});

我们从读取开始,变量被设置为模板_variables的内容。我们之前创建的js文件。

结论


通过构建CLI工具来快速创建新项目,也可以根据需要扩展它。优秀开发人员的特征是创建自动化的工具。如果你发现自己在做重复性的工作,那就停下来想一想是否能让它自动化。大多数情况下,这是可能的,而且长期效益是巨大的。