前言

前端工程化是人们经常提到的货色,其目标基本上都是为了进步开发效率,降低成本以及保证质量。而脚手架工具则是前端工程化中很重要的环节,一个好用的web工程通用脚手架工具能够在很大水平上做到下面所提到的。

咱们不仅要会用市面上很多成熟的脚手架,还要能依据理论的我的项目状况,去实现一些适宜本人我的项目的脚手架。本文就将和大家一起实现一个根底的通用脚手架工具,后续就能够随便拓展了。

我的项目构造

我的项目的整体构造如下,前面咱们会一步步编写代码,最终实现整个脚手架工具。

xman-cli├─ bin│  └─ xman.js├─ command│  ├─ add.js│  ├─ delete.js│  ├─ init.js│  └─ list.js├─ lib│  ├─ remove.js│  └─ update.js├─ .gitignore├─ LICENSE├─ package.json├─ README.md└─ templates.json

具体实现

初始化我的项目

能够用 npm init 进行创立,也能够依据上面列出的 package.json 进行批改。

{  "name": "xman-cli",  "version": "1.0.0",  "description": "web通用脚手架工具",  "bin": {    "xman": "bin/xman.js"  },  "scripts": {    "test": "echo \"Error: no test specified\" && exit 1"  },  "repository": {    "type": "git",    "url": "https://github.com/XmanLin/xman-cli.git"  },  "keywords": [    "cli"  ],  "author": "xmanlin",  "license": "MIT",  "bugs": {    "url": "https://github.com/XmanLin/xman-cli/issues"  },  "homepage": "https://github.com/XmanLin/xman-cli#readme",  "dependencies": {    "chalk": "^4.1.2",    "clear": "^0.1.0",    "clui": "^0.3.6",    "commander": "^8.2.0",    "figlet": "^1.5.2",    "handlebars": "^4.7.7",    "inquirer": "^8.1.5",    "update-notifier": "^5.1.0"  }}

这里提两点:

  • bin字段:能够自定义脚手架工具的命令,例如下面的xman,而xman前面的就是命令的执行脚本。
  • 我的项目中的依赖前面会用到,用到的时候会介绍。

编写bin/xman.js

要使得脚本可执行,就须要在xman.js的最顶部增加以下代码:

#!/usr/bin/env node

编写好后引入commander(node.js命令行界面的残缺解决方案),能够点击链接或者到npm官网查看具体API的用法,前面一些列的相干依赖都一样。

#!/usr/bin/env nodeconst { program } = require('commander');

此时,咱们能够定义以后脚手架的版本以及版本查看的命令。

#!/usr/bin/env nodeconst { program } = require('commander');program    .version(require('../package').version, '-v, --version');    program.parse(process.argv); // 这里是必要的if (!program.args.length) {    program.help();}

在以后xman-cli目录下,执行 npm link 后,就能够在本地对脚手架工具进行调试了。

而后在当前目录下执行

xman -v

就能看到咱们定义的版本号了,也证实脚手架工具初步搭建胜利。

利用脚手架工具初始化搭建我的项目

这个是脚手架工具的最外围的性能点,通过脚手架工具命令疾速抉择拉取,当时在git仓库中构建好根底我的项目模板。咱们能够依据理论需要,自定义我的项目模板,并在我的项目中制订相干的开发标准和约定。

首先在git上搭建好本人的根底我的项目,这里须要留神的是:在搭建根底我的项目模板的时候,我的项目的 package.json中的 name 字段要写成上面这种模式:

{    "name": "{{name}}",}

至于为什么要这样写,前面的代码中会有体现。

而后在根目录下创立 templates.json:

{    "templates": {        "xman-manage": {            "url": "https://github.com/XmanLin/xman-manage.git",            "branch": "master"        },        "xman-web": {            "url": "https://github.com/XmanLin/xman-web.git",            "branch": "master"        }    }}

以上 xman-managexman-web 别离代表不同的我的项目,能够依据理论状况自定义,url 为根底我的项目的地址, branch为主动拉取时的分支。

接着在command文件夹(这个文件夹下会放后续一些列命令的实现逻辑)下创立init.js:

const fs = require('fs'); // node.js文件系统const exec = require('child_process').exec; // 启动一个新过程,用来执行命令const config = require('../templates'); // 引入定义好的根底我的项目列表const chalk = require('chalk'); // 给提醒语增加色调const clear = require('clear'); // 革除命令const figlet = require('figlet'); // 能够用来定制CLI执行时的头部const inquirer = require('inquirer'); // 提供交互式命令行const handlebars = require('handlebars'); // 一种简略的模板语言,能够自行百度一下const clui = require('clui'); // 提供期待的状态const Spinner = clui.Spinner;const status = new Spinner('正在下载...');const removeDir = require('../lib/remove'); // 用来删除文件和文件夹module.exports = () => {    let gitUrl;    let branch;    clear();    // 定制酷炫CLI头部    console.log(chalk.yellow(figlet.textSync('XMAN-CLI', {        horizontalLayout: 'full'    })));    inquirer.prompt([        {            name: 'templateName',            type: 'list',            message: '请抉择你须要的我的项目模板:',            choices: Object.keys(config.templates),        },        {            name: 'projectName',            type: 'input',            message: '请输出你的项目名称:',            validate: function (value) {                if (value.length) {                    return true;                } else {                    return '请输出你的项目名称';                }            },        }    ])    .then(answers => {        gitUrl = config.templates[answers.templateName].url;        branch = config.templates[answers.templateName].branch;        // 执行的命令,从git上克隆想要的我的项目模板        let cmdStr = `git clone ${gitUrl} ${answers.projectName} && cd ${answers.projectName} && git checkout ${branch}`;        status.start();        exec(cmdStr, (error, stdou, stderr) => {            status.stop();            if (error) {                console.log('产生了一个谬误:', chalk.red(JSON.stringify(error)));                process.exit();            }            const meta = {                name: answers.projectName            };            // 这里须要留神:我的项目模板的 package.json 中的 name 要写成 "name": "{{name}}"的模式            const content = fs.readFileSync(`${answers.projectName}/package.json`).toString();            // 利用handlebars.compile来进行 {{name}} 的填写             const result = handlebars.compile(content)(meta);            fs.writeFileSync(`${answers.projectName}/package.json`, result);            // 删除模板自带的 .git 文件            removeDir(`${answers.projectName}/.git`);            console.log(chalk.green('\n √ 下载实现!'));            console.log(chalk.cyan(`\n cd ${answers.projectName} && yarn \n`));            process.exit();        })    })    .catch(error => {        console.log(error);        console.log('产生了一个谬误:', chalk.red(JSON.stringify(error)));        process.exit();    });}

lib/remove.js

const fs = require('fs');let path = require('path');function removeDir(dir) {    let files = fs.readdirSync(dir); //返回一个蕴含“指定目录下所有文件名称”的数组对象    for (var i = 0; i < files.length; i++) {        let newPath = path.join(dir, files[i]);        let stat = fs.statSync(newPath); // 获取fs.Stats 对象        if (stat.isDirectory()) {            //判断是否是文件夹,如果是文件夹就递归上来            removeDir(newPath);        } else {            //删除文件            fs.unlinkSync(newPath);        }    }    fs.rmdirSync(dir); //如果文件夹是空的,就将本人删除掉};module.exports = removeDir;

最初持续在 xman.js 定义命令:

#!/usr/bin/env nodeconst { program } = require('commander');...    program    .command('init')    .description('Generate a new project')    .alias('i')    .action(() => {        require('../command/init')()    });    ...

轻易再找个文件夹下执行定义好的命令:

xman i

关上咱们下载好的模板我的项目看看:

通过命令增加我的项目模板配置

当初咱们可能通过命令拉取构建我的项目了,然而如果当前有了新的我的项目模板了怎么办?难道每次都是手动去批改 templates.json 吗。这当然是不合理的,所以接下来咱们要实现通过命令增加我的项目模板。

首先在git仓库外面新建一个我的项目模板,轻易叫什么,我这里叫 xman-mobile ,而后开始编写我的项目模板增加的逻辑和命令,新建command/add.js:

const config = require('../templates.json');const chalk = require('chalk');const fs = require('fs');const inquirer = require('inquirer');const clear = require('clear');module.exports = () => {    clear();    inquirer.prompt([        {            name: 'templateName',            type: 'input',            message: '请输出模板名称:',            validate: function (value) {                if (value.length) {                    if (config.templates[value]) {                        return '模板已存在,请从新输出';                    } else {                        return true;                    }                } else {                    return '请输出模板名称';                }            },        },        {            name: 'gitLink',            type: 'input',            message: '请输出 Git https link:',            validate: function (value) {                if (value.length) {                    return true;                } else {                    return '请输出 Git https link';                }            },        },        {            name: 'branch',            type: 'input',            message: '请输出分支名称:',            validate: function (value) {                if (value.length) {                    return true;                } else {                    return '请输出分支名称';                }            },        }    ])    .then(res => {        config.templates[res.templateName] = {};        config.templates[res.templateName]['url'] = res.gitLink.replace(/[\u0000-\u0019]/g, ''); // 过滤unicode字符        config.templates[res.templateName]['branch'] = res.branch;        fs.writeFile(__dirname + '/../templates.json', JSON.stringify(config), 'utf-8', (err) => {            if (err) {                console.log(err);            } else {                console.log(chalk.green('新模板增加胜利!\n'));            }            process.exit();        })    })    .catch(error => {        console.log(error);        console.log('产生了一个谬误:', chalk.red(JSON.stringify(error)));        process.exit();    });}

持续在bin/xman.js中增加命令

#!/usr/bin/env nodeconst { program } = require('commander');...program    .command('add')    .description('Add a new template')    .alias('a')    .action(() => {        require('../command/add')()    });    ...

执行 npm link --force ,而后再执行配置好的命令 xman a:

能够看到 templates.json 中,新的模板信息曾经被增加上了。

通过命令删除我的项目模板配置

既然有增加,那就必定有删除命令了。同样,新建command/delete.js:

const fs = require('fs');const config = require('../templates');const chalk = require('chalk');const inquirer = require('inquirer');const clear = require('clear');module.exports = () => {    clear();    inquirer.prompt([        {            name: 'templateName',            type: 'input',            message: '请输出要删除的模板名称:',            validate: function (value) {                if (value.length) {                    if (!config.templates[value]) {                        return '模板不存在,请从新输出';                    } else {                        return true;                    }                } else {                    return '请输出要删除的模板名称';                }            },        }    ])    .then(res => {        config.templates[res.templateName] = undefined;        fs.writeFile(__dirname + '/../templates.json', JSON.stringify(config), 'utf-8', (err) => {            if (err) {                console.log(err);            } else {                console.log(chalk.green('模板已删除!'));            }            process.exit();        });    })    .catch(error => {        console.log(error);        console.log('产生了一个谬误:', chalk.red(JSON.stringify(error)));        process.exit();    });}

持续增加命令:

#!/usr/bin/env nodeconst { program } = require('commander');...program    .command('delete')    .description('Delete a template')    .alias('d')    .action(() => {        require('../command/delete')()    });...

执行 npm link --force ,而后再执行配置好的命令 xman d。查看 templates.json ,咱们曾经删除了想要删除的模板信息。

通过命令疾速查看已有模板

一般来说咱们不可能记住曾经增加的所有模板,有时候须要去疾速查看。所以接下来咱们将要实现一个简略的疾速查看模板列表的命令:

新建command/list.js

const config = require('../templates');const chalk = require('chalk');module.exports = () => {    let str = '';    Object.keys(config.templates).forEach((item, index, array) => {        if (index === array.length - 1) {            str += item;        } else {            str += `${item} \n`;        }    });    console.log(chalk.cyan(str));    process.exit();}

增加命令:

#!/usr/bin/env nodeconst { program } = require('commander');...program    .command('list')    .description('show temlpate list')    .alias('l')    .action(() => {        require('../command/list')()    });...

执行 npm link --force ,而后再执行配置好的命令 xman l:

通过命令查看CLI版本是否是最新版本

一个通用的脚手架工具必定不是本人一个人用的,应用的人可能须要晓得CLI是不是有最新版本,所以也须要有查看CLI版本的性能。

新建 bin/update.js:

const updateNotifier = require('update-notifier');  // 更新CLI应用程序的告诉const chalk = require('chalk');const pkg = require('../package.json');const notifier = updateNotifier({    pkg,    updateCheckInterval: 1000 * 60 * 60, // 默认为 1000 * 60 * 60 * 24(1 天)})function updateChk() {    if (notifier.update) {        console.log(`有新版本可用:${chalk.cyan(notifier.update.latest)},建议您在应用前进行更新`);        notifier.notify();    } else {        console.log(chalk.cyan('曾经是最新版本'));    }};module.exports = updateChk;

增加命令:

#!/usr/bin/env nodeconst { program } = require('commander');...program    .command('upgrade')    .description("Check the js-plugin-cli version.")    .alias('u')    .action(() => {        updateChk();    });...

执行 npm link --force ,而后再执行配置好的命令 xman u:

到此,咱们曾经实现了一个根底但很残缺的web工程通用脚手架工具。大家能够依据本人的理论需要进行批改和拓展了。

总结

一个web工程通用脚手架的实质作用其实就是以下几点:

  • 疾速的创立根底我的项目构造;
  • 提供我的项目开发的标准和约定;
  • 依据理论我的项目需要,定制不同的性能,来进步咱们的效率。