欲取代絕大多 JavaScript 工具鏈?Rome 嚐鮮

一個包含希臘斯巴達頭盔的羅馬項目 Logo

條條大路通 Rome。在 Rome 尚未發佈 NPM 正式版之際。咱們圍繞 JavaScript 工具鏈爲核心點,來看看前往 Rome 的路上都有什麼;以及 Rome 自己,意味着什麼?

二月的最後一天,我在爲「開源愛好者月刊」搜尋本月最新的開源項目時,偶遇一個名叫 Rome 的倉庫霸榜,眼前着實一亮。「一個實驗性的 JavaScript 工具鏈」、「包括編譯器、lint、格式化程序、捆綁器、測試框架等」以及「旨在成爲與 JavaScript 源碼處理相關的全部功能的綜合工具」短短几句話展示了一個宏大的目標。如今,是時候入坑瞭解一波並在我的能力範圍內做一個淺要的分享。javascript

Rome 由就任於 Facebook 同時是 Babel 和 Yarn 做者的 Sebastian McKenzie 主導開源,開源以前,Rome 基本是他的我的項目,如今 Facebook 願意付薪水讓他潛心開發。截止如今(2020 年 04 月初),Rome 的提交記錄已經從 70+ 到 600+,貢獻者拓展到了 40+ 位,產生了 30+ issues 和 170+ Pull Request。前端

Rome 的 Star 趨勢圖,發佈之初便 3k+ star

此外,或許也能從側面呼應我曾在月刊第三期中收錄的一句關於「創業公司和大公司開源出發點有何不一樣」的話:大公司可能在一個項目的早期便開源,憑藉其號召力但願更多人一塊兒「貢獻」迭代,初創團隊則會在產品相對成熟的時候再開放,但願儘快吸引用戶深度「使用」,注重完善產品在工業環境下的綜合表現。java

正文 & 背景 & 乾貨開始。node

Rome:從我的項目到 Facebook 新開源

從官網不難看出,Rome 旨在成爲與 JavaScript 源代碼處理相關的全部功能的綜合工具,其中包括「編譯器、Linter、格式化程序、捆綁器、依賴管理器和測試框架」等。Rome 源於對整個項目的擴展範圍一致性的渴望。ios

同時,Rome 也來源於 Babel 做者自己對 Babel 的一些不知足而新創,就像 Deno 之於 Node 同樣。git

Rome 做者 Sebastian 關於 Rome 的推文集

本節根據 README.md 和官網首頁的介紹,來以問答形式展現 Rome 的背景和想要達到具體目標。github

0一、Rome 的一些來源?

在計算機科學中只有兩件難事:緩存失效和命名。 ——Phil Karlton
  • 立項來源:由 Babel and Yarn 的做者 Sebastian McKenzie 發起,是 React Native 團隊的一個項目。
  • 名稱來源:因「通向羅馬的全部道路」,「羅馬不是一天建成」和「在羅馬時要像羅馬人同樣」這樣的諺語而得名。 這是指整個項目的擴展範圍和對一致性的渴望,它始於一個辦公室玩笑。
  • Logo 來源:一個古希臘斯巴達頭盔。雖然它不是羅馬字母,也不太相關,但看起來比 Galea (羅馬士兵的頭盔)酷。

0二、Rome 的編碼架構?

在版本控制系統中,monorepo(單聲道存儲庫的音節縮寫)是一種軟件開發策略,其中許多項目的代碼存儲在同一存儲庫中。 截至 2017 年,這種軟件工程實踐已有十多年的歷史,但直到最近才被命名。——Monorepo,維基百科
  • 徹底使用 TypeScript 編寫,不多使用鬆散類型。
  • 支持處理 JSX 以及 Flow 和 TypeScript 代碼。
  • self-hosted,能夠本身編譯本身。
  • 不是現有工具的集合,全部組件都是自定義的,不使用第三方依賴項(對 JavaScript 生態系統進行了從新思考,對整個工具鏈採用了不依賴第三方庫的大膽實現)。
  • 是帶有內部軟件包的 monorepo 架構以便劃定代碼邊界。

0三、Rome 的工做展望?

  • 旨在成爲與 JavaScript 源代碼處理相關的全部功能的綜合工具。
  • 目標是替代許多現有的 JavaScript 工具,但也將提供爲其餘工具提供自身的集成方案,以根據須要隨意使用——例如使用 Rome 編譯器做爲另外一個捆綁程序的插件。
  • 目前關注的領域是 Linter(用於分析源代碼以標記編程錯誤,bug,樣式錯誤和可疑結構的工具),這是將 Rome 變成最容易使用的工具鏈的目標裏阻力最小的一個環節。

微欄:回看 JavaScript 工具鏈

在學習一個工具以前,每每咱們應該先去了解這個工具能夠用來解決什麼樣的問題;一樣的,當咱們遇到一個問題的時候,咱們也應該帶着這個問題去找工具解決。
——阿里巴巴集團 高級前端工程師 葉俊星

成熟的軟件項目必然遵循的良好的開發規範,也擁有屬於自身獨特的軟件開發生命週期,編程實踐只佔整個開發週期的很小一部分。當一個 JavaScript 軟件被創建時一般還會遇到哪些須要解決的問題?這便涉及到了 JavaScript 項目的技術選型,而 JavaScript 生態圈的明星項目數不勝數,如下做一個縱覽,不涉及各個工具的具體使用方式。web

JavaScript 工具鏈示意圖

  • JS 開發環境?有 V八、Node 甚至是 Deno 等;
  • JS 前端框架?有 Angular、React、Vue、React Native、jQuery 等;
  • JS 後端框架?有 Nest、Express、Koa 等;
  • JS 腳手架?有 Vue CLI、Angular CLI、Create React App、Yeoman 等;
  • JS 轉譯工具?有 Babel 等;
  • JS 測試工具?圍繞單元測試、集成測試,有 Mocha、Jasmine、Jest、Karma 等;
  • JS 調試工具?有 Chrome DevTools/Firebug/Webkit inspector 等各大主流瀏覽器、VS Code/WebStorm 等各大編輯器/IDE 等;
  • JS 格式規範工具?有 JSLint、JSHint、ESLint、TSLint 等;
  • JS 接口聯調工具?有 Axios、Fetch 等;
  • JS 包管理器?有 NPM、Yarn、Bower、PNPM 等;
  • JS 模塊加載器?有 RequireJS、SystemJS、StealJS、ES Module Loader 等;
  • JS 任務管理工具?Grunt、Gulp、Webpack 監聽文件變化,自動執行任務;
  • JS 靜態化支持?有 TypeScript、CoffeeScript、Flow、LiveScript 等;
  • JS 代碼後處理工具?圍繞混淆器、縮小器、優化器諸多領域有各類各樣的 loader 等;
  • JS 打包工具?Webpack、Rollup、Parcel、Browserify 等;
  • JS 模板引擎?有 handlebarsjs、etpl、templatejs 甚至各大前端框架內置的模板語法等;
  • JS 非 Web 框架?在物聯網、區塊鏈、大數據等領域均有相關庫支持,本文不涉及。
  • JS 進程管理?有 Forever、PM二、StrongLoop Process Manager 等;
  • ......?甚至編輯器、IDE、CSS 預處理器、代碼託管平臺、團隊開發模式(純前端、重後端、先後分離)、WebAssembly、Serverless、JS DevOps 等均可以加到項目的技術選型範圍內。

所以能夠看出,技術選型即是針對能讓項目成功運轉各個環節尋找相應的解決方案;工做流(Workflow)是全部解決方案融合後的落實流程;而工具鏈(Toolchain)即是工做流下全部實現方式的彙總,同時一個工具也能表明一個解決方案。npm

簡而言之,JavaScript 工具鏈即是 JavaScript 工程師在開發過程當中會用到的一系列工具。編程

淺嘗初試 Rome (v0.0.52)

如今 Rome 並無直接在 Github 上發佈任何版本,但編譯後生成的 rome.json 能夠看出有一個 v0.0.52 的版本號,處於一個很早期的狀態,項目簡介也是「一個實驗性的 JavaScript 工具鏈」。

想要嘗試 Rome,就得從如下步驟逐步展開(因爲 Rome 沒有發佈正式版本,這裏無需過多涉及如何整合在 package.json 的腳本中使用等工程化過程)。

帝國時代裏的羅馬大軍

本章全部 Demo 均在 @hylerrix/demos 的 Rome 文件夾中。

0一、git clone rome

既然 Rome 沒有正式發佈版本,咱們也沒法直接從 NPM 上直接安裝 Rome。現階段,Rome 提供了本地安裝的方式,只須要克隆到本地並本地編譯和本地 NPM 安裝便可使用。

注:安裝 Rome 前請確保本地已正常安裝 Node 和 NPM
# 克隆 Rome 項目到本地
$ git clone https://github.com/facebookexperimental/rome
# 命令行進入 Rome 項目
$ cd rome
# 方式一:編譯 Rome
$ ./scripts/build-release dist
# 方式二:編譯 Rome(Windows 10 的狀況下,使用 PowerShell 7)
$ cd rome && node scripts/build-release dist
# 安裝編譯後的 Rome 到本地全局環境中
$ npm install -g ./dist/
# 如今即可以使用 Rome 了
$ rome # No command specified. Run --help to see available commands.

0二、rome init

rome init 命令會在當前目錄生成一個 rome.json 文件,使用推薦配置會初始化如下內容:

{
  "version": "^0.0.52",
  "lint": {
    "enabled": true
  }
}

該文件告訴 Rome 至少應爲 0.0.52 版本,以便與當前項目一塊兒使用。具體使用文檔還在開發中。

0三、rome run index.ts

rome run 命令將運行傳遞給它的任何文件,一般與項目的主文件一塊兒使用。目前仍在開發中,可能沒法正確處理全部源文件。此時咱們爲測試 rome run 成功運行,創建一個 index.ts 和 api.ts 文件,以下。

// index.ts
import { getData } from './api'

async function setData () {
  const { success, data } = await getData()
  console.log('success:', success)
  console.log('data:', data)
}

await setData()

// api.ts
export const getData = () => Promise.resolve({
  success: true,
  data: 'Hello World!'
})

此時,運行以下命令即可以成功使用:

$ rome run index.ts
# ℹ Bundling index.ts
# success: true
# data: 'Hello World!'

0四、rome lint index.ts

因爲我真的不喜歡在 JavaScript 應用裏面寫分號,這與主流規範有些不一樣,因此 rome lint 命令恰好派上了用場:rome 默認須要在 JavaScript 語句結尾寫分號。同時在 api.ts 中故意不導出一個 interface 且在 index.ts 中故意將其錯誤導入,重構後的有錯誤 index.ts 和 api.ts 以及 rome lint 後執行過程以下:

// 故意錯誤編寫的 index.ts
import { getData } from './api'

async function setData() {
  const {success, data} = await getData()
  console.log('success:', success)
  console.log('data:', data)
}

await setData()

// 故意錯誤不導出的 api.ts
interface Params {
  username: string
  token: string
}

export const getData = (params: Params) => Promise.resolve({
  success: true,
  data: 'Hello World!'
})
$ rome lint index.ts
#  index.ts lint/pendingFixes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 
#   ✖ Pending fixes
# 
#      1 │ + import {getData, Params} from './api.ts';
#        │ - import {·getData, Params·} from './api.ts'
#      2 │   
#     .. │ 
#      4 │     const param: Params = {
#      5 │       username: 'hylerrix',
#      6 │ +     token: 'ningowood',
#      7 │ +   };
#      8 │ +   const {success, data} = await getData(param);
#      9 │ +   console.log('success:', success);
#     10 │ +   console.log('data:', data);
#     11 │   }
#     12 │   
#     13 │ + await setData();
#     14 │   
# 
#  index.ts:1:18 resolver/unknownExport ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 
#   ✖ Couldn't find export Params in api.ts
# 
#   > 1 │ import { getData, Params } from './api.ts'
#       │                   ^^^^^^ 
#     2 │ 
#     3 │ async function setData() {
# 
#   ℹ However we found a matching local variable in api.ts. Did you forget to export it?
# 
#   > 1 │ interface Params {
#       │           ^^^^^^ 
#     2 │   username: string
#     3 │   token: string
# 
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 
# ℹ Fixes available. Run `rome lint --fix` to apply.
# ✖ Found 2 problems
$ rome lint index.ts --fix
# ......
# ✔ 1 file fixed
# ✖ Found 2 problems

rome lint 命令在這裏提示咱們須要加分號並須要在 api.ts 中成功導出 interface。前者可使用 rome lint index.ts --fix 直接來修理(不會在 api.ts 中添加分號);後者須要手動修理,可是提供了十分完善的友好提示。

0五、rome compile index.ts

rome compile 命令將使用一組默認轉換來編譯文件。因爲在開發中,當前此命令沒有用於指定轉換子集的選項。使用這條命令後,輸出的結果已經沒有了 interface 的存在。

$ rome compile index.ts
# import {getData, Params} from './api';
# 
# async function setData() {
#   const param = {
#     username: 'hylerrix',
#     token: 'ningowood',
#   };
#   const {success, data} = await getData(param);
#   console.log('success:', success);
#   console.log('data:', data);
# }
# 
# await setData();

0六、rome parse index.ts

rome parse 命令將解析文件並輸出格式精美的 AST。

$ rome parse index.ts
# Program {
#   comments: Array []
#   corrupt: false
#   diagnostics: Array []
#   directives: Array []
#   filename: 'project-rome/index.ts'
#   hasHoistedVars: false
#   mtime: 1_586_498_633_476.8486
#   sourceType: 'module'
#   syntax: Array ['ts']
#   body: Array [
#     ImportDeclaration {
#       source: StringLiteral {value: './api'}
#       namedSpecifiers: Array [
#           ......

0七、Rome 的更多命令

除了官網展現的幾個命令外,從源碼能夠看出還有不少內置的命令正在開發,能夠從 rome --help 中尋找答案。

# 分析並輸出文件的依賴
$ rome analyzeDependencies index.ts
# 把 JavaScript 打包爲一個文件
$ rome bundle index.ts dist
# 啓動 Web 服務器
$ rome develop
# 計算文件路徑
$ rome resolve index.ts
# 安全依賴,運行 Linter 和測試
$ rome ci
# 運行測試
$ rome test
# ...restart/start/status/stop/web
# ...config/publish/run/evict/logs/rage

總結 & 訂閱

通過近幾年的蓬勃發展,JavaScript 早已再也不侷限於「前端開發」的領域中。所以本篇寫做的角度並非僅僅之前端開發爲主體探索,而是將 JavaScript 自己抽離出來,這也是本身逐步理清職業發展的一個重要改變。

本文經過學習和寫做分享對 Rome 進行了簡要的瞭解,但這還僅僅是入門。本身對 Babel 自己並不熟,還有不少學習過程當中產生的疑惑都沒法如今進行合適的解答,好比「Rome 和 Babel 的具體異同」、「如何看待 Rome 倉庫使用 Git 跟蹤 Node Modules」、「Rome 替代現有工具或進行集成方案的具體原理」以及「Rome 的打包流程有何特色」等,挖個坑能夠一塊兒交流。

不管最終是否使用 Rome,能引起對 JavaScript 工具鏈的從新思考也會頗有收穫。

最後,感謝你的閱讀,公衆號(@ningowood) 及配套羣聊歡迎加入,同時歡迎給如期更新了三期,即將支持線上 UI 界面瀏覽並提供更多拓展功能的「開源愛好者月刊(@ningowood/open-source-magazine)」倉庫點個 Star 吧!(Github 很久沒漲粉絲了,也歡迎關注我~)

@wechat/ningowood

參考資料

相關文章
相關標籤/搜索