摘要:在年年底的時(shí)候,同事聊起腳手架。由于公司業(yè)務(wù)的多樣性前端的靈活性讓我們不得不思考更通用的腳手架。針對(duì)開(kāi)發(fā)使用的腳手架針對(duì)項(xiàng)目創(chuàng)建項(xiàng)目通用腳手架是一款強(qiáng)壯的且有一系列工具的通用型腳手架,但發(fā)布指定名稱,和用其開(kāi)發(fā)工具。
在16年年底的時(shí)候,同事聊起腳手架。由于公司業(yè)務(wù)的多樣性,前端的靈活性,讓我們不得不思考更通用的腳手架。而不是伴隨著前端技術(shù)的發(fā)展,不斷的把時(shí)間花在配置上。于是chef-cli誕生了。 18年年初,把過(guò)往一年的東西整理和總結(jié)下,重新增強(qiáng)了原有的腳手架project-next-cli, 不單單滿足我們團(tuán)隊(duì)的需求,也可以滿足其他人的需求。
project-next-cli面向的目標(biāo)用戶:
公司業(yè)務(wù)雜,但有一定的積累
愛(ài)折騰的同學(xué)和團(tuán)隊(duì)
借助github大量開(kāi)發(fā)模板開(kāi)發(fā)
發(fā)展前端這幾年(13年-15年)處于高速發(fā)展,主要表現(xiàn):
備注:以下發(fā)展過(guò)程出現(xiàn),請(qǐng)不要糾結(jié)出現(xiàn)順序 [捂臉]
庫(kù)/框架:jQuery, backbone, angular,react,vue
模塊化:commonjs, AMD(CMD), UMD, es module
任務(wù)管理器:npm scripts, grunt, gulp
模塊打包工具: r.js, webpack, rollup, browserify
css預(yù)處理器:Sass, Less, Stylus, Postcss
靜態(tài)檢查器:flow/typescript
測(cè)試工具:mocha,jasmine,jest,ava
代碼檢測(cè)工具:eslint,jslint
開(kāi)發(fā)當(dāng)我們真實(shí)開(kāi)發(fā)中,會(huì)遇到各種各樣的業(yè)務(wù)需求(場(chǎng)景),根據(jù)需求和場(chǎng)景選用不同的技術(shù)棧,由于技術(shù)的進(jìn)步和不同瀏覽器運(yùn)行時(shí)的限制,不得不配置對(duì)應(yīng)的環(huán)境等,導(dǎo)致我們從而滿足業(yè)務(wù)需求。
畫(huà)了一張圖來(lái)表示,業(yè)務(wù),配置(環(huán)境),技術(shù)之間的關(guān)系
前端配置工程師于是明見(jiàn)流傳了一個(gè)新的職業(yè),前端配置工程師 O(∩_∩)O~
社區(qū)現(xiàn)狀 專一的腳手架社區(qū)中存在著大量的專一型框架,主要針對(duì)一個(gè)目標(biāo)任務(wù)做定制。比如下列腳手架
vue-cli
vue-cli提供利用vue開(kāi)發(fā)webpack, 以及 遠(yuǎn)程克隆生成文件等 pwa等模板,本文腳手架參考了vue-cli的實(shí)現(xiàn)。
dva-cli
dva-cli 針對(duì)dva開(kāi)發(fā)使用的腳手架
think-cli
think-cli 針對(duì) thinkjs項(xiàng)目創(chuàng)建項(xiàng)目
通用腳手架yeoman
yeoman是一款強(qiáng)壯的且有一系列工具的通用型腳手架,但yeoman發(fā)布指定package名稱,和用其開(kāi)發(fā)工具。具體可點(diǎn)擊這里查看yeoman添加生成器規(guī)則
開(kāi)發(fā)初衷和目標(biāo)由于公司形態(tài)決定了,業(yè)務(wù)類型多樣,前端技術(shù)發(fā)展迭代,為了跟進(jìn)社區(qū)發(fā)展,更好的完成下列目標(biāo)而誕生。
完成業(yè)務(wù):專心,穩(wěn)定,快速
團(tuán)隊(duì)規(guī)范:代碼規(guī)范,測(cè)試流程,發(fā)布流程
沉淀:專人做專事,持續(xù)穩(wěn)定的迭代更新,跟進(jìn)時(shí)代
效益:少加班,少造輪子,完成kpi,做更有意義的事兒
實(shí)現(xiàn)準(zhǔn)備依托于Github,根據(jù)Github API來(lái)實(shí)現(xiàn),如下:
獲取項(xiàng)目
curl -i https://api.github.com/orgs/project-scaffold/repos
獲取版本
curl -i https://api.github.com/repos/project-scaffold/cli/tags實(shí)現(xiàn)邏輯
根據(jù)github api獲取到項(xiàng)目列表和版本號(hào)之后,根據(jù)輸入的名稱,選擇對(duì)應(yīng)的版本下載到本地私有倉(cāng)庫(kù),生成到執(zhí)行目錄下。核心流程圖如下:。
總體設(shè)計(jì)規(guī)范
使用Node進(jìn)行腳手架開(kāi)發(fā),版本選擇 >=6.0.0
選用async/await開(kāi)發(fā),解決異步回調(diào)問(wèn)題
使用babel編譯
使用ESLint規(guī)范代碼
功能
遵守單一職責(zé)原則,每個(gè)文件為一個(gè)多帶帶模塊,解決獨(dú)立的問(wèn)題??梢宰杂山M合,從而實(shí)現(xiàn)復(fù)用。以下是最終的目錄結(jié)構(gòu):
├── LICENSE ├── README.md ├── bin │?? └── project ├── package.json ├── src │?? ├── clear.js │?? ├── config.js │?? ├── helper │?? │?? ├── metalAsk.js │?? │?? ├── metalsimth.js │?? │?? └── render.js │?? ├── index.js │?? ├── init.js │?? ├── install.js │?? ├── list.js │?? ├── project.js │?? ├── search.js │?? ├── uninstall.js │?? ├── update.js │?? └── utils │?? ├── betterRequire.js │?? ├── check.js │?? ├── copy.js │?? ├── defs.js │?? ├── git.js │?? ├── loading.js │?? └── rc.js └── yarn.lock配置和主框架 使用babel-preset-env保證版本兼容
{ "presets": [ ["env", { "targets": { "node": "6.0.0" } }] ] }使用eslint管理代碼
eslint demo
{ "parserOptions": { "ecmaVersion": 7, "sourceType": "module", "ecmaFeatures": { "jsx": true } }, "extends": "airbnb-base/legacy", "rules": { "consistent-return": 1, "prefer-destructuring": 0, "no-mixed-spaces-and-tabs": 0, "no-console": 0, "no-tabs": 0, "one-var":0, "no-unused-vars": 2, "no-multi-spaces": 2, "key-spacing": [ 2, { "beforeColon": false, "afterColon": true, "align": { "on": "colon" } } ], "no-return-await": 0 }, "env": { "node": true, "es6": true } }使用husky檢測(cè)提交
使用husky, 來(lái)定義git-hooks, 規(guī)范git代碼提交流程,這里只做 commit校驗(yàn)
在package.json配置如下:
"husky": { "hooks": { "pre-commit": "npm run lint" } }入口
統(tǒng)一配置和入口,分發(fā)到不同單一文件,執(zhí)行輸出。核心代碼
function registerAction(command, type, typeMap) { command .command(type) .description(typeMap[type].desc) .alias(typeMap[type].alias) .action(async () => { try { if (type === "help") { help(); } else if (type === "config") { await project("config", ...process.argv.slice(3)); } else { await project(type); } } catch (e) { console.log(e); help(); } }); return command; }本地配置讀和寫(xiě)
配置用來(lái)獲取腳手架的基本設(shè)置, 如registry, type等基本信息。
使用
project config set registry koajs # 設(shè)置本地倉(cāng)庫(kù)下載源 project config get registry # 獲取本地倉(cāng)庫(kù)設(shè)置的屬性 project config delete registry # 刪除本地設(shè)置的屬性
邏輯
判定本地設(shè)置文件存在 ===> 讀/寫(xiě)
本地配置文件, 格式是 .ini
若中間每一步 數(shù)據(jù)為空/文件不存在 則給予提示
核心代碼
switch (action) { case "get": console.log(await rc(k)); console.log(""); return true; case "set": await rc(k, v); return true; case "remove": await rc(k, v, true); return true; default: console.log(await rc());
下面每個(gè)命令的實(shí)現(xiàn)邏輯。
下載使用
project i
邏輯
Github API ===> 獲取項(xiàng)目列表 ===> 選擇一個(gè)項(xiàng)目 ===> 獲取項(xiàng)目版本號(hào) ===> 選擇一個(gè)版本號(hào) ===> 下載到本地倉(cāng)庫(kù)
獲取項(xiàng)目列表
https://api.github.com/orgs/p...
獲取tag列表
若中間每一步 數(shù)據(jù)為空/文件不存在 則給予提示
請(qǐng)求代碼
request
function fetch(api) { return new Promise((resolve, reject) => { request({ url : api, method : "GET", headers: { "User-Agent": `${ua}` } }, (err, res, body) => { if (err) { reject(err); return; } const data = JSON.parse(body); if (data.message === "Not Found") { reject(new Error(`${api} is not found`)); } else { resolve(data); } }); }); }
下載代碼
download-git-repo
export const download = async (repo) => { const { url, scaffold } = await getGitInfo(repo); return new Promise((resolve, reject) => { downloadGit(url, `${dirs.download}/${scaffold}`, (err) => { if (err) { reject(err); return; } resolve(); }); }); };
核心代碼
// 獲取github項(xiàng)目列表 const repos = await repoList(); choices = repos.map(({ name }) => name); answers = await inquirer.prompt([ { type : "list", name : "repo", message: "which repo do you want to install?", choices } ]); // 選擇的項(xiàng)目 const repo = answers.repo; // 項(xiàng)目的版本號(hào)劣幣愛(ài)哦 const tags = await tagList(repo); if (tags.length === 0) { version = ""; } else { choices = tags.map(({ name }) => name); answers = await inquirer.prompt([ { type : "list", name : "version", message: "which version do you want to install?", choices } ]); version = answers.version; } // 下載 await download([repo, version].join("@"));生成項(xiàng)目
使用
project init
邏輯
獲取本地倉(cāng)庫(kù)列表 ===> 選擇一個(gè)本地項(xiàng)目 ===> 輸入基本信息 ===> 編譯生成到臨時(shí)文件 ===> 復(fù)制并重名到目標(biāo)目錄
若中間每一步 數(shù)據(jù)為空/文件不存在/生成目錄已重復(fù) 則給予提示
核心代碼
// 獲取本地倉(cāng)庫(kù)項(xiàng)目 const list = await readdir(dirs.download); // 基本信息 const answers = await inquirer.prompt([ { type : "list", name : "scaffold", message: "which scaffold do you want to init?", choices: list }, { type : "input", name : "dir", message: "project name", // 必要的驗(yàn)證 async validate(input) { const done = this.async(); if (input.length === 0) { done("You must input project name"); return; } const dir = resolve(process.cwd(), input); if (await exists(dir)) { done("The project name is already existed. Please change another name"); } done(null, true); } } ]); const metalsmith = await rc("metalsmith"); if (metalsmith) { const tmp = `${dirs.tmp}/${answers.scaffold}`; // 復(fù)制一份到臨時(shí)目錄,在臨時(shí)目錄編譯生成 await copy(`${dirs.download}/${answers.scaffold}`, tmp); await metal(answers.scaffold); await copy(`${tmp}/${dirs.metalsmith}`, answers.dir); // 刪除臨時(shí)目錄 await rmfr(tmp); } else { await copy(`${dirs.download}/${answers.scaffold}`, answers.dir); }
其中模板引擎編譯實(shí)現(xiàn)核心代碼如下:
// metalsmith邏輯 function metal(answers, tmpBuildDir) { return new Promise((resolve, reject) => { metalsmith .metadata(answers) .source("./") .destination(tmpBuildDir) .clean(false) .use(render()) .build((err) => { if (err) { reject(err); return; } resolve(true); }); }); } // metalsmith render中間件實(shí)現(xiàn) function render() { return function _render(files, metalsmith, next) { const meta = metalsmith.metadata(); /* eslint-disable */ Object.keys(files).forEach(function(file){ const str = files[file].contents.toString(); consolidate.swig.render(str, meta, (err, res) => { if (err) { return next(err); } files[file].contents = new Buffer(res); next(); }); }) } }升級(jí)/降級(jí)版本
使用
project update
邏輯
獲取本地倉(cāng)庫(kù)列表 ===> 選擇一個(gè)本地項(xiàng)目 ===> 獲取版本信息列表 ===> 選擇一個(gè)版本 ===> 覆蓋原有的版本文件
若中間每一步 數(shù)據(jù)為空/文件不存在 則給予提示
核心代碼
// 獲取本地倉(cāng)庫(kù)列表 const list = await readdir(dirs.download); // 選擇一個(gè)要升級(jí)的項(xiàng)目 answers = await inquirer.prompt([ { type : "list", name : "scaffold", message: "which scaffold do you want to update?", choices: list, async validate(input) { const done = this.async(); if (input.length === 0) { done("You must choice one scaffold to update the version. If not update, Ctrl+C"); return; } done(null, true); } } ]); const repo = answers.scaffold; // 獲取該項(xiàng)目的版本信息 const tags = await tagList(repo); if (tags.length === 0) { version = ""; } else { choices = tags.map(({ name }) => name); answers = await inquirer.prompt([ { type : "list", name : "version", message: "which version do you want to install?", choices } ]); version = answers.version; } // 下載覆蓋文件 await download([repo, version].join("@"))搜索
搜索遠(yuǎn)程的github倉(cāng)庫(kù)有哪些項(xiàng)目列表
使用
project search
邏輯
獲取github項(xiàng)目列表 ===> 輸入搜索的內(nèi)容 ===> 返回匹配的列表
若中間每一步 數(shù)據(jù)為空 則給予提示
核心代碼
const answers = await inquirer.prompt([ { type : "input", name : "search", message: "search repo" } ]); if (answers.search) { let list = await searchList(); list = list .filter(item => item.name.indexOf(answers.search) > -1) .map(({ name }) => name); console.log(""); if (list.length === 0) { console.log(`${answers.search} is not found`); } console.log(list.join(" ")); console.log(""); }總結(jié)
以上是這款通用腳手架產(chǎn)生的背景,針對(duì)用戶以及具體實(shí)現(xiàn),該腳手架目前還有一些可以優(yōu)化的地方:
不同源,存儲(chǔ)不同的文件
支持離線功能
硬廣:如果您覺(jué)得project-next-cli好用,歡迎star,也歡迎fork一塊維護(hù)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/101986.html
摘要:是一個(gè)現(xiàn)代應(yīng)用程序的靜態(tài)模塊打包器,前端模塊化的基礎(chǔ)。作為一個(gè)前端工程師切圖仔,非常有必要學(xué)習(xí)。官網(wǎng)的文檔非常的棒,中文文檔也非常給力,可以媲美的文檔。建議先看概念篇章,再看指南,然后看和配置總覽。 webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器,前端模塊化的基礎(chǔ)。作為一個(gè)前端工程師(切圖仔),非常有必要學(xué)習(xí)。 showImg(https://segment...
摘要:官方推薦不寫(xiě)重復(fù)的配置,即把本地和生產(chǎn)環(huán)境共用的配置放到一個(gè)文件,然后通過(guò)進(jìn)行合并我們可以看到,通過(guò)插件,將共用配置和開(kāi)發(fā)的配置進(jìn)行合并定義了全局變量這個(gè)插件是為了在我們?cè)试S后,自動(dòng)打開(kāi)頁(yè)面,避免每次都手動(dòng)打開(kāi)。 之前只知道webpack很強(qiáng)大,但是一直沒(méi)有深入學(xué)習(xí)過(guò),這次從頭看了一下教程,然后從0開(kāi)始搭建了一個(gè)多入口網(wǎng)站的開(kāi)發(fā)腳手架,期間遇到過(guò)很多問(wèn)題,所以有心整理一下,希望能給大家...
摘要:官方推薦不寫(xiě)重復(fù)的配置,即把本地和生產(chǎn)環(huán)境共用的配置放到一個(gè)文件,然后通過(guò)進(jìn)行合并我們可以看到,通過(guò)插件,將共用配置和開(kāi)發(fā)的配置進(jìn)行合并定義了全局變量這個(gè)插件是為了在我們?cè)试S后,自動(dòng)打開(kāi)頁(yè)面,避免每次都手動(dòng)打開(kāi)。 之前只知道webpack很強(qiáng)大,但是一直沒(méi)有深入學(xué)習(xí)過(guò),這次從頭看了一下教程,然后從0開(kāi)始搭建了一個(gè)多入口網(wǎng)站的開(kāi)發(fā)腳手架,期間遇到過(guò)很多問(wèn)題,所以有心整理一下,希望能給大家...
摘要:而中實(shí)現(xiàn)原理是利用高階函數(shù)通過(guò)將多個(gè)函數(shù)組合成一個(gè)可執(zhí)行執(zhí)行函數(shù)關(guān)鍵步驟代碼如下所示。和都是基于更新差異元素。 引言 平時(shí)開(kāi)發(fā)單頁(yè)項(xiàng)目應(yīng)用基于vue,目前另外兩個(gè)比較熱的庫(kù)還有angular和react,angular 1系列用過(guò),進(jìn)入公司后由于基于vue技術(shù)棧就沒(méi)在關(guān)注了。一直在關(guān)注react,目的不是學(xué)習(xí)用法,只是為了拓展自己的視野和思維,通過(guò)了解一些使用上的差異性,來(lái)進(jìn)一步的思考...
摘要:如何構(gòu)建大型的前端項(xiàng)目搭建好項(xiàng)目的腳手架一般新開(kāi)發(fā)一個(gè)項(xiàng)目時(shí),我們會(huì)首先搭建好一個(gè)腳手架,然后才會(huì)開(kāi)始寫(xiě)代碼。組件化一般分為項(xiàng)目?jī)?nèi)的組件化和項(xiàng)目外的組件化。 如何構(gòu)建大型的前端項(xiàng)目 1. 搭建好項(xiàng)目的腳手架 一般新開(kāi)發(fā)一個(gè)項(xiàng)目時(shí),我們會(huì)首先搭建好一個(gè)腳手架,然后才會(huì)開(kāi)始寫(xiě)代碼。一般腳手架都應(yīng)當(dāng)有以下的幾個(gè)功能: 自動(dòng)化構(gòu)建代碼,比如打包、壓縮、上傳等功能 本地開(kāi)發(fā)與調(diào)試,并有熱替換與...
閱讀 2941·2021-11-23 09:51
閱讀 3110·2021-11-15 11:39
閱讀 2996·2021-11-09 09:47
閱讀 2541·2019-08-30 13:49
閱讀 2126·2019-08-30 13:09
閱讀 3109·2019-08-29 16:10
閱讀 3518·2019-08-26 17:04
閱讀 1006·2019-08-26 13:57