本文首發於我的 Github,歡迎 issue / fxxk。css
相信你們都寫過vue
,react
或者angular
的各位同窗,也必定不會對如下庫陌生:html
體驗過上述工具的同窗,有沒有發現他們都有一個共同點——提供了一個可供快速開發的樣板文件(boilerplate)。本文就將從樣板文件入來進行闡述。經過本文,你將學到:前端
CLI
工具須要解決的問題;CLI
的基本思路;CLI
須要作哪些準備;PS:因爲腳手架的英文 scaffolding 太長,本文我將以更可愛的 cli 來代替。vue
因爲篇幅有限,本節將以create-react-app
和vue-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
。看起來很陌生,可是若是我告訴你它的依賴中有babel
、webpack
、webpack-dev-server
和autoprefixer
這些常見的前端流氓,我想你也清楚了react-scripts
到底作了什麼:web
包裝了
webpack
和webpack-dev-server
,提供一套默認的配置(內置css,file,svg等等各類loader),在react-scripts start
和react-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 要解決的問題就是:
react-scripts
,讓開發者省略痛苦的配置流程.(webpack
配置工程師看來要失業了😇)接下來,再回顧一下 vue-cli
。截止本文書寫日期, vue-cli@3.0 仍然處於 beta 階段,所以本文將以 2.x.x 爲例,咱們建立一個名爲 my-vue-app,模板爲 webpack-simple 的 vue 項目:
可見,vue-cli在生成項目以前多了一個很是重要的一步 —— Prompt,也就是問詢,根據詢問的內容最終生成你的項目。兩條FYI:
對本節作一下結,一個腳手架一般由如下基本幾部分組成:
通過上一節的洗禮,你可能已經有大體的思路了,而後,讓咱們以 vue-cli 爲例,直接進入實戰吧。
my-first-package
├── meta.js
└── 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 的技術選型:
熱衷於看這個世界的你,是否已經躍躍欲試了呢?
固然,若是隻是這樣的一個 CLI,很顯然只是玩具,你能夠考慮支持如下可愛的特性:
固然,還有不少了,只要你想獲得。
是時候給你們介紹一些甜點了。
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 的緣由了吧。
Github傳送門:github.com/ulivz/poz
因爲在實際生產中須要支持 重命名 和 動態輸出路徑,我寫了 POZ 這個庫,在前人的基礎上,基於徹底不同的實現,實現了同樣的功能,並加了一點兒特效,這大概就是造輪子的樂趣吧。歡迎你們 Fxxk/Issue。
最近 POZ 也計劃開始開發 1.0的穩定版本了,快來看看這個 RoadMap,太有野心了有木有。
Github傳送門:github.com/ulivz/alpha…
這是 poz 底層使用的一個庫,基於Stream,靈感來源於 metalsmith 和 SAO 底層的 majo,最近我完全重寫了該庫,讓其有了如下可愛的特性:
它使用起來像這樣:
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 所具備的功能,敬請關注。
以上,全文終。)