#[譯]JavaScript中的development模式怎麼實現javascript
原文連接: overreacted.io/how-does-th…html
譯註: 忽略了一些內容,仍是強烈建議閱讀下原文吧。前端
隨着你的 JavaScript
應用愈來愈複雜,你極可能會在 development
和 production
模式下,分別加載和執行不一樣的代碼邏輯。vue
可以在 development
和 production
模式下,分別打包或執行不一樣的代碼,是一種很是強大的能力。在 development
模式下,React
會給出關於你代碼的一些警告,一般狀況下,這些代碼極可能會致使bug。可是,去提早監測並給出警告的這些代碼,也增大了打包以後的JS文件大小,甚至會下降應用的性能。java
在開發階段,咱們能夠接受由於額外的監測代碼而致使的應用性能損耗。事實上,在開發中,代碼運行比production模式更加緩慢,甚至可能帶來一些好處,這讓開發者能體會到在一些性能低下的設備上,咱們應用的運行效果,正如你知道的,一般開發者的機器性能是優於大部分的用戶設備。node
在 production
模式下,咱們不能忍受這些額外的監測代碼帶來的性能損耗。所以,在 production
模式下, React
不會包含這些監測代碼。下面讓咱們來看看具體是怎麼作到的。react
實現 development
和 production
模式執行不一樣的代碼,這依賴於你的JS代碼編譯流程(固然前提是你有JS編譯流程……)。在Facebook,咱們大概這樣寫代碼:webpack
if (__DEV__) {
doSomethingDev();
} else {
doSomethingProd();
}
複製代碼
上面代碼裏,__DEV__
不是一個真實存在的變量。它在JS代碼編譯階段,會被一個常量來替換,一般在 development
下是 true
,在 production
模式下是 false
。在不一樣模式下,編譯出來的最終代碼長這樣:git
// In development:
if (true) {
doSomethingDev(); // 👈
} else {
doSomethingProd();
}
// In production:
if (false) {
doSomethingDev();
} else {
doSomethingProd(); // 👈
}
複製代碼
在 production
模式下,你一般會使用一些壓縮工具(好比 terser )來壓縮JS代碼。同時,大多數的JS代碼壓縮工具,都會進行一些 死碼消除 ,好比在生成的代碼中,刪除掉 if(false){}
這樣的代碼分支。 所以,在 production
模式下,通過壓縮以後,最終產出的代碼長這樣:github
// In production (after minification):
doSomethingProd();
複製代碼
你在實際項目中,用到的可能並非 __DEV__
這個標記常量,若是你用的是 webpack
這樣的JS編譯打包工具,那一般用的是另一種常量標記方式,來實現這個功能。好比,在 webpack
社區中,一般是這樣來區分 development
和 production
的代碼分支:
if (process.env.NODE_ENV !== 'production') {
doSomethingDev();
} else {
doSomethingProd();
}
複製代碼
當你使用一些打包工具(好比webpack)來從 npm
包的引入 React、Vue 這樣的類庫時,這些類庫裏區分 development
和 production
的方式和上面這段代碼是同樣的(譯註: 經過 process.env.NODE_ENV
來區分不一樣模式,應該是 web
前端開發中的一種約定了吧)。若是你是經過 <script>
標籤的方式,來直接加載已經提早編譯好的版本,那麼一般是以JS代碼的文件後綴 .js
和 .min.js
來區分 development
和 production
模式的代碼。
經過 process.env.NODE_ENV
標記來區分 development
和 production
環境的約定,最初是來自於 Node.js
。在 Node.js 中,有一個全局變量 process
,而且能夠經過 process.env
這個對象來訪問到代碼執行時候的環境變量。可是,在前端代碼(譯註:指運行在瀏覽器裏的那種JS代碼)裏,並不存在全局的 process
變量🤯。
實際上,相似咱們前面提到的 __DEV__
,process.env.NODE_ENV
也是在代碼編譯階段會被 development
或者 production
常量替換掉。替換以後的代碼以下:
// In development:
if ('development' !== 'production') { // true
doSomethingDev(); // 👈
} else {
doSomethingProd();
}
// In production:
if ('production' !== 'production') { // false
doSomethingDev();
} else {
doSomethingProd(); // 👈
}
複製代碼
從上面能夠看出,替換以後, if
裏的表達式是不變的('production' !== 'production'
的值永遠都是 false
),代碼壓縮工具可以移出掉對應的分支代碼。最終 production
模式下,壓縮以後的代碼是這樣的:
// In production (after minification):
doSomethingProd();
複製代碼
注意,若是表達式變得比上面複雜,代碼壓縮工具 不會 刪除掉那些死碼。看下面這個栗子:
let mode = 'production';
if (mode !== 'production') {
// 🔴 not guaranteed to be eliminated
}
複製代碼
因爲JavaScript語言自己的動態特性,JS代碼的靜態分析工具不是太智能。當靜態分析工具在遇到一個變量(好比是 mode
)時,他們一般什麼也不會作。只有當他們遇到明確的常量表達式(好比 false
或者 'production' !== 'production'
)時,他們能夠大膽的移出對應的代碼分支。
類似的,JavaScript壓縮工具的的死碼刪除功能,在面臨跨模塊引用時,也不會生效,好比下面的例子:
// 🔴 not guaranteed to be eliminated
import {someFunc} from 'some-module';
if (false) {
someFunc();
}
複製代碼
所以,想要利用JavaScript壓縮工具的死碼刪除功能,你必須確保條件語句是常量,沒有包含變量,而且對應的分支代碼必須是在同一個 module
內部。
要實現這些功能,你的JavaScript打包工具須要根據你的編譯環境(development
或者 production
)來替換 process.env.NODE_ENV
。
在幾年之前,開發者嚐嚐會忘記在編譯 production
包時設置對應的模式,這致使不少 development
模式下編譯的 React
代碼在生產環境被使用。
在生產環境使用 development
模式下的代碼,會增長咱們的JS文件大小,減慢頁面加載速度以及最終的代碼執行性能。
在過去的兩年,這種情形有所緩解。好比,webpack
引入了一個簡單的 mode
配置項,來避免開發者手動去設置 process.env.NODE_ENV
的值。React開發者工具,在檢測到頁面使用了 development
模式的React代碼時,會顯示一個紅色的圖標,這讓咱們很容易就察覺問題。
一些流行的代碼初始化工具,好比 Create React App,Next/Nuxt,Vue Cli,Gatsby等等,直接將development
和 production
模式下的打包命令分紅2個(好比開發模式是 npm start
,生產環境使用 npm run build
),進一步避免開發者去手動設置變量。一般來說,只有 production
模式產出的代碼,會被部署到生產環境,所以,開發者不會再遇到之前的問題了。
一直以來都有一些爭論,認爲應該把 production
模式設置爲編譯工具的默認模式,development
模式成爲須要開發者明確指定的。我我的認爲,這個觀點不是頗有說服力(譯註:我我的認爲默認是 production
模式比較好😅)。那些可以從 development
模式下的各類警告信息收益的開發者,一般是使用類庫的新手。他們可能並不知道怎麼開啓 development
模式,由此可能會遇到不少原本能夠被那些警告信息所避免的bug。
誠然,development
模式的代碼性能比較低,可能會讓一些開發者苦惱;但這也比發佈帶有bug的功能要強。舉個例子,React 列表的 key 警告 可以幫助開發者避免一些bug,好比發送了一條消息給錯誤的對象,或者購買了另外一個商品。若是在開發過程當中,沒有這些警告信息,開發者很難在開發階段就發現並解決一些潛在的bug。若是這些警告信息是默認關閉的(默認開啓 production
模式的話),當你在某個時刻,切換到 development
模式時,你講看到太多的警告信息,這可能讓你不太想去修復這些問題。不修復問題,警告信息又太多,怎麼辦呢,大多數開發者可能會關閉警告,從新設置爲 production
模式。這就是爲何,development
模式應該是默認開啓的,這讓開發者能夠一步步的發現並修復警告,而不是忽然面對一堆的警告信息變得無所適從。
若是development
模式是可選的,而且開發者在初始時候就打開了 development
模式,咱們就又回到了最初的那個問題:一些開發者可能會忘記在上線的時候,切換到 production
模式,從而將 development
模式的代碼發佈到了生產環境。
嗯,理論的東西說完啦(終於)
讓咱們再來看看這段代碼:
if (process.env.NODE_ENV !== 'production') {
doSomethingDev();
} else {
doSomethingProd();
}
複製代碼
你可能想知道:既然前端代碼中,根本就不存在全局的 process
對象,爲何經過 npm
包引入的 React/Vue 這些類庫,還要去依賴 process.env.NODE_ENV
呢?
(再次說明下:若是你選擇的是直接經過 <script>
標籤引入已經提早編譯好的代碼,你須要根據文件後綴 .js
仍是 .min.js
來手動選擇 development
版本仍是production
版本。後文要講的,都是從 npm
包通過打包工具來引入 React/Vue 的場景!)
和軟件編程領域的不少事情同樣,採用 process.env.NODE_ENV
來標記不一樣環境,是歷史緣由。咱們繼續遵照這個約定,僅僅是由於它已經普遍地被大多數工具支持。換一個別的標記,沒有太大意義,也沒有社區工具的支持。
那麼這段背後的歷史是什麼呢?
在 import
和 export
被加入JS語言標準以前的不少年,存在好幾個相互競爭的JS模塊化方案。Node.js 採用了來自 CommonJS 的 require()
和 module.exports
。
最初發布到 npm
上的包,都是給 Node.js 使用的,並非給前端代碼用的。做爲曾經是(或許如今也是?)最流行的 Node.js 服務端框架,Express 使用NODE_ENV這個環境變量 來開啓 production
模式。在這以後,其餘的一些 npm
包也採用了 process.env.NODE_ENV
來區分不一樣環境。
像 browserify 這樣的早期JavaScript代碼打包工具但願能在前端工程中,引入 npm
包提供的代碼。(是的,那時候 幾乎沒有前端開發者使用npm來發布本身的代碼,你能想象到嗎?) 。所以,這些早期的打包工具採納了 Node.js 生態中已經普遍採用的約定,使用 process.env.NODE_ENV
來區分不一樣環境。
從 React 發佈的那天起,在提供預編譯好的JS代碼以後,React 還提供了對應的 npm
版本。伴隨着 React 的流行,愈來愈多的前端開發者也基於 CommonJS 的方式來發布前端代碼到 npm 倉庫。
React 須要在 production
模式中移出掉 development
模式下的一些代碼。正好 Browserify 對這個問題有了解決方案,所以 React 也採用了這個方案,在 npm 包中經過 process.env.NODE_ENV
來區分不一樣環境。到後來,愈來愈多的類庫和工具,好比 Vue 和 webpack,都採納了這個方案。
到2019年,browserify 已經再也不流行。可是,在JavaScript代碼編譯階段,將 process.env.NODE_ENV
替換成 development
或 production
繼續像以前同樣被社區普遍採用。
還有一個事情,可能會讓你困惑。在 React 的 Github 源碼中,你會發現 __DEV__
這樣的環境標記。可是在 npm 上的 React 代碼裏,使用的倒是 process.env.NODE_ENV
。這是神馬狀況呢?
因爲歷史緣由,爲了和 Facebook 內部代碼保持一致,咱們使用了 __DEV__
這個標記。在很長的一段時間裏,React 代碼都是直接被拷貝到 Facebook 的代碼倉庫,所以須要遵照相同的規則。所以,在發佈到 npm 以前,咱們會有一個替換步驟,將 __DEV__
替換爲 process.env.NODE_ENV !== 'production'
。
在一些場景下,這會致使問題。一段基於 Node.js 約定的代碼,在 npm 下運行的很好,可是在 Facebook 內部卻出錯;或者是相反的問題。
所以,從 React 16 開始,咱們作出了一些調整。針對每一個環境(包括 <script>
引入的預編譯代碼,npm,以及 Facebook內部倉庫),咱們都會 編譯對應的bundle 。
這意味着,針對 React 中的源碼 if (__DEV__)
,咱們實際上針對每一個包都會生成2個bundle:一個是設置了 __DEV__ = true
編譯出來的bundle;另外一個是設置了 __DEV__ = false
編譯的bundle。
舉個例子:
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react.production.min.js');
} else {
module.exports = require('./cjs/react.development.js');
}
複製代碼
這個入口JS,是唯一一處你的打包工具須要替換 process.env.NODE_ENV
的地方,也是在這個入口JS,打包工具會忽略掉 development
的 require
。
react.production.min.js
和 react.development.js
兩個文件中,都 沒有 包含 process.env.NODE_ENV
這樣的檢查代碼。這很好,由於在 Node.js 裏訪問 process.env
是 有些慢的。提早編譯好兩個模式下的 React 代碼,也能讓咱們的最初JS文件大小,無論咱們使用哪一個打包工具,都 更加的一致。
根據運行環境執去執行不一樣的代碼,是一項很是強大的技術。我推薦你在你的類庫或者APP代碼中,也使用這個技術,只在 development
模式下去作一些消耗性能的校驗和警告,在 production
模式下移除這些代碼。
再次說明: 有一些片斷沒有搬過來,強烈推薦看看原文!
原文連接: overreacted.io/how-does-th…
最後慣例,歡迎你們star咱們的人人貸大前端團隊博客,全部的文章還會同步更新到知乎專欄 和 掘金帳號,咱們每週都會分享幾篇高質量的大前端技術文章。若是你喜歡這篇文章,但願能動動小手給個贊。