手写一个合格的前端脚手架
為什么我們需要一套腳手架
為什么我們需要一套腳手架,它能幫助我們解決哪些痛點(diǎn)問題。
?前端項(xiàng)目配置越來越繁瑣、耗時(shí),重復(fù)無意義的工作?項(xiàng)目結(jié)構(gòu)不統(tǒng)一、不規(guī)范?前端項(xiàng)目類型繁多,不同項(xiàng)目不同配置,管理成本高?腳手架也可以是一套命令集,不只用來創(chuàng)建項(xiàng)目
那么為什么不用一些開源框架自身的 CLI 工具,需要自己開發(fā)呢,這里仁者見仁智者見智,我個(gè)人建議就是對于中型團(tuán)隊(duì)以上需要自己維護(hù)一套腳手架,因?yàn)榭煽匦愿?#xff0c;能滿足團(tuán)隊(duì)特定需求的研發(fā)。
如何按照開源要求開發(fā)一個(gè)前端腳手架?
下面是我們常見的前端開源目錄結(jié)構(gòu)
腳手架的設(shè)計(jì)
思路
?解耦:腳手架與模板分離?腳手架負(fù)責(zé)構(gòu)建流程,通過命令行與用戶交互,獲取項(xiàng)目信息?模板負(fù)責(zé)統(tǒng)一項(xiàng)目結(jié)構(gòu)、工作流程、依賴項(xiàng)管理?腳手架需要檢測模板的版本是否有更新,支持模板的刪除與新建?……
流程圖
代碼講解
目錄結(jié)構(gòu)
配置 Git hook
首先進(jìn)行開發(fā)前的準(zhǔn)備工作,來保證你代碼的質(zhì)量。
Husky + Lint-staged
通過 Git hook 完成 commitlint、ESLint、prettiter 等,具體配置我后面會給源碼,有興趣的可以自己搜索下。
// package.json "husky": {"hooks": {"pre-commit": "lint-staged","commit-msg": "commitlint -E HUSKY_GIT_PARAMS"}},"lint-staged": {"**/*.js": ["eslint --fix","prettier --write","git add"]}package.json 下的 bin 字段
bin:配置內(nèi)部命令對應(yīng)的可執(zhí)行文件位置,配置命令后,npm 會尋找到對應(yīng)的可執(zhí)行文件,然后在 node_modules/.bin 目錄下建立對應(yīng)的符號鏈接。
由于 node_modules/.bin 會在運(yùn)行時(shí)候加入到系統(tǒng)的環(huán)境變量,因此我們可以通過 npm 調(diào)用命令來執(zhí)行腳本。
所有 node_modules/.bin 目錄下的命令都可以通過 npm run [命令] 執(zhí)行。
所以我們需要在 package.json 配置入口
"bin": {"easy": "bin/easy.js"}npm link 本地調(diào)試
這里介紹下開發(fā)腳手架的調(diào)試方法。npm link 官網(wǎng)使用介紹。使用方法:
// cd 到你項(xiàng)目的bin目錄(腳本)下 $ npm link去掉 link 也非常方便:
npm unlink linknamebin 目錄下的入口文件
#!/usr/bin/env nodeconst program = require('commander'); // 命令行工具 const chalk = require('chalk'); // 命令行輸出美化 const didYouMean = require('didyoumean'); // 簡易的智能匹配引擎 const semver = require('semver'); // npm的語義版本包 const enhanceErrorMessages = require('../lib/util/enhanceErrorMessages.js'); const requiredNodeVersion = require('../package.json').engines.node;didYouMean.threshold = 0.6;function checkNodeVersion(wanted, cliName) {// 檢測node版本是否符合要求范圍if (!semver.satisfies(process.version, wanted)) {console.log(chalk.red('You are using Node ' +process.version +', but this version of ' +cliName +' requires Node ' +wanted +'.\nPlease upgrade your Node version.'));// 退出進(jìn)程process.exit(1);} }// 檢測node版本 checkNodeVersion(requiredNodeVersion, '@easy/cli');program.version(require('../package').version, '-v, --version') // 版本.usage('<command> [options]'); // 使用信息// 初始化項(xiàng)目模板 program.command('create <template-name> <project-name>').description('create a new project from a template').action((templateName, projectName, cmd) => {// 輸入?yún)?shù)校驗(yàn)validateArgsLen(process.argv.length, 5);require('../lib/easy-create')(lowercase(templateName), projectName);});// 添加一個(gè)項(xiàng)目模板 program.command('add <template-name> <git-repo-address>').description('add a project template').action((templateName, gitRepoAddress, cmd) => {validateArgsLen(process.argv.length, 5);require('../lib/add-template')(lowercase(templateName), gitRepoAddress);});// 列出支持的項(xiàng)目模板 program.command('list').description('list all available project template').action(cmd => {validateArgsLen(process.argv.length, 3);require('../lib/list-template')();});// 刪除一個(gè)項(xiàng)目模板 program.command('delete <template-name>').description('delete a project template').action((templateName, cmd) => {validateArgsLen(process.argv.length, 4);require('../lib/delete-template')(templateName);});// 處理非法命令 program.arguments('<command>').action(cmd => {// 不退出輸出幫助信息program.outputHelp();console.log(` ` + chalk.red(`Unknown command ${chalk.yellow(cmd)}.`));console.log();suggestCommands(cmd); });// 重寫commander某些事件 enhanceErrorMessages('missingArgument', argsName => {return `Missing required argument ${chalk.yellow(`<${argsName}>`)}`; });program.parse(process.argv); // 把命令行參數(shù)傳給commander解析// 輸入easy顯示幫助信息 if (!process.argv.slice(2).length) {program.outputHelp(); }// easy支持的命令 function suggestCommands(cmd) {const avaliableCommands = program.commands.map(cmd => {return cmd._name;});// 簡易智能匹配用戶命令const suggestion = didYouMean(cmd, avaliableCommands);if (suggestion) {console.log(` ` + chalk.red(`Did you mean ${chalk.yellow(suggestion)}?`));} }function lowercase(str) {return str.toLocaleLowerCase(); }function validateArgsLen(argvLen, maxArgvLens) {if (argvLen > maxArgvLens) {console.log(chalk.yellow('\n Info: You provided more than argument. the rest are ignored.'));} }其他代碼就不貼了我會給出源碼鏈接,下面分享一下幾個(gè)有意思的點(diǎn)。建議大家有興趣的跟著敲一遍,有很多小細(xì)節(jié)需要注意。
發(fā)布腳本
// script/release.jsconst { execSync } = require('child_process'); const semver = require('semver'); const inquirer = require('inquirer');const currentVerison = require('../package.json').version;const release = async () => {console.log(`Current easy cli version is ${currentVerison}`);const releaseActions = ['patch', 'minor', 'major'];const versions = {};// 生成預(yù)發(fā)布版本標(biāo)示releaseActions.map(r => (versions[r] = semver.inc(currentVerison, r)));const releaseChoices = releaseActions.map(r => ({name: `${r} (${versions[r]})`,value: r}));// 選擇發(fā)布方式const { release } = await inquirer.prompt([{name: 'release',message: 'Select a release type',type: 'list',choices: [...releaseChoices]}]);// 優(yōu)先自定義版本const version = versions[release];// 二次確認(rèn)發(fā)布const { yes } = await inquirer.prompt([{name: 'yes',message: `Confirm releasing ${version}`,type: 'confirm'}]);if (yes) {execSync(`standard-version -r ${release}`, {stdio: 'inherit'});} };release().catch(err => {console.error(err);process.exit(1); });npm version 與 tag
官網(wǎng)關(guān)于 npm version 的介紹:
https://docs.npmjs.com/cli/version.html如果不熟悉 Node 語義化版本可以閱讀:
https://semver.org/lang/zh-CN/ npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git]'npm [-v | --version]' to print npm version 'npm view <pkg> version' to view a package's published version 'npm ls' to inspect current package/dependency versions其實(shí)我們自己使用 npn publish,最終執(zhí)行的還是 npm version 下命令。
官網(wǎng)關(guān)于 npm-dist-tag 的介紹:
npm-dist-tag[1]
npm install <name>@<tag> npm install --tag <tag>npm 也有 tag 的概念,一般情況下我們不會指定 tag,這個(gè)時(shí)候默認(rèn)使用的就是 latest 這個(gè) tag,所有的發(fā)布與安裝都是最新的正式版本,如果指定 tag 之后,我們可以在這個(gè) tag 上發(fā)布一個(gè)新的版本,用戶安裝時(shí)候也可以指定這個(gè) tag 來進(jìn)行安裝,你可以簡單理解 tag 類型 git 中的 branch。
常用的一些關(guān)于 tag 的命令:
# 查看當(dāng)前的tag和對應(yīng)的version。 npm dist-tag ls# 查看my-package發(fā)布過的所有版本號。 npm view my-package versions# 給my-package設(shè)置tag,對應(yīng)到版本version。 npm dist-tag add my-package@version tag如果一不小心把測試版發(fā)布成了正式版?發(fā)布之前我們是這樣的:
latest: 1.0.0 next: 1.0.0-alpha.0錯(cuò)誤的把 1.0.0-alpha.1 直接 npm publish:
latest: 1.0.0-alpha.1 next: 1.0.0-alpha.0解決方法:
# 把原來的1.0.0設(shè)置成最新的正式版 $ npm dist-tag add my-package@1.0.0 latest# 把1.0.0-alpha.1更新到最新的測試版 $ npm dist-tag add my-package@1.0.0-alpha.1 nextnpm publish 一個(gè)包
1.創(chuàng)建一個(gè) npm 賬戶2.cd 到你需要發(fā)布的 repo 倉庫下, 記得切換到 npm 源(或者公司內(nèi)網(wǎng)自建源)3.npm login,需要輸入用戶名、密碼、郵箱4.npm publish
集成 CI(Travis CI)自動發(fā)布
每次手動發(fā)布太 low 了,要是可以自動發(fā)布就好了。
Travis CI 提供的是持續(xù)集成服務(wù)(Continuous Integration,簡稱 CI)。它綁定 GitHub/GitLab 等上面的項(xiàng)目,只要有新的代碼,就會自動抓取。然后,提供一個(gè)運(yùn)行環(huán)境,執(zhí)行測試,完成構(gòu)建,還能部署到服務(wù)器。
持續(xù)集成指的是只要代碼有變更,就自動運(yùn)行構(gòu)建和測試,反饋運(yùn)行結(jié)果。確保符合預(yù)期以后,再將新代碼“集成”到主干。
簡單理解就是:它的作用是自動幫你做好從代碼測試到發(fā)布的一系列流程,配合版本控制使用的話可以設(shè)置成每一次 push 都自動進(jìn)行一次集成,保證代碼的正確性。
注意現(xiàn)在 GitHub 也出了集成工具,感興趣的可以去體驗(yàn)下。
如果你的項(xiàng)目是在 GitHub 并且是開源的,推薦使用這個(gè)?org[2]。
使用 GitHub 進(jìn)行登錄 Travis CI,完成一些授權(quán)工作,Travis CI 才能監(jiān)聽到你的 GitHub 項(xiàng)目代碼的變化。
Travis CI 要求你項(xiàng)目的根目錄必須有一個(gè)配置文件 .travis.yml 文件,這是一個(gè)配置文件,指定 travis 的行為,該文件還必須保存在 GitHub 的倉庫。一旦有新的 push,travis 就會找到這個(gè)文件進(jìn)行執(zhí)行。
關(guān)于 travis 更多使用推薦閱讀官網(wǎng)[3],這里主要講下利用?travis 自動發(fā)布包到 npm[4],Continuous Integration environments[5]。
下面是一個(gè) .travis.yml 配置文件:
language: node_js node_js:- '8' cache:directories:- node_modules install:- npm install script:- npm run lint deploy:provider: npmemail: "$NPM_EMAIL"api_key: "$AUTH_TOKEN"skip_cleanup: trueon:branch: master # after_success:然后在你的 travis 上選擇需要開啟 CI 的項(xiàng)目。
配置對應(yīng)環(huán)境變量到該倉庫下如:
環(huán)境變量名格式必須為“大寫字母_大寫字母”格式。
token 生成也非常簡單,官網(wǎng)[6]介紹,可以直接在你的 npm 賬戶下的 tokens 頁面手動生成或者通過 npm 命令行生成。
# 切換到npm源下, 登陸npm npm login# 生成token, npm可以指定生成token的權(quán)限(只讀或者可讀可寫) npm token create然后配置一些腳本來執(zhí)行 npm version,這樣當(dāng)你包版本有更新后 push 到 GitHub repo,就會觸發(fā) travis 自動發(fā)包到 npm。
DEMO
源碼鏈接[7]
推薦閱讀
若川知乎高贊:有哪些必看的 JS庫?
我在阿里招前端,我該怎么幫你?(現(xiàn)在還可以加模擬面試群)
如何拿下阿里巴巴 P6 的前端 Offer
如何準(zhǔn)備阿里P6/P7前端面試--項(xiàng)目經(jīng)歷準(zhǔn)備篇
大廠面試官常問的亮點(diǎn),該如何做出?
如何從初級到專家(P4-P7)打破成長瓶頸和有效突破
若川知乎問答:2年前端經(jīng)驗(yàn),做的項(xiàng)目沒什么技術(shù)含量,怎么辦?
末尾
你好,我是若川,江湖人稱菜如若川,歷時(shí)一年只寫了一個(gè)學(xué)習(xí)源碼整體架構(gòu)系列~(點(diǎn)擊藍(lán)字了解我)
關(guān)注若川視野,回復(fù)"pdf" 領(lǐng)取優(yōu)質(zhì)前端書籍pdf,回復(fù)"1",可加群長期交流學(xué)習(xí)
我的博客地址:https://lxchuan12.gitee.io?歡迎收藏
覺得文章不錯(cuò),可以點(diǎn)個(gè)在看呀^_^另外歡迎留言交流~
精選前端好文,伴你不斷成長
若川原創(chuàng)文章精選!可點(diǎn)擊
小提醒:若川視野公眾號面試、源碼等文章合集在菜單欄中間【源碼精選】按鈕,歡迎點(diǎn)擊閱讀,也可以星標(biāo)我的公眾號,便于查找
總結(jié)
以上是生活随笔為你收集整理的手写一个合格的前端脚手架的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓加密软件_安卓视频加密软件使用哪个好
- 下一篇: 前端学习(3083):vue+eleme