原文:overreacted.io/how-does-th…
譯者:前端技術小哥前端
若是您的JavaScript代碼庫很是複雜,那麼您可能會想辦法在開發模式和生產模式中捆綁和運行不一樣代碼。react
在開發模式和生產模式中捆綁並運行不一樣的代碼是很是強大的。在開發模式中,React裏有許多預警,能夠幫助咱們在致使bug以前找到問題。然而,檢測此類錯誤所需的代碼一般會增長bundle文件的大小,並使應用程序運行得更慢。webpack
在開發模式中程序運行緩慢是能夠接受的。事實上,在開發過程當中減慢代碼的運行速度甚至多是有益的,由於它在必定程度上彌補了快速開發人員計算機和普通消費設備之間的差別。web
在生產中,咱們不想付出任何成本。所以,咱們在生產中省略了這些檢查。這是怎麼回事?讓咱們來看看。npm
在開發中運行不一樣代碼的確切方法取決於JavaScript構建管道(以及是否有)。Facebook是這樣的:編程
if (__DEV__) {
doSomethingDev();
} else {
doSomethingProd();
}
複製代碼
這裏,__DEV__不是一個真正的變量。它是一個常量,當瀏覽器的模塊被拼接在一塊兒時這個常量就被替換掉了。結果是這樣的:瀏覽器
// In development:
if (true) {
doSomethingDev(); // 👈
} else {
doSomethingProd();
}
// In production:
if (false) {
doSomethingDev();
} else {
doSomethingProd(); // 👈
}
複製代碼
在生產中,咱們還須要在代碼上運行一個壓縮器(例如,terser)。大多數JavaScript微引擎都會進行部分的死代碼消除,例如刪除if(false)分支。因此在生產中你只會看到:bash
// In production (after minification):
doSomethingProd();
複製代碼
(請注意,主流JavaScript工具如何有效地消除死碼是有諸多限制的,但又這是一個單獨的問題了。)服務器
雖然您可能沒有使用一個神奇的__DEV__常量,可是若是您使用一個流行的JavaScript捆綁器(如webpack),那麼可能還有其餘一些慣例能夠遵循。例如,一般這樣表示相同的模式:框架
if (process.env.NODE_ENV !== 'production') {
doSomethingDev();
} else {
doSomethingProd();
}
複製代碼
這正是使用捆綁器從NPM導入時React和Vue等庫使用的模式。(單個文件(script)標籤構建中將開發和生產版本做爲單獨的.js和.min.js文件提供。)
這一慣例最初來自Node.js。在Node.js中,有一個全局process變量將系統的環境變量做爲process.env對象的屬性公開。然而,當您在前端代碼庫中看到這個模式時,一般不涉及任何實際的process變量。
相反,整個process.env.NODE_ENV表達式在構建時被字符串文字替換,就像咱們的神奇的__DEV__變量:
// In development:
if ('development' !== 'production') { // true
doSomethingDev(); // 👈
} else {
doSomethingProd();
}
// In production:
if ('production' !== 'production') { // false
doSomethingDev();
} else {
doSomethingProd(); // 👈
}
複製代碼
由於整個表達式是常量('production' !== 'production'確保爲false),因此壓縮器也能夠刪除其餘分支。
// In production (after minification):
doSomethingProd();
複製代碼
這個麻煩就解決啦
注意,這對更復雜的表達式沒用:
let mode = 'production';
if (mode !== 'production') {
// 🔴 not guaranteed to be eliminated
}
複製代碼
因爲JavaScript語言的動態特性,JavaScript靜態分析工具不是很智能。當他們看到像mode這樣的變量而不是像false或'production' !== 'production'這樣的靜態表達式時,他們一般會放棄。
一樣,JavaScript在咱們使用頂級import語句時,死代碼消除常常不能正常地跨模塊邊界運做:
// 🔴 not guaranteed to be eliminated
import {someFunc} from 'some-module';
if (false) {
someFunc();
}
複製代碼
所以,咱們須要以一種很是機械的方式編寫代碼,使條件絕對靜態,並確保要消除的全部代碼都在其中。
要使全部這些正常運做,咱們的bundler須要執行process.env.NODE_ENV替換,並須要知道但願在哪一種模式中構建項目。
幾年前,咱們經常會忘記配置環境。因此咱們常常會看到一個處於開發模式的項目部署到生產模式中。這很糟糕,由於這會使網站加載和運行速度變慢。
在過去兩年中,狀況有了顯著的改善。例如,webpack添加了一個簡單的mode選項,而不是手動配置process.env.NODE_ENV替換。React DevTools如今還會在帶有開發模式的站點上顯示一個紅色圖標,這使得用戶更容易發現以及報告。(此處需翻譯圖片中的文字)(此頁面正在使用React開發構建模式。打開開發工具,React鍵將會出如今右側。注意:發構建模式並不適用於生產模式。確保在部署前使用生產構建模式 )
老是有這樣一種說法,即生產模式才應該被設置爲默認的,而開發模式須要是手動切入。就我我的而言,我不認爲這個論點有說服力。從開發模式的預警中獲益最多的人一般是庫的初學者。他們通常都不知道如何打開開發模式,而且會錯過開發模式早就能給出的bug的高能預警。
是的,性能問題很糟糕。但向終端用戶提供漏洞百出的體驗也是如此。例如,React key預警有助於防止犯錯,好比向錯誤的人發送消息或購買錯誤的產品。在禁用預警時進行開發對您和您的用戶都會帶來重大風險。若是默認狀況下它是關閉的,那麼當咱們找到切換鍵並打開它時,咱們將會面對過量的預警並須要清除。因此大多數人會把它切換回去。這就是爲何須要從一開始就打開它,而不是稍後才啓用它。
最後,即便選擇切入開發預警,而且開發人員知道早早的時候就要打開它們,咱們又回到最初的問題。有人可能會在部署到生產環境中時忘記關閉它們!咱們又回到了出發點。
就我我的而言,我相信可以顯示和使用正確模式的工具取決於咱們是在調試仍是部署。幾十年來,除了Web瀏覽器以外,幾乎全部其餘環境(不管是移動、桌面仍是服務器)都有可以加載和區分開發和生產構建。也許是時候讓JavaScript環境將這種區別視爲頭等需求了,而不是由庫提出並依賴於臨時約定。
說了這麼多的理論知識!讓咱們來看看實際操做:
if (process.env.NODE_ENV !== 'production') {
doSomethingDev();
} else {
doSomethingProd();
}
複製代碼
你們可能會好奇:若是前端代碼中沒有真正的process對象,爲何像React和Vue這樣的庫在npm構建中依賴它呢?(再次澄清一下:您能夠在瀏覽器中加載的(script)標籤,由React和Vue提供,不依賴於此。相反,咱們必須本身在開發.js和生產.min.js之間做選擇。下面部分提到的只是關於經過從npm導入它們將React或Vue與捆綁器一塊兒使用。)
在import和export語法標準化以前的不少年,存在着不止一種方式在競爭着來表達模塊之間的關係。Node.js推廣了require()和module.exports,稱爲CommonJS。早期在npm註冊表上發佈的代碼是爲Node.js編寫的。Express是(而且可能如今仍然是?)Node.js最受歡迎的服務器端框架,它使用NODE_ENV環境變量來啓用生產模式。其餘一些npm包採用了相同的慣例。
像browserify這樣的早期JavaScript捆綁包但願可以在前端項目中使用來自npm的代碼。(是的,當時幾乎沒有人使用npm做爲前端!你們能想象嗎?)所以他們將Node.js生態系統中已經存在的相同慣例擴展到前端代碼。
最初的「envify」轉換是在2013年發佈的。React是在那個時候開源的,並且在那個時代使用browserify的npm彷佛是捆綁前端CommonJS代碼的最佳解決方案。從一開始React就開始提供npm構建(包括(script)標記構建)。隨着React的流行,使用CommonJS模塊編寫模塊化JavaScript並經過npm發送前端代碼的作法也開始流行。
React須要在生產模式中刪除僅用於開發的代碼。Browserify已經爲這個問題提供瞭解決方案,所以React也採用了將process.env.NODE_ENV用於其npm構建的慣例。隨着時間的推移,許多其餘工具和庫,包括webpack和Vue,都作了一樣的事情。
到2019年,browserify已經失去了至關多的市場佔有率。可是,在構建步驟中用'development'或'production'替換process.env.NODE_ENV還是一種流行的慣例。(看看如何採用ES模塊做爲分發格式,而不只僅是創做格式,會改變方程式,這頗有意思。)
還有一件事情可能仍然讓你們感到困惑,在GitHub上的React源代碼中,咱們看到__DEV__被用做魔術變量。可是在npm的React代碼中,它使用process.env.NODE_ENV。這是怎麼回事? 過去,咱們在源代碼中使用__DEV__來匹配Facebook源代碼。很長一段時間,React被直接複製到Facebook代碼庫中,因此它須要遵循相同的規則。對於npm,咱們有一個構建步驟,在發佈以前用process.env.NODE_ENV !== 'production'直接替換__DEV__查驗。
這有時會帶來問題。有時,依賴於某些Node.js慣例的代碼模式在npm上運行良好,可是在Facebook崩潰了,反之亦然。
自React 16以來,咱們改變了咱們的方法。相反,咱們如今爲每一個環境編譯一個捆綁包(包括(script)標籤,npm和Facebook內部代碼庫)。所以,即便是針對npm的CommonJS代碼也會提早被編譯爲分開開發和生產捆綁包。
這意味着當React源代碼說if (DEV)時,咱們實際上爲每一個包生成了兩個捆綁包。一個已經預編譯了__DEV__ = true,另外一個預編譯了__DEV__ = false。npm上每一個包的入口點「決定」要導出哪一個包。 舉個例子:
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react.production.min.js');
} else {
module.exports = require('./cjs/react.development.js');
}
複製代碼
並且這是捆綁器將'development'或'production'插入字符串的惟一位置,這也是咱們的壓縮器將擺脫僅用於開發require的位置。
react.production.min.js 和 react.development.js都沒有任何process.env.NODE_ENV 查驗。這很棒,由於當實際在Node.js上運行時,訪問process.env會有點慢。提早在兩種模式下編譯捆綁包還可讓咱們更加一致地優化文件大小,不管您使用哪一種捆綁器或壓縮器。這就是它的工做原理!
我但願有一種更優秀的方式來作到這一點而不依賴於慣例,但咱們如今就這樣了。若是模式在全部JavaScript環境中都是一等的概念,而且若是瀏覽器有某種方式代表某些代碼在開發模式下運行當它們其實不該該被運行的時候,那麼它將是極好的。
另外一方面,一個項目中的慣例如何可以在整個生態系統中傳播是頗有趣的。在2010年 EXPRESS_ENV 成爲 NODE_ENV並在2013年擴展到前端。也許這個解決方案並不完美,但對於每一個項目,採用它的成本要低於說服其餘人作不一樣的事情的成本。這教授了關於採用自上而下和自下而上的寶貴經驗。理解這種動態的運行方式能夠區分紅功的標準化嘗試和失敗。
分離開發模式和生產模式是一種很是有用的技巧。我建議在您的庫和應用程序代碼中使用它,用於那些在生產環境中執行開銷太大,但在開發中執行卻頗有價值(並且經常很關鍵!)的檢查。對於任何強大的特性,都有一些方法會誤用它。
但願本文能幫助到您! 看以後
點贊,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓 -_-)
關注公衆號「新前端社區」,享受文章首發體驗!
每週重點攻克一個前端技術難點。