开始一个新项目(尤其是作为JavaScript开发人员)通常是一个重复而乏味的过程。 对于每个新项目,我们通常需要添加一个package.json
文件,引入一些标准依赖项,对其进行配置,创建正确的目录结构,添加各种其他文件……清单不胜枚举。
但是我们是懒惰的开发人员,对吗? 幸运的是,我们可以实现此自动化。 它不需要任何特殊的工具或奇怪的语言-如果您已经知道JavaScript,那么过程实际上很简单。
在本教程中,我们将使用Node.js构建跨平台的命令行界面(CLI)。 这将使我们能够使用一组预定义的模板快速搭建新项目。 它是完全可扩展的,因此您可以轻松地使其适应自己的需求,并使工作流程中繁琐的部分自动化。
为什么要自己动手?
尽管有很多类似的工具可以完成此任务(例如Yeoman ),但通过建立自己的工具,我们可以获得知识,经验并可以完全自定义。 您应该始终考虑在使用现有工具之前创建工具的想法,尤其是在您试图解决特殊问题时。 这听起来可能与总是重复使用软件的惯常做法相反,但是在某些情况下,实施自己的工具可能会非常有益。 获得知识总是有帮助的,但是您也可以提出高度个性化和高效的工具,专门针对您的需求量身定制。
如此说来,我们将不会完全重新发明轮子。 CLI本身将使用名为Caporal.js的库构建 。 在内部,它还将使用提示来询问用户数据和shellJS ,这将为我们在Node.js环境中提供一些Unix工具。 我之所以选择这些库,主要是因为它们易于使用,但是在完成本教程后,您将可以将它们换成最适合您需求的替代品。
与以往一样,您可以在Github上找到完成的项目: https : //github.com/sitepoint-editors/node-scaffolding-tool
现在开始吧...
使用Caporal.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"
}
}
这已经包括我们需要的一切。 现在要安装软件包,请执行npm install
,所有标记的依赖项将在我们的项目中可用。 这些软件包的版本是在撰写本文时的最新版本。 如果同时有较新版本可用,则您可以考虑对其进行更新(注意任何API更改)。
注意bin
的scaffold
值。 它指示了命令的名称以及每次我们在终端( index.js
)中输入该命令时将要调用的文件。 随时根据需要更改此值。
建立入口点
CLI的第一个组件是index.js
文件,其中包含命令,选项和将要提供给我们的各个功能的列表。 但是,在编写此文件之前,让我们首先详细定义CLI的工作方式。
- 主要(也是唯一的)命令是
create
,它使我们能够创建所选的项目样板。 -
create
命令采用强制性template
参数,该参数指示我们要使用的模板。 - 它还带有
--variant
选项,该选项允许我们选择模板的特定版本。 - 如果没有提供特定的变体,它将使用默认变体(我们将在以后定义)。
Caporal.js允许我们以紧凑的方式定义以上内容。 让我们将以下内容添加到我们的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);
第一行是一个Shebang ,表示这是一个Node.js可执行文件。
这里包含的shebang仅适用于类Unix的系统。 Windows不支持shebang,因此,如果您想直接在Windows上执行文件,则必须寻找解决方法。 通过npm(在本节末尾说明)运行命令将在所有平台上运行。
接下来,我们将Caporal.js
包包含在prog
然后开始定义程序。 使用command函数 ,我们将create
命令定义为第一个参数,并将一些描述作为第二个参数。 这将显示在CLI的自动生成的帮助选项中(使用--help
)。
然后,我们将template
参数链接到参数函数中 ,并且由于它是必需的参数,因此将其包装在尖括号( <
和>
)中。
我们可以通过在option函数中编写--variant <variant>
来定义variant选项。 这意味着,我们的命令选项称为--variant
和值将被存储在一个variant
变量。
最后,在action命令中,我们传递了另一个函数来处理当前命令。 将使用三个参数调用此回调:
- 传递的参数(
args
) - 传递的选项(
options
) - 在屏幕上显示事物的实用程序对象(
logger
)。
此时,我们将注销传递的参数和选项的值,因此我们可以了解如何获取必要的信息以从CLI执行操作。
最后一行将信息从scaffold
命令传递到Caporal.js解析器,该解析器将完成繁重的工作。
使CLI全局可用
现在,我们可以测试我们的应用程序,看一切是否按计划进行。 为此,我们需要使用npm's link命令使它在系统中全局可用。 从项目根目录执行以下命令:
npm link
该过程完成后,我们将能够在终端中的任何目录内执行scaffold
,而无需显式引用我们的index.js
文件:
scaffold create node --variant mvc
您应该得到以下响应:
{ args: { template: 'node' }, options: { variant: 'mvc' } }
这是信息的样本,我们接下来将使用这些信息从模板创建项目。
建立范本
我们的模板将包含启动和运行某种类型项目所需的文件和目录结构。 每个模板都会有一个package.json
文件,其中包含一些占位符值,我们可以用真实数据填充这些文件。
首先,在项目中创建一个templates
目录,并在其中创建一个node
目录。 在node
目录中,创建一个default
目录(如果我们不提供一个variant
选项,将使用该default
目录)和另一个名为mvc
目录(以使用MVC架构创建Node.js项目)。
最终结构应如下所示:
.
└── templates
└── node
├── default
└── mvc
现在,我们需要使用项目文件填充default
文件夹和mvc
文件夹。 您可以创建自己的应用程序 ,也可以使用示例应用程序中提供的应用程序 。
接下来,我们可以将变量标识符放在需要动态值的位置。 每个模板文件夹应包含一个package.json
文件。 打开这些文件,并在所有变量中使用大写字母(无空格)和方括号。
这是我们默认模板中的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
命令构建函数,以完成所有工作。
建立“创建”功能
在我们的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
包含对执行命令的目录的引用。
复制模板文件
使用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);
}
首先,我们确保模板存在,否则,我们将使用logger.error()
函数退出显示错误消息的过程。 如果模板存在,我们将使用显示通知消息logger.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
设置为我们先前创建的模板_variables.js
文件的内容。
然后,因为我们已经从模板复制了所有文件,所以第一条if
语句将从我们的本地目录中删除_variables.js
文件,因为仅CLI本身才需要它。
使用提示工具获取每个变量的值,并将变量数组传递给get()
函数。 这样,CLI将询问我们该数组中每个项目的值,并将结果保存在一个名为result
的对象中,该对象将传递给回调函数。 该对象包含每个变量作为键,并包含输入的文本作为值。
仅当您使用存储库中包含的模板时,才需要下一个if
语句,因为我们还包含一个LICENSE文件。 尽管如此,查看如何获取每个变量的值还是很有用的,在这种情况下,可以使用result.license
从license
属性获取值。 如果用户输入的许可证不是MIT
,那么我们将使用ShellJS的rm()
函数从目录中删除LICENSE
文件。
现在我们进入有趣的部分。 通过使用ShellJS中的ls函数 ,我们可以获得要替换变量的当前目录( .
)中所有文件的列表。 我们将-Rl
选项传递给它,因此它变为递归并返回文件对象而不是文件名。
我们使用forEach()
遍历文件对象列表,对于每个对象,我们使用isFile()
函数检查是否接收到文件。 如果得到目录,则不执行任何操作。
然后对于得到的每个文件,我们遍历所有变量并执行sed
函数,如下所示:
shell.sed('-i', `\\[${variable.toUpperCase()}\\]`, result[variable], entry.name);
在这里,我们传递了-i
选项,该选项允许我们替换文本,然后传递一个正则表达式字符串,该字符串将匹配variable
标识符为大写并括在方括号( [
和]
)中。 然后,该正则表达式的每个匹配项将被相应变量( result[variable]
)的值替换,最后我们从forEach()
函数( entry.name
)传递要替换的文件的名称。
第二个sed
是完全可选的。 这只是用当年替换[YEAR]
事件。 对于LICENSE
或README.md
文件很有用。
就是这样! 现在,我们可以在一个空目录中再次执行命令,以查看其如何生成项目结构并将所有变量替换为新值:
// To generate a Node.js MVC project
scaffold create node --variant mvc
// To generate a default Node.js project
scaffold create node
执行完命令后,它应该开始询问您变量的值,并且一旦过程完成,它将显示一条成功消息。 要检查一切是否按预期进行,请打开一个包含变量的文件,您应该看到在CLI进程中输入的文本,而不是大写的标识符。
如果您已使用[回购]中的模板(https://github.com/sitepoint-editors/node-scaffolding-tool
)进行后续操作,您还应该生成了可运行的Node项目,可以通过运行npm install
和npm start
。
接下来做什么
我们已经成功创建了一个CLI工具,可以从模板创建新的Node.js项目,但是我们不必在此停止。 因为我们是从头开始构建我们的工具,所以我们在工具上有绝对的自由。 您可以从以下想法中汲取灵感:
- 扩展变量以替换代码块而不是简单的单词; 您可以在
sed
函数中使用更复杂的正则表达式和捕获组来实现此目的。 - 添加更多命令以为每种项目创建特定文件,例如MVC模板的新模型。
- 包括将项目部署到服务器的命令,这可以通过将库用于rsync和通过SSH的远程命令来实现。
- 如果设置复杂,还可以尝试添加命令以构建静态资产或源文件,这在静态站点的情况下可能很有用。
- 使用
mv
函数从变量名重命名文件。
结论
在本教程中,我演示了如何构建CLI以在熟悉的环境中快速启动新项目。 但这不是一次性项目,您可以根据需要扩展它。 自动化工具的创建是开发人员的特征。 如果发现自己执行重复性任务,请停下来思考是否可以自动化。 在大多数情况下,这是可能的,并且长期利益可能是巨大的。
现在结束了吗? 您是否喜欢自动执行重复繁琐的工作? 您选择什么工具包? 在下面的评论中让我知道。
Joan Yin , Camilo Reyes和Tim Severien对本文进行了同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!
From: https://www.sitepoint.com/scaffolding-tool-caporal-js/