Babel還是Node開發的「必需品」嗎?
- 2019 年 10 月 7 日
- 筆記
點擊上方藍字關注,關注後還可加入「前端交流群」共同進步

作者 | Joel Griffith Follow
譯者 | 王強
編輯 | 張之棟、Yonie
本文轉載自公眾號前端之巔(ID:frontshow)
現在做 Node 開發還需要「麻煩」的 Babel 嗎?毋庸置疑,Babel 曾經對構建和開發 Node.js 應用程式有過很大的影響,但隨著 Node.js 的原生功能不斷強大,Babel 或許也不再是 Node 開發的「必需品」。本文將主要介紹關於如何在 Node 開發中擺脫 Babel 的方法。
如果你是 Node.js 資深開發人員,乃至涉足了 React 或 Vue.js 等前端庫,那麼不用說你很有可能跟 Babel 打過交道。Babel 最初曾是 Reddit 上的一個不起眼的項目,但現在已經發展得如此壯大,甚至從根本上改變了我們構建和開發 Node.js 應用程式的方式。
Babel項目: https://www.reddit.com/r/javascript/comments/2mxt5f/6to5_js_a_readable_es6_compiler/
很難準確地形容 Babel 的影響力到底有多大,因為現在它被拆分成了許多小包的形式,但只要看看 npm 的 @Babel/core 包就足見一斑了(提示:它一周的下載量差不多有 800 萬次,而 React 才不過 500 萬而已!)。
Babel 的確取得了驚人的成就,但它也在某些方面很讓人胃疼。首先,現在你得在你的應用程式或庫中引入一套構建系統。雖然這本身沒那麼可怕,但這樣做確實帶來了許多額外的複雜性和問題:你有沒有同時打包兼容 ES 和 ES20XX 版本的庫呢?你想要輸出到 ECMAScript 規範的哪一個「階段」?此外我個人覺得最典型的例子就是,你當前的工具集怎樣與它配合呢(調試之類的事情)?
當然,我們不能忘了我們的源映射(source maps)老朋友,我們可以用它智慧地從已轉換的程式碼逆向到源程式碼上。如果你正在同時為瀏覽器和 Node.js 構建,那麼事情就更複雜了,因為你還必須為瀏覽器打包一個版本——啊,好麻煩!
但我要說的是,也許你根本就用不著 Babel。以前只有 Babel 才有的許多酷炫的玩意兒現在都成了 Node.js 的原生功能,也就是說你可以省掉許多依賴項和構建步驟,甚至用不著第三方系統幫你做自動編譯了。
認真讀完這篇文章後,我希望大家能和我達成共識,看到 Node 開發的「復興」時代就要來臨了——我們不再需要什麼構建系統,自然也用不著 Babel!
擺脫 Babel 的第一步:處理模組
JavaScript 開發中比較讓人頭疼的一部分就是它的模組系統。有些人可能不太熟悉這塊,比如說你可能會在 Web 上看到很多這樣的語法:
export const double = (number) => number * 2; export const square = (number) => number * number;
但要在 Node 中運行上面的程式碼卻不加任何類型的 Babel-ifying(或標誌),就會出現以下錯誤:
export const double = (number) => number * 2; ^^^^^^ SyntaxError: Unexpected token export
有很多年經歷的開發人員可能會回想起 requirejs 和 commonjs 語法流行的時代,彼時和我們現在處理 commonjs 和 ECMAScript 模組語法的方式非常相似。
但是如果你在用 Node 比較新的版本——哪怕是 8.0 也行——那麼無需任何轉換或 Babel 也能用 ECMAScript 模組。你只需使用 –experimental-modules 開關啟動你的應用程式即可:
node --experimental-modules my-app.mjs
當然,最關鍵的是要注意——至少在版本 8 和 10 中——你的文件必須用 mjs 這個擴展名,表明它們是 ECMAScript 模組而不是 CommonJS。在 Node 12 中情況要好得多,只需將一個新屬性附加到你的應用程式(或庫)的 pacakge.json 即可:
// package.json { "name": "my-application", "type": "module" // Required for ECMASCript modules }
在 Node.js 12 及更高版本上使用 type 方法時,它還有一個額外的好處,就是載入的所有依賴項都支援 ECMAScript 模組。因此隨著越來越多的庫遷移到「原生」JavaScript,你用不著再擔心當不同的庫打包不同的模組系統時如何處理 import 或 require 了。
可以在 Node 的文檔站點上閱讀更多資訊: https://nodejs.org/docs/latest-v12.x/api/esm.html#esm_enabling
擺脫 Babel 的第二步:使用現代化的非同步控制流程
如果你一直在愉快地使用 Node.js 中更現代化的非同步控制流方法(名為 Promise 和搭配它們的 async/await),一個好消息是它們自 Node 8 以來就獲得了原生支援!
良好的控制流(特別是針對並行發出請求等操作)是編寫快速且可維護的 Node 應用程式的關鍵所在。要在 Node 8 中使用像 Promise 或 await 這樣的東西,其實你什麼都用不著準備:
// log.js async function delayedLogger(...messages) { return new Promise((resolve) => { setImmediate(() => { console.debug(...messages); resolve(true); }); }); } async function doLogs() { delayedLogger('2. Then I run next!'); console.log('1. I run first!'); await delayedLogger('3. Now I run third because I "await"'); console.log('4. And I run last!'); } doLogs();
下面這個例子現在很容易就能實現:
node log.js
用不著特殊的開關,也不用更新你的 package.json——直接就能搞定!不僅如此,你甚至可以使用這些原生 Promise 嘗試捕獲未捕獲的異常,萬一你的應用程式出現問題也能即時發現:
process.on('unhandledRejection', (reason, promise) => { console.log('Unhandled Rejection at:', promise, 'nMessage:', reason); }); async function willThrowErrors() { return new Promise(function shouldBeCaught(resolve, reject) { reject('I should be caught and handled with!'); }); } willThrowErrors();
雖說這很好用,但如果我們需要深入了解非同步調用堆棧並查看拋出的內容和背後的機制,有時就會很讓人頭疼。要啟用非同步堆棧跟蹤,你需要升級到 Node 12 並對特定版本使用 –async-stack-traces 開關。
成功啟用後,你就可以更容易地推斷出錯誤的來源,並找出問題的根源所在。舉個例子,像下面這樣的程式很難看出它到底是怎麼出錯的:
// app.js async function sleep(num) { return new Promise((resolve) => { setTimeout(resolve, num); }); } async function execute() { await sleep(10); await stepOne(); } async function stepOne() { await sleep(10); await stepTwo(); } async function stepTwo() { await sleep(10); await stepThree(); } async function stepThree() { await sleep(10); throw new Error('Oops'); } execute() .then(() => console.log('success')) .catch((error) => console.error(error.stack));
在 Node 10 中運行它將返回以下跟蹤:
$ node temp.js --async-stack-traces Error: Oops at stepThree (/Users/joelgriffith/Desktop/app.js:24:11)
如果我們切換到 Node 12 上就能獲得更好的輸出,可以清楚地看到調用的結構:
$ node temp.js --async-stack-traces Error: Oops at stepThree (/Users/joelgriffith/Desktop/temp.js:24:11) at async stepTwo (/Users/joelgriffith/Desktop/temp.js:19:5) at async stepOne (/Users/joelgriffith/Desktop/temp.js:14:5) at async execute (/Users/joelgriffith/Desktop/temp.js:9:5)
擺脫 Babel 第三步:留下語法糖!
Babel 的一大好處就是它從 ES6 開始這麼多年積累的一大堆出色的語法糖。有了這些便利,我們就能用更易讀和更簡潔的方式執行常用操作。但更讓我高興的是,自 Node 的第 6 版以來,大多數語法糖都能直接用了。
我最喜歡的例子之一是解構賦值。這個小捷徑做出的下面這種效果更容易理解,並且不需要任何構建系統就能在 Node 中正常運作:
const letters = ['a', 'b', 'c']; const [a, b, c] = letters; console.log(a, b, c);
如果你只關心第三個元素,那麼下面這種程式碼也能用,就是看起來有點難看。
const stuff = ['boring', 'boring', 'interesting']; const [,, interesting] = stuff; console.log(interesting);
提到語法糖,對象解構也是開箱即用的:
const person = { name: 'Joel', occupation: 'Engineer', }; const personWithHobbies = { ...person, hobbies: ['music', 'hacking'], }; console.log(personWithHobbies);
不過對象解構需要 Node 8 以上版本,而數組解構最早獲得支援的版本是 Node 6。
最後,現在 Node 6 及以上版本完整支援默認參數了(這是這個語言以前缺少的重要功能)。它會省去程式中(以及 Babel 的轉換輸出)的大量 typeof 檢查,因此你可以執行以下操作:
function messageLogger(message, level = 'debug >') { console.log(level, message); } messageLogger('Cool it works!'); messageLogger('And this also works', 'error >');
其實 Node 中能用的東西太多了,上面舉的這些例子也不過是皮毛而已:此外模板字面量、反引號(多行字元串),胖箭頭,甚至是 class 關鍵字都準備好了。
別急,還有呢!
擺脫不必要的依賴項是提高應用程式安全性和可維護性的一個好辦法。你不再需要依賴外部維護的軟體,不需要等待生態系統進化,於是就能更快地前進。除此之外,移除 Babel 後你實際上也在部署更易讀的程式碼。
例如,Babel 有時會在程式文件的開頭注入大量的 polyfill。雖然這些幫助程式在大多數情況下完全無害,但它可能會把新手或不熟悉這種程式碼的人們繞糊塗了。這條規律一般來說沒錯:如果新手會被某件事物弄糊塗,那麼它可能就不應該加到你的項目里。
如果有人正在使用你的軟體包,想要確定問題是來自你的程式碼還是來自轉換器注入的幫助程式就更複雜了。如果你的最終輸出中注入的程式碼沒那麼多,你也能更好地理解正在構建的程式底層是怎樣的結構。
不管是 Babel 也好,其他依賴項也罷,要採用或者拋棄它們都要考慮一點,那就是責任。不管什麼時候,只要你引入了沒有親自閱讀或了解過的程式碼,都可能會出一些意料之外的問題。比如說依賴關係太複雜導致 npm install 速度緩慢,比如說模組需要在線打猴子修補程式而導致程式啟動緩慢,又比如說出現問題卻沒能正確報告……為了避免這些麻煩,不用 Babel 這樣的包可能是最好的。
引入新的模組或構建過程不單單是我們個人的事情,更是我們團隊和項目要解決的問題,所以我希望你們更多地把它當作是一種責任(維護它、升級它,並意識到使用它的後果) ,而不僅僅是當成隨手拿來用的工具。
最後,為什麼你可能還是要用 Babel 呢
雖然 Node 已經進步了這麼多,但有時你可能還是要用 Babel 才行。如果你想體驗規範中「最新和最好的」那部分,那麼 Babel 是你唯一的選擇。如果你想無需改動整個構建管道就使用 TypeScript,那麼用 Babel 也能做到。
有時 Babel 的程式碼實際上比 Node 原生方法更快。通常來說這是源於 Node 維護者必須處理的一些邊緣情況,這些情況 Babel 不一定要考慮。再過幾年,我相信 Node 的性能優勢會覆蓋所有層面,但是新功能往往會比用戶手裡的實現慢很多。
最後,如果你需要向 Web 瀏覽器交付程式碼,那麼在可預見的未來你可能還得繼續使用 Babel。像 React 這樣的庫以及其他用來實現或增強語言的庫總歸需要一種方法來轉換為瀏覽器可理解的程式碼。
但如果你的用戶群主要使用的是現代化的瀏覽器,那麼放棄構建系統就是利大於弊的,能顯著縮小程式體積。這不僅能加快頁面載入,而且還能顯著提升性能表現,因為哪怕額外的 1KB 內容也可能花費大量時間來處理,畢竟每個位元組在執行之前都需要解析和驗證!
我希望本文能幫助你編寫更好、更快、更安全的 Node.js 應用程式——而且不用 Babel 也能寫出很多功能來!
英文原文: https://blog.logrocket.com/you-dont-need-babel-with-node/