实战
我将会使用之前自己开发的一款脚手架工具strview-cli来介绍如何实现它的。这个脚手架工具的源码地址如下:
- https://github.com/maomincoding/strview-cli
第一步
首先,我们姑且创建一个文件夹名字叫strviewCli。
- mkdir strviewCli
然后,在文件夹中初始化项目
- npm init
之后,自动生成一个package.json文件。
第二步
我们再创建一个文件夹,即bin。
- mkdir bin
接着,在文件夹中我们创建一个index.js文件、config文件夹、utils文件夹。
- touch index.js
- mkdir config
- mkdir utils
最后,在config 文件中创建一个index.js文件,在utils文件夹中创建一个checkDire.js。
- touch index.js
- touch checkDire.js
目前,我们文件目录结构为
- - bin
- -- config
- --- index.js
- -- utils
- --- checkDire.js
- -- index.js
- - package.json
最后的最后,我们在根目录下,创建一个.gitignore文件,以及README.md。
最后,命令行工具项目文件目录如下:
- - bin
- -- config
- --- index.js
- -- utils
- --- checkDire.js
- -- index.js
- - package.json
- - README.md
- - .gitignore
第三步
上面两步,我们给我们的命令行工具奠定了基本结构。下面,我们将在一一为每个文件注入灵魂~
首先,.gitignore文件和README.md,这里就不过多阐述了,可以根据自己的需要进行增添内容。
其次,详细介绍的就是package.json文件。以下是我自己写的package.json,这里需要注意几个重要的字段。
- name :项目名
- version:版本号
- description:项目描述
- main:入口文件
- author:作者
- keywords:关键词
- bin:脚本执行入口
- repository:代码仓库
- license:许可证
- private:私有
- dependencies:依赖
- browserslist:指定了项目的目标浏览器的范围
- {
- "name": "strview-cli",
- "version": "1.1.1",
- "description": "Strview.js project scaffolding tools.",
- "main": "index.js",
- "author": {
- "name": "maomincoding",
- "email": "17864296568@163.com",
- "url": "https://www.maomin.club"
- },
- "keywords": [
- "strview",
- "strview.js",
- "strview-app",
- "strview-cli"
- ],
- "bin": {
- "strview-cli": "./bin/index.js"
- },
- "repository": {
- "type": "git",
- "url": "https://github.com/maomincoding/strview-cli.git"
- },
- "license": "MIT",
- "private": false,
- "dependencies": {
- "chalk": "^4.0.0",
- "commander": "^5.0.0",
- "fs-extra": "^9.0.0",
- "inquirer": "^7.1.0"
- },
- "browserslist": [
- "> 1%",
- "last 2 versions"
- ]
- }
上面的package.json中几个属性如:name、version、description、main、author、keywords、repository、license可以根据自己的需求来定义。
你可能会看到dependencies属性中有几个依赖,分别是chalk、commander、fs-extra、inquirer。这里先提一下,下面会详细介绍它们。不过,需要注意的是fs-extra模块是添加了本机fs模块中不包含的文件系统方法,并向fs方法添加了promise支持。它还使用优美的fs来防止EMFILE错误。它应该是fs的替代品。
第四步
接下来,我们将进入bin文件夹中,然后,我们首先需要编辑config\index.js文件。
- module.exports = {
- npmUrl: 'https://registry.npmjs.org/strview-cli',
- promptTypeList: [
- {
- type: 'list',
- message: 'Please select the template type to pull:',
- name: 'type',
- choices: [
- {
- name: 'strview-app',
- value: {
- url: 'https://github.com/maomincoding/strview-app.git',
- gitName: 'strview-app',
- val: 'strview-app',
- },
- },
- ],
- },
- ],
- };
以上代码中导出一个对象,对象中有两个属性:npmUrl和promptTypeList。
npmUrl属性是命令行工具提交到NPM的地址。怎么得到这个地址呢?你需要按照下面步骤:
登录NPM
- npm login
依次输入你的用户名、密码、邮箱。
发布到NPM
- npm publish
发布成功后,会显示版本号。记住,每次发布都要更改版本号,否则会出错。
正常发布之后,你可以打开NPM网址,搜索你的命令行工具的名称。比如我的命令行工具strview-cli。网址即:https://registry.npmjs.org/strview-cli。
promptTypeList属性中有一个choices属性,它是一个数组,你可以配置你远程项目工程仓库。数组每个元素是一个对象,比如这里的
- {
- name: 'strview-app',
- value: {
- url: 'https://github.com/maomincoding/strview-app.git',
- gitName: 'strview-app',
- val: 'strview-app',
- },
- }
name属性是你的项目工程名称,value属性又是一个对象,里面有三个属性:url、gitName、val分别表示远程仓库地址、仓库名、值(一般与仓库名一致)。你可以根据你的需要进行配置,这里是我配置的自己的strview-app。
以上就是config\index.js文件的配置。
第五步
下面,我们还在bin文件夹中,我们接下来编辑utils\checkDire.js文件。
- const fs = require("fs");
- const chalk = require("chalk");
-
- module.exports = function (dir, name) {
- let isExists = fs.existsSync(dir);
- if (isExists) {
- console.log(
- chalk.red(
- `The ${name} project already exists in directory. Please try to use another projectName`
- )
- );
- process.exit(1);
- }
- };
这个文件没有自定义的部分,你只需要直接用即可。这里我们看到引入两个模块,分别是fs、chalk。Node.js内置的fs模块就是文件系统模块,负责读写文件。chalk模块是美化命令行输出样式,使输出命令不再单调。
我们看到这里导出一个函数,函数有两个参数:分别是dir和name。我们这里先暂且不看这个函数,先只知道需要传两个参数就可以。
第六步
下面我们先分析bin\index.js文件,这个文件是命令行工具的入口文件,非常重要。同样,这里不需要自定义,直接用就可以。
- #!/usr/bin/env node
-
- const fs = require('fs');
- const path = require('path');
- const chalk = require('chalk');
- const commander = require('commander');
- const inquirer = require('inquirer');
- const checkDire = require('./utils/checkDire.js');
- const { exec } = require('child_process');
- const { version } = require('../package.json');
- const { promptTypeList } = require('./config');
-
- commander
- .version(version, '-v, --version')
- .command('init
' ) - .alias('i')
- .description('Enter the project name and initialize the project template')
- .action(async (projectName) => {
- await checkDire(path.join(process.cwd(), projectName), projectName);
- inquirer.prompt(promptTypeList).then((result) => {
- const { url, gitName, val } = result.type;
- console.log(
- 'The template type information you selected is as follows:' + val
- );
- console.log('Project initialization copy acquisition...');
- if (!url) {
- console.log(
- chalk.red(`${val} This type is not supported at the moment...`)
- );
- process.exit(1);
- }
- exec('git clone ' + url, function (error, stdout, stderr) {
- if (error !== null) {
- console.log(chalk.red(`clone fail,${error}`));
- return;
- }
- fs.rename(gitName, projectName, (err) => {
- if (err) {
- exec('rm -rf ' + gitName, function (err, out) { });
- console.log(
- chalk.red(`The ${projectName} project template already exist`)
- );
- } else {
- if (fs.existsSync(`./${projectName}/.git/config`)) {
- exec('git remote rm origin', { cwd: `./${projectName}` });
- console.log(
- chalk.green(
- `✔ The ${projectName} project template successfully create`
- )
- );
- }
- }
- });
- });
- });
- });
-
- commander.parse(process.argv);
我们从头开始看,我们会看到引入了fs、path、chalk、commander、inquirer、child_process。
path 模块提供了用于处理文件和目录的路径的实用工具。
commander是完整的 node.js 命令行解决方案,它有很多用法,具体你可以参照Commander中文文档:
- https://github.com/tj/commander.js/blob/master/Readme_zh-CN.md
inquirer是通用交互式命令行用户界面的集合。开始通过npm init创建package.json文件的时候就有大量与用户的交互,而现在大多数工程都是通过脚手架来创建的,使用脚手架的时候最明显的就是与命令行的交互,如果想自己做一个脚手架或者在某些时候要与用户进行交互,这个时候就不得不提到inquirer.js了。
child_process模块是nodejs的一个子进程模块,可以用来创建一个子进程,并执行一些任务。比如说就可以直接在js里面调用shell命令。
介绍完引入的模块,然后再介绍下面的代码。你会看到下面的代码大部分都用到了commander的方法。
首先,commander.version(version, '-v, --version'),.version()方法可以设置版本,之后就可以使用-v或--version命令查看版本。
通过 .command('init
.description('Enter the project name and initialize the project template')这行代码中.description方法则是对上面初始化配置项目名的描述。
.action((projectName, cmd) => {...})这行代码中.action方法是定义命令的回调函数,我们发现它使用了async/await这组关键字用来处理异步。首先,await checkDire(path.join(process.cwd(), projectName), projectName);传入两个参数分别为项目所在的目录、项目名称。我们这里先分析checkDire方法,也就是之前utils\checkDire.js文件内的方法。
- module.exports = function (dir, name) {
- let isExists = fs.existsSync(dir);
- if (isExists) {
- console.log(
- chalk.red(
- `The ${name} project already exists in directory. Please try to use another projectName`
- )
- );
- process.exit(1);
- }
- };
fs.existsSync(dir)以同步的方法检测目录是否存在。如果目录存在 返回 true ,如果目录不存在 返回false。如果存在了,就执行下面的提示并退出终止进程。
接着,我们又回到bin\index.js文件。接着往下执行,到了inquirer.prompt()这个方法,这个方法的作用主要是启动提示界面(查询会话),第一个参数是包含问题对象的问题(数组)(使用反应式接口,还可以传递一个Rx.Observable实例),这里我们传入的是config\index.js文件中的promptTypeList属性,它是一个数组。
- promptTypeList: [
- {
- type: 'list',
- message: 'Please select the template type to pull:',
- name: 'type',
- choices: [
- {
- name: 'strview-app',
- value: {
- url: 'https://github.com/maomincoding/strview-app.git',
- gitName: 'strview-app',
- val: 'strview-app',
- },
- },
- ],
- },
- ],
inquirer.prompt()这个方法返回一个Promise对象,所以这里可以then()方法来获取返回的数据。然后我们通过解构来分别获取到url、gitName、val这三个属性值。
- const { url, gitName, val } = result.type;
然后,下面就是输出命令以及执行命令的环节了。我分为两部分来分析剩余代码,第一部分如下:
- console.log('The template type information you selected is as follows:' + val);
- console.log('Project initialization copy acquisition...');
-
- if (!url) {
- console.log(chalk.red(`${val} This type is not supported at the moment...`));
- process.exit(1);
- }
我们这里有一个判断语句,就是判断远程仓库地址是否是假值,如果是假值的话,就执行提示并退出终止进程。
接着,我们分析第二部分:
- exec('git clone ' + url, function (error, stdout, stderr) {
- if (error !== null) {
- console.log(chalk.red(`clone fail,${error}`));
- return;
- }
- fs.rename(gitName, projectName, (err) => {
- if (err) {
- exec('rm -rf ' + gitName, function (err, out) { });
- console.log(chalk.red(`The ${projectName} project template already exist`));
- } else {
- if (fs.existsSync(`./${projectName}/.git/config`)) {
- exec('git remote rm origin', { cwd: `./${projectName}` });
- console.log(
- chalk.green(`✔ The ${projectName} project template successfully create`)
- );
- }
- }
- });
- }
这部分主要是执行命令,也是最关键的部分。这部分首先使用了exec()方法,第一个参数是要执行的命令,第二个参数是回调函数。
首先,我们执行exec('git clone ' + url)来下载远程项目,接着我们进入回调函数,如果有错误,就输出提示并退出。否则,将使用fs.rename()方法将文件重命名。因为我们下载的远程仓库名不一定跟我们初始化配置的名字一样。
如果错误,就删除远程项目工程目录。否则,就判断./${projectName}/.git/config文件是否存在,如果存在就执行exec('git remote rm origin', { cwd:./${projectName}});命令删除远程仓库地址。这是因为需要自定义配置仓库地址,而不是直接使用下载的仓库地址。最后,提示创建成功。
最后一行。commander.parse(process.argv);这行代码中.parse()的第一个参数是要解析的字符串数组,也可以省略参数而使用process.argv。指明,按 node 约定。
第七步
到了这一步,所有配置文件都配置完成了。如果你想开源的话,你可以参照线上自己生成一个LICENSE文件。这个文件是软件许可证,可以去github去自动生成这个文件。
最后,我们就要发布我们这个命令行工具了。注意,在发布之前,需要改一下你的版本号。 如之前是1.0.0,现在可以改成2.0.0。具体这三个数字怎么定义,也有说法。第一部分为主版本号,变化了表示有了一个不兼容上个版本的大更改。第二部分为次版本号,变化了表示增加了新功能,并且可以向后兼容。第三部分为修订版本号,变化了表示有bug修复,并且可以向后兼容。
- npm publish
发布成功。
第八步
这里以strview-cli为例。
你可以全局安装你的脚手架。
- npm i strview-cli -g
安装完成之后,你可以查看版本。
- strview-cli -v
最后,就是初始化项目了,
- strview-cli init
or
- strview-cli i
结语
谢谢阅读,希望没有耽误你的时间。
你可以自己封装一个常用的项目工程,可以通过这种方式来初始化你的项目。这样,才会显得有逼格!!!哈哈哈~
本文转载自微信公众号「前端历劫之路」,可以通过以下二维码关注。转载本文请联系前端历劫之路公众号。