<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          手把手從0到1實(shí)現(xiàn)一個(gè)web工程通用腳手架工具

          共 11077字,需瀏覽 23分鐘

           ·

          2021-10-19 20:01

          前言

          前端工程化是人們常常提到的東西,其目的基本上都是為了提高開發(fā)效率,降低成本以及保證質(zhì)量。而腳手架工具則是前端工程化中很重要的環(huán)節(jié),一個(gè)好用的web工程通用腳手架工具可以在很大程度上做到上面所提到的。

          我們不僅要會用市面上很多成熟的腳手架,還要能根據(jù)實(shí)際的項(xiàng)目情況,去實(shí)現(xiàn)一些適合自己項(xiàng)目的腳手架。本文就將和大家一起實(shí)現(xiàn)一個(gè)基礎(chǔ)的通用腳手架工具,后續(xù)就可以隨意拓展了。

          項(xiàng)目結(jié)構(gòu)

          項(xiàng)目的整體結(jié)構(gòu)如下,后面我們會一步步編寫代碼,最終實(shí)現(xiàn)整個(gè)腳手架工具。

          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

          具體實(shí)現(xiàn)

          初始化項(xiàng)目

          可以用 npm init 進(jìn)行創(chuàng)建,也可以根據(jù)下面列出的 package.json 進(jìn)行修改。

          {
          ??"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"
          ??}
          }

          這里提兩點(diǎn):

          • bin字段:可以自定義腳手架工具的命令,例如上面的xman,而xman后面的就是命令的執(zhí)行腳本。
          • 項(xiàng)目中的依賴后面會用到,用到的時(shí)候會介紹。

          編寫bin/xman.js

          要使得腳本可執(zhí)行,就需要在xman.js的最頂部添加以下代碼:

          #!/usr/bin/env?node

          編寫好后引入commander(node.js命令行界面的完整解決方案),可以點(diǎn)擊鏈接或者到npm官網(wǎng)查看具體API的用法,后面一些列的相關(guān)依賴都一樣。

          #!/usr/bin/env?node

          const?{?program?}?=?require('commander');

          此時(shí),我們可以定義當(dāng)前腳手架的版本以及版本查看的命令。

          #!/usr/bin/env?node

          const?{?program?}?=?require('commander');

          program
          ?.version(require('../package').version,?'-v,?--version');
          ?
          program.parse(process.argv);?//?這里是必要的

          if?(!program.args.length)?{
          ?program.help();
          }

          在當(dāng)前xman-cli目錄下,執(zhí)行 npm link 后,就可以在本地對腳手架工具進(jìn)行調(diào)試了。

          然后在當(dāng)前目錄下執(zhí)行

          xman?-v

          就能看到我們定義的版本號了,也證明腳手架工具初步搭建成功。


          利用腳手架工具初始化搭建項(xiàng)目

          這個(gè)是腳手架工具的最核心的功能點(diǎn),通過腳手架工具命令快速選擇拉取,事先在git倉庫中構(gòu)建好基礎(chǔ)項(xiàng)目模板。我們可以根據(jù)實(shí)際需求,自定義項(xiàng)目模板,并在項(xiàng)目中制定相關(guān)的開發(fā)規(guī)范和約定。

          首先在git上搭建好自己的基礎(chǔ)項(xiàng)目,這里需要注意的是:在搭建基礎(chǔ)項(xiàng)目模板的時(shí)候,項(xiàng)目的 package.json中的 name 字段要寫成下面這種形式:

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

          至于為什么要這樣寫,后面的代碼中會有體現(xiàn)。

          然后在根目錄下創(chuàng)建 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 分別代表不同的項(xiàng)目,可以根據(jù)實(shí)際情況自定義,url 為基礎(chǔ)項(xiàng)目的地址, branch為自動拉取時(shí)的分支。

          接著在command文件夾(這個(gè)文件夾下會放后續(xù)一些列命令的實(shí)現(xiàn)邏輯)下創(chuàng)建init.js:

          const?fs?=?require('fs');?//?node.js文件系統(tǒng)
          const?exec?=?require('child_process').exec;?//?啟動一個(gè)新進(jìn)程,用來執(zhí)行命令
          const?config?=?require('../templates');?//?引入定義好的基礎(chǔ)項(xiàng)目列表
          const?chalk?=?require('chalk');?//?給提示語添加色彩
          const?clear?=?require('clear');?//?清除命令
          const?figlet?=?require('figlet');?//?可以用來定制CLI執(zhí)行時(shí)的頭部
          const?inquirer?=?require('inquirer');?//?提供交互式命令行
          const?handlebars?=?require('handlebars');?//?一種簡單的模板語言,可以自行百度一下
          const?clui?=?require('clui');?//?提供等待的狀態(tài)
          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:?'請選擇你需要的項(xiàng)目模板:',
          ????????????choices:?Object.keys(config.templates),
          ????????},
          ????????{
          ????????????name:?'projectName',
          ????????????type:?'input',
          ????????????message:?'請輸入你的項(xiàng)目名稱:',
          ????????????validate:?function?(value)?{
          ????????????????if?(value.length)?{
          ????????????????????return?true;
          ????????????????}?else?{
          ????????????????????return?'請輸入你的項(xiàng)目名稱';
          ????????????????}
          ????????????},
          ????????}
          ????])
          ????.then(answers?=>?{
          ????????gitUrl?=?config.templates[answers.templateName].url;
          ????????branch?=?config.templates[answers.templateName].branch;
          ????????//?執(zhí)行的命令,從git上克隆想要的項(xiàng)目模板
          ????????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('發(fā)生了一個(gè)錯(cuò)誤:',?chalk.red(JSON.stringify(error)));
          ????????????????process.exit();
          ????????????}
          ????????????const?meta?=?{
          ????????????????name:?answers.projectName
          ????????????};
          ????????????//?這里需要注意:項(xiàng)目模板的 package.json 中的 name 要寫成?"name":?"{{name}}"的形式
          ????????????const?content?=?fs.readFileSync(`${answers.projectName}/package.json`).toString();
          ????????????//?利用handlebars.compile來進(jìn)行?{{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('發(fā)生了一個(gè)錯(cuò)誤:',?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);?//返回一個(gè)包含“指定目錄下所有文件名稱”的數(shù)組對象
          ????for?(var?i?=?0;?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;

          最后繼續(xù)在 xman.js 定義命令:

          #!/usr/bin/env?node

          const?{?program?}?=?require('commander');

          ...
          ?
          program
          ?.command('init')
          ?.description('Generate?a?new?project')
          ?.alias('i')
          ?.action(()?=>?{
          ??require('../command/init')()
          ?});
          ?

          ...

          隨便再找個(gè)文件夾下執(zhí)行定義好的命令:

          xman?i
          初始化項(xiàng)目.gif

          打開我們下載好的模板項(xiàng)目看看:

          初始化后的demo.png

          通過命令添加項(xiàng)目模板配置

          現(xiàn)在我們能夠通過命令拉取構(gòu)建項(xiàng)目了,但是如果以后有了新的項(xiàng)目模板了怎么辦?難道每次都是手動去修改 templates.json 嗎。這當(dāng)然是不合理的,所以接下來我們要實(shí)現(xiàn)通過命令添加項(xiàng)目模板。

          首先在git倉庫里面新建一個(gè)項(xiàng)目模板,隨便叫什么,我這里叫 xman-mobile ,然后開始編寫項(xiàng)目模板添加的邏輯和命令,新建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('發(fā)生了一個(gè)錯(cuò)誤:',?chalk.red(JSON.stringify(error)));
          ????????process.exit();
          ????});
          }

          繼續(xù)在bin/xman.js中添加命令

          #!/usr/bin/env?node

          const?{?program?}?=?require('commander');

          ...

          program
          ?.command('add')
          ?.description('Add?a?new?template')
          ?.alias('a')
          ?.action(()?=>?{
          ??require('../command/add')()
          ?});
          ?
          ...

          執(zhí)行 npm link --force ,然后再執(zhí)行配置好的命令 xman a:

          添加模板.gif

          可以看到 templates.json 中,新的模板信息已經(jīng)被添加上了。

          通過命令刪除項(xiàng)目模板配置

          既然有添加,那就肯定有刪除命令了。同樣,新建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('發(fā)生了一個(gè)錯(cuò)誤:',?chalk.red(JSON.stringify(error)));
          ????????process.exit();
          ????});
          }

          繼續(xù)添加命令:

          #!/usr/bin/env?node

          const?{?program?}?=?require('commander');

          ...

          program
          ?.command('delete')
          ?.description('Delete?a?template')
          ?.alias('d')
          ?.action(()?=>?{
          ??require('../command/delete')()
          ?});

          ...

          執(zhí)行 npm link --force ,然后再執(zhí)行配置好的命令 xman d。查看 ?templates.json ,我們已經(jīng)刪除了想要刪除的模板信息。

          通過命令快速查看已有模板

          一般來說我們不可能記住已經(jīng)添加的所有模板,有時(shí)候需要去快速查看。所以接下來我們將要實(shí)現(xiàn)一個(gè)簡單的快速查看模板列表的命令:

          新建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?node

          const?{?program?}?=?require('commander');

          ...

          program
          ?.command('list')
          ?.description('show?temlpate?list')
          ?.alias('l')
          ?.action(()?=>?{
          ??require('../command/list')()
          ?});

          ...

          執(zhí)行 npm link --force ,然后再執(zhí)行配置好的命令 xman l:

          查看模板列表.gif

          通過命令檢查CLI版本是否是最新版本

          一個(gè)通用的腳手架工具肯定不是自己一個(gè)人用的,使用的人可能需要知道CLI是不是有最新版本,所以也需要有檢查CLI版本的功能。

          新建 bin/update.js:

          const?updateNotifier?=?require('update-notifier');??//?更新CLI應(yīng)用程序的通知
          const?chalk?=?require('chalk');
          const?pkg?=?require('../package.json');

          const?notifier?=?updateNotifier({
          ????pkg,
          ????updateCheckInterval:?1000?*?60?*?60,?//?默認(rèn)為?1000?*?60?*?60?*?24(1?天)
          })

          function?updateChk()?{
          ????if?(notifier.update)?{
          ????????console.log(`有新版本可用:${chalk.cyan(notifier.update.latest)},建議您在使用前進(jìn)行更新`);
          ????????notifier.notify();
          ????}?else?{
          ????????console.log(chalk.cyan('已經(jīng)是最新版本'));
          ????}
          };

          module.exports?=?updateChk;

          添加命令:

          #!/usr/bin/env?node

          const?{?program?}?=?require('commander');

          ...

          program
          ?.command('upgrade')
          ?.description("Check?the?js-plugin-cli?version.")
          ?.alias('u')
          ?.action(()?=>?{
          ??updateChk();
          ?});

          ...

          執(zhí)行 npm link --force ,然后再執(zhí)行配置好的命令 xman u:

          檢查版本.gif

          到此,我們已經(jīng)實(shí)現(xiàn)了一個(gè)基礎(chǔ)但很完整的web工程通用腳手架工具。大家可以根據(jù)自己的實(shí)際需求進(jìn)行修改和拓展了。

          總結(jié)

          一個(gè)web工程通用腳手架的本質(zhì)作用其實(shí)就是以下幾點(diǎn):

          • 快速的創(chuàng)建基礎(chǔ)項(xiàng)目結(jié)構(gòu);
          • 提供項(xiàng)目開發(fā)的規(guī)范和約定;
          • 根據(jù)實(shí)際項(xiàng)目需求,定制不同的功能,來提高我們的效率。


          瀏覽 47
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品成人精品久久久 | 久久午夜成人电影 | 亚洲成人免费高清无码 | 夜夜久久| 做爰 视频毛片下载蜜桃 |