如何寫一個可愛的腳手架 (一)

本文首發於我的 Github,歡迎 issue / fxxk。css

前言

相信你們都寫過vue,react或者angular的各位同窗,也必定不會對如下庫陌生:html

體驗過上述工具的同窗,有沒有發現他們都有一個共同點——提供了一個可供快速開發的樣板文件(boilerplate)。本文就將從樣板文件入來進行闡述。經過本文,你將學到:前端

  1. 一個CLI工具須要解決的問題;
  2. 寫一個CLI的基本思路;
  3. 寫一個CLI須要作哪些準備;

PS:因爲腳手架的英文 scaffolding 太長,本文我將以更可愛的 cli 來代替。vue

預熱

因爲篇幅有限,本節將以create-react-appvue-cli爲例(真地很難說服你們我是ng起家的...),回顧其使用過程:node

首先是create-react-app, 按照README,咱們生成出一個基本項目後,打開目錄,其目錄結構以下:react

my-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public 
│   └── favicon.ico
│   └── index.html
│   └── manifest.json 
└── src
    └── App.css
    └── App.js
    └── App.test.js
    └── index.css
    └── index.js
    └── logo.svg
    └── registerServiceWorker.js
複製代碼

說一下其中兩個有意思的點:webpack

  • public/manifest.json: 這是PWA 的一部分,用來描述應用相關的信息。之前開發cordova的時候,這個還用來作過熱更新。
  • src/registerServiceWorker.js: 安裝Service Workers文件。

好久沒用它,原來已經默認支持PWA了,nice, 還不會的同窗趕忙學起來,這裏就不展開了。git

接着,咱們打開package.json,探一下究竟:github

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-scripts": "1.1.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}
複製代碼

很是簡潔,可是有一個react-scripts。看起來很陌生,可是若是我告訴你它的依賴中有babelwebpackwebpack-dev-serverautoprefixer這些常見的前端流氓,我想你也清楚了react-scripts到底作了什麼:web

包裝了 webpackwebpack-dev-server,提供一套默認的配置(內置css,file,svg等等各類loader),在 react-scripts startreact-scripts build 時直接運行這些配置。

一句話歸納,就是爲了幫你簡化構建時的配置(Create React apps with no build configuration.😂)。

固然,零配置並不是適用於大型定製項目的開發,react-scripts就像一個巨大的黑盒同樣,總有存在一些其未提供 API 或者指令的場景,讓你無從下手。react-scripts固然也不傻,還提供了一個比較fancy的指令:

  • react-scripts eject: 將全部的工具(配置文件和 package.json 依賴庫)解壓到應用所在的路徑。

厲害了😅,來運行看看:

當全部的依賴暴露出來是,果真package.json瞬間爆炸性增加了,順時有點懷念從前的美好了。

好了,說到這裏,create-react-app 要解決的問題就是:

  1. 根據樣板文件生成統一的項目骨架,讓開發者快速投入開發;
  2. 預置了一個易於開發的react-scripts,讓開發者省略痛苦的配置流程.(webpack配置工程師看來要失業了😇)

接下來,再回顧一下 vue-cli。截止本文書寫日期, vue-cli@3.0 仍然處於 beta 階段,所以本文將以 2.x.x 爲例,咱們建立一個名爲 my-vue-app,模板爲 webpack-simple 的 vue 項目:

可見,vue-cli在生成項目以前多了一個很是重要的一步 —— Prompt,也就是問詢,根據詢問的內容最終生成你的項目。兩條FYI:

  1. vue-cli 的問詢功能是使用 Inquirer.js 這個庫完成的。
  2. webpack-simple 源碼你能夠在這裏找到 webpack-simple

對本節作一下結,一個腳手架一般由如下基本幾部分組成:

  1. 問詢(Prompts)
  2. 樣板文件(Boilerplate)
  3. 生成文件(Generate)

實戰

通過上一節的洗禮,你可能已經有大體的思路了,而後,讓咱們以 vue-cli 爲例,直接進入實戰吧。

基本思路

  1. 定義一個模板包的規則,這裏採用 vue-cli 的規則:template 爲源文件,meta.js/meta.json 爲配置入口文件。
my-first-package
├── meta.js
└── template
複製代碼
  1. 解析包的meta.js,得到要詢問的問題(prompts),並運行它,將最終用戶的答案分配到一個上下文對象中;
  2. 讀取 template 中的內容,用剛剛獲取的上下文對象去渲染它。

僞代碼實現

如下是一個CLI的僞代碼實現:

// 10行僞代碼實現一個CLI
function CLI(packageSourcePath) {
    const context = {}
    const meta = require(path.join(packageSourcePath, 'meta.js'))
    const templatePath = path.join(packageSourcePath, 'template')
    const { prompts } = meta
    return promptsRunner(prompts).then(anwsers => {
        Object.assign(context, anwsers)
        return generateFiles(templatePath, context)
    })
    .then(() =>  console.log('[OK]'))
    .error(() => console.log('[Error]'))
}
複製代碼

其中,promptsRunner用來問詢,generateFiles 用來渲染並生成文件。哇!原來這麼簡單。

技術選型

這是 vue-cli 的技術選型:

  1. 問詢:Inquirer.js
  2. 命令行解析:commander.js
  3. 模板渲染:handlebars.js
  4. 文件生成:metalsmith

熱衷於看這個世界的你,是否已經躍躍欲試了呢?

更多

固然,若是隻是這樣的一個 CLI,很顯然只是玩具,你能夠考慮支持如下可愛的特性:

  1. 在Context中注入默認的一些屬性(如git的username)
  2. 支持文件過濾(根據Context過濾)
  3. 支持文件重命名(vue-cli默認不直接支持,但能夠經過 metalsmith 的插件實現)
  4. 支持包管理(如包的生成,緩存,拉取,更新,刪除,自動化測試)
  5. 提供一些生命週期的鉤子(如beforePrompt,beforeRender,beforeExit等等)
  6. 動態的輸出路徑(這個對於我來講頗有用)

固然,還有不少了,只要你想獲得。

甜點時刻

是時候給你們介紹一些甜點了。

SAO

Github傳送門:github.com/saojs/sao

一個聽起來很騷氣的名字,這是咱們可愛的 EGOIST 寫的一個庫,基本上實現了上述我說的全部特性。

最爲關鍵的是,SAO目前已經提供了大量的高質量的樣板文件:

name description
template Template for scaffolding out an SAO template
lass Lass scaffolds a modern package boilerplate for node
lad Lad scaffolds a Koa webapp and API framework for node
vue Kickstart a Vue project with Poi
gi Generate .gitignore file in your project
nm Scaffold out a node module
vue-webpack Vue.js offcial webpack template (SAO port)
basic Basic project skeleton
react SAO template for react with vbuild
micro-service Scaffolding out a micro-service
node-cli Scaffold a node cli tool
next Scaffold out a Next.js project
electron Scaffold out an Electron project
expo Scaffold out an Expo app

太強大了,這也大概就是我不得不愛 EGOIST 的緣由了吧。

poz

Github傳送門:github.com/ulivz/poz

因爲在實際生產中須要支持 重命名 和 動態輸出路徑,我寫了 POZ 這個庫,在前人的基礎上,基於徹底不同的實現,實現了同樣的功能,並加了一點兒特效,這大概就是造輪子的樂趣吧。歡迎你們 Fxxk/Issue。

最近 POZ 也計劃開始開發 1.0的穩定版本了,快來看看這個 RoadMap,太有野心了有木有。

alphax

Github傳送門:github.com/ulivz/alpha…

這是 poz 底層使用的一個庫,基於Stream,靈感來源於 metalsmith 和 SAO 底層的 majo,最近我完全重寫了該庫,讓其有了如下可愛的特性:

  1. 極簡的API;
  2. 任務流控制;
  3. 中間件;
  4. 基於純函數或JSON的的文件過濾;
  5. 基於純函數或JSON的文件重命名;
  6. 支持不寫入硬盤,易於測試;

它使用起來像這樣:

alphax()
  .src('**')
  .task(task1)
  .task(task2)
  .task(task3)
  .use(file => file.content += Date.now())
  .rename(filepath => filepath.replace('{name}', name))
  .rename(filepath => filepath.replace('{age}', age))
  .transform(content => content.replace('{name}', name))
  .filter(filepath => filepath.endWith('.js'))
  .filter(filepath => !filepath.startWith('test'))
  .dest('dist')
  .then(files => console.log(files))
  .catch(error => console.log(error))
複製代碼

若是你不喜歡函數,也能夠用配置的方式來:

const config = {
  tasks: [task1, task3, task3],
  use: file => file.content += Date.now(),
  rename: {
    '{name}': name,
    '{age}': age
  },
  filter: {
    'app.js': true,
    'test.js': false
  },
  transform(content) {
    return content.replace('{name}', name)
  }
}

alphax()
  .src('**', config)
  .dest('dist')
  .then(files => console.log(files))
  .catch(error => console.log(error))
複製代碼

相信這個lib已經解決掉你大部分的 meta.js 的API設計問題了😄。附上用可愛的 docute 寫的文檔地址: Documentation

總結

一個CLI歸根究竟是要解決的是生產力和統一性的問題,可是,對於create-react-app這種過分封裝,和 vue-cli@2.x.x 的過分鬆散,彷佛都不是最佳方案。配置少和拓展性高這原本就是兩個互相矛盾的話題,從長遠來看,選擇怎樣的CLI仍是依賴於具體的場景,但做爲一個CLI開發者,若是作到更好的平衡,還值得多多思考。

本文僅談及了寫一個CLI工具的第一部分,其基本思路較爲簡單,只是實現層面會有較多的優化點和 error catch 😅,Good luck!

下文,我將繼續闡述相似於 create-react-app 中 react-scripts 的基本實現原理及其思路,實際上,這也是 vue-cli@3.x.x 和 poi 所具備的功能,敬請關注。

以上,全文終。)

相關文章
相關標籤/搜索