摘要:在年年底的時候,同事聊起腳手架。由于公司業務的多樣性前端的靈活性讓我們不得不思考更通用的腳手架。針對開發使用的腳手架針對項目創建項目通用腳手架是一款強壯的且有一系列工具的通用型腳手架,但發布指定名稱,和用其開發工具。
在16年年底的時候,同事聊起腳手架。由于公司業務的多樣性,前端的靈活性,讓我們不得不思考更通用的腳手架。而不是伴隨著前端技術的發展,不斷的把時間花在配置上。于是chef-cli誕生了。 18年年初,把過往一年的東西整理和總結下,重新增強了原有的腳手架project-next-cli, 不單單滿足我們團隊的需求,也可以滿足其他人的需求。
project-next-cli面向的目標用戶:
公司業務雜,但有一定的積累
愛折騰的同學和團隊
借助github大量開發模板開發
發展前端這幾年(13年-15年)處于高速發展,主要表現:
備注:以下發展過程出現,請不要糾結出現順序 [捂臉]
庫/框架:jQuery, backbone, angular,react,vue
模塊化:commonjs, AMD(CMD), UMD, es module
任務管理器:npm scripts, grunt, gulp
模塊打包工具: r.js, webpack, rollup, browserify
css預處理器:Sass, Less, Stylus, Postcss
靜態檢查器:flow/typescript
測試工具:mocha,jasmine,jest,ava
代碼檢測工具:eslint,jslint
開發當我們真實開發中,會遇到各種各樣的業務需求(場景),根據需求和場景選用不同的技術棧,由于技術的進步和不同瀏覽器運行時的限制,不得不配置對應的環境等,導致我們從而滿足業務需求。
畫了一張圖來表示,業務,配置(環境),技術之間的關系
前端配置工程師于是明見流傳了一個新的職業,前端配置工程師 O(∩_∩)O~
社區現狀 專一的腳手架社區中存在著大量的專一型框架,主要針對一個目標任務做定制。比如下列腳手架
vue-cli
vue-cli提供利用vue開發webpack, 以及 遠程克隆生成文件等 pwa等模板,本文腳手架參考了vue-cli的實現。
dva-cli
dva-cli 針對dva開發使用的腳手架
think-cli
think-cli 針對 thinkjs項目創建項目
通用腳手架yeoman
yeoman是一款強壯的且有一系列工具的通用型腳手架,但yeoman發布指定package名稱,和用其開發工具。具體可點擊這里查看yeoman添加生成器規則
開發初衷和目標由于公司形態決定了,業務類型多樣,前端技術發展迭代,為了跟進社區發展,更好的完成下列目標而誕生。
完成業務:專心,穩定,快速
團隊規范:代碼規范,測試流程,發布流程
沉淀:專人做專事,持續穩定的迭代更新,跟進時代
效益:少加班,少造輪子,完成kpi,做更有意義的事兒
實現準備依托于Github,根據Github API來實現,如下:
獲取項目
curl -i https://api.github.com/orgs/project-scaffold/repos
獲取版本
curl -i https://api.github.com/repos/project-scaffold/cli/tags實現邏輯
根據github api獲取到項目列表和版本號之后,根據輸入的名稱,選擇對應的版本下載到本地私有倉庫,生成到執行目錄下。核心流程圖如下:。
總體設計規范
使用Node進行腳手架開發,版本選擇 >=6.0.0
選用async/await開發,解決異步回調問題
使用babel編譯
使用ESLint規范代碼
功能
遵守單一職責原則,每個文件為一個多帶帶模塊,解決獨立的問題。可以自由組合,從而實現復用。以下是最終的目錄結構:
├── 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檢測提交
使用husky, 來定義git-hooks, 規范git代碼提交流程,這里只做 commit校驗
在package.json配置如下:
"husky": { "hooks": { "pre-commit": "npm run lint" } }入口
統一配置和入口,分發到不同單一文件,執行輸出。核心代碼
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; }本地配置讀和寫
配置用來獲取腳手架的基本設置, 如registry, type等基本信息。
使用
project config set registry koajs # 設置本地倉庫下載源 project config get registry # 獲取本地倉庫設置的屬性 project config delete registry # 刪除本地設置的屬性
邏輯
判定本地設置文件存在 ===> 讀/寫
本地配置文件, 格式是 .ini
若中間每一步 數據為空/文件不存在 則給予提示
核心代碼
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());
下面每個命令的實現邏輯。
下載使用
project i
邏輯
Github API ===> 獲取項目列表 ===> 選擇一個項目 ===> 獲取項目版本號 ===> 選擇一個版本號 ===> 下載到本地倉庫
獲取項目列表
https://api.github.com/orgs/p...
獲取tag列表
若中間每一步 數據為空/文件不存在 則給予提示
請求代碼
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項目列表 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 } ]); // 選擇的項目 const repo = answers.repo; // 項目的版本號劣幣愛哦 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("@"));生成項目
使用
project init
邏輯
獲取本地倉庫列表 ===> 選擇一個本地項目 ===> 輸入基本信息 ===> 編譯生成到臨時文件 ===> 復制并重名到目標目錄
若中間每一步 數據為空/文件不存在/生成目錄已重復 則給予提示
核心代碼
// 獲取本地倉庫項目 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", // 必要的驗證 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}`; // 復制一份到臨時目錄,在臨時目錄編譯生成 await copy(`${dirs.download}/${answers.scaffold}`, tmp); await metal(answers.scaffold); await copy(`${tmp}/${dirs.metalsmith}`, answers.dir); // 刪除臨時目錄 await rmfr(tmp); } else { await copy(`${dirs.download}/${answers.scaffold}`, answers.dir); }
其中模板引擎編譯實現核心代碼如下:
// 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中間件實現 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(); }); }) } }升級/降級版本
使用
project update
邏輯
獲取本地倉庫列表 ===> 選擇一個本地項目 ===> 獲取版本信息列表 ===> 選擇一個版本 ===> 覆蓋原有的版本文件
若中間每一步 數據為空/文件不存在 則給予提示
核心代碼
// 獲取本地倉庫列表 const list = await readdir(dirs.download); // 選擇一個要升級的項目 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; // 獲取該項目的版本信息 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("@"))搜索
搜索遠程的github倉庫有哪些項目列表
使用
project search
邏輯
獲取github項目列表 ===> 輸入搜索的內容 ===> 返回匹配的列表
若中間每一步 數據為空 則給予提示
核心代碼
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(""); }總結
以上是這款通用腳手架產生的背景,針對用戶以及具體實現,該腳手架目前還有一些可以優化的地方:
不同源,存儲不同的文件
支持離線功能
硬廣:如果您覺得project-next-cli好用,歡迎star,也歡迎fork一塊維護。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101986.html
摘要:是一個現代應用程序的靜態模塊打包器,前端模塊化的基礎。作為一個前端工程師切圖仔,非常有必要學習。官網的文檔非常的棒,中文文檔也非常給力,可以媲美的文檔。建議先看概念篇章,再看指南,然后看和配置總覽。 webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器,前端模塊化的基礎。作為一個前端工程師(切圖仔),非常有必要學習。 showImg(https://segment...
摘要:官方推薦不寫重復的配置,即把本地和生產環境共用的配置放到一個文件,然后通過進行合并我們可以看到,通過插件,將共用配置和開發的配置進行合并定義了全局變量這個插件是為了在我們允許后,自動打開頁面,避免每次都手動打開。 之前只知道webpack很強大,但是一直沒有深入學習過,這次從頭看了一下教程,然后從0開始搭建了一個多入口網站的開發腳手架,期間遇到過很多問題,所以有心整理一下,希望能給大家...
摘要:官方推薦不寫重復的配置,即把本地和生產環境共用的配置放到一個文件,然后通過進行合并我們可以看到,通過插件,將共用配置和開發的配置進行合并定義了全局變量這個插件是為了在我們允許后,自動打開頁面,避免每次都手動打開。 之前只知道webpack很強大,但是一直沒有深入學習過,這次從頭看了一下教程,然后從0開始搭建了一個多入口網站的開發腳手架,期間遇到過很多問題,所以有心整理一下,希望能給大家...
摘要:而中實現原理是利用高階函數通過將多個函數組合成一個可執行執行函數關鍵步驟代碼如下所示。和都是基于更新差異元素。 引言 平時開發單頁項目應用基于vue,目前另外兩個比較熱的庫還有angular和react,angular 1系列用過,進入公司后由于基于vue技術棧就沒在關注了。一直在關注react,目的不是學習用法,只是為了拓展自己的視野和思維,通過了解一些使用上的差異性,來進一步的思考...
摘要:如何構建大型的前端項目搭建好項目的腳手架一般新開發一個項目時,我們會首先搭建好一個腳手架,然后才會開始寫代碼。組件化一般分為項目內的組件化和項目外的組件化。 如何構建大型的前端項目 1. 搭建好項目的腳手架 一般新開發一個項目時,我們會首先搭建好一個腳手架,然后才會開始寫代碼。一般腳手架都應當有以下的幾個功能: 自動化構建代碼,比如打包、壓縮、上傳等功能 本地開發與調試,并有熱替換與...
閱讀 2930·2021-11-23 09:51
閱讀 3103·2021-11-15 11:39
閱讀 2985·2021-11-09 09:47
閱讀 2533·2019-08-30 13:49
閱讀 2116·2019-08-30 13:09
閱讀 3101·2019-08-29 16:10
閱讀 3509·2019-08-26 17:04
閱讀 995·2019-08-26 13:57