最近2018中文字幕在日韩欧美国产成人片_国产日韩精品一区二区在线_在线观看成年美女黄网色视频_国产精品一区三区五区_国产精彩刺激乱对白_看黄色黄大色黄片免费_人人超碰自拍cao_国产高清av在线_亚洲精品电影av_日韩美女尤物视频网站

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
我從17wstar的Vuejs中學(xué)到了什么?

 框架設(shè)計遠(yuǎn)沒有大家想的那么簡單,并不是說只把功能開發(fā)完成,能用就算完事兒了,這里面還是有很多學(xué)問的。比如說,我們的框架應(yīng)該給用戶提供哪些構(gòu)建產(chǎn)物?產(chǎn)物的模塊格式如何?當(dāng)用戶沒有以預(yù)期的方式使用框架時是否應(yīng)該打印合適的警告信息從而提升更好的開發(fā)體驗,讓用戶快速定位問題?開發(fā)版本的構(gòu)建和生產(chǎn)版本的構(gòu)建有何區(qū)別?熱跟新(HMR:Hot Module Replacement)需要框架層面的支持才行,我們是否也應(yīng)該考慮?再有就是當(dāng)你的框架提供了多個功能,如果用戶只需要其中幾個功能,那么用戶是否可以選擇關(guān)閉其他功能從而減少資源的打包體積?所有以上這些問題我們都會在本節(jié)內(nèi)容進(jìn)行討論。

[[361379]]

本文內(nèi)容需要大家對常用的模塊打包工具有一定的使用經(jīng)驗,尤其是 rollup.js 以及 webpack。如果你只用過或了解過其中一個也沒關(guān)系,因為它們很多概念其實是類似的。如果你沒有使用任何模塊打包工具那么需要你自行去了解一下,至少有了初步認(rèn)識之后再來看本節(jié)內(nèi)容會更好一些。

提升用戶的開發(fā)體驗

衡量一個框架是否足夠優(yōu)秀的指標(biāo)之一就是看它的開發(fā)體驗如何,我們拿 Vue3 舉個例子:

  
 
 
 
  1. createApp(App).mount('#not-exist') 

當(dāng)我們創(chuàng)建一個 Vue 應(yīng)用并試圖將其掛載到一個不存在的 DOM 節(jié)點時就會得到一個警告信息:

w arn

從這條信息中我們得知掛載失敗了,并說明了失敗的原因:Vue 根據(jù)我們提供的選擇器無法找到相應(yīng)的 DOM 元素(返回 null ),正式因為這條信息的存在使得我們能夠清晰且快速的了解并定位問題,可以試想一下如果 Vue 內(nèi)部不做任何處理,那么很可能得到的是一個 JS 層面的錯誤信息,例如: Uncaught TypeError: Cannot read property 'xxx' of null ,但是根據(jù)此信息我們很難知道問題出在哪里。

所以在框架設(shè)計和開發(fā)的過程中,提供友好的警告信息是至關(guān)重要的,如果這一點做得不好那么很可能經(jīng)常收到用戶的抱怨。始終提供友好的警告信息不僅能夠快速幫助用戶定位問題,節(jié)省用戶的時間,還能夠為框架收獲良好的口碑,讓用戶認(rèn)為你是非常專業(yè)的。

在 Vue 的源碼中,你經(jīng)常能夠看到 warn() 函數(shù)的調(diào)用,例如上面圖片中的信息就是由這句 warn() 函數(shù)調(diào)用打印的:

  
 
 
 
  1. warn( 
  2.   `Failed to mount app: mount target selector "${container}" returned null.` 

對于 warn() 函數(shù)來說,由于它需要盡可能的提供有用的信息,因此它需要收集當(dāng)前發(fā)生錯誤的組件的組件棧信息,所以如果你去看源碼你會發(fā)現(xiàn)有些復(fù)雜,但其實最終就是調(diào)用了 console.warn() 函數(shù)。

對于開發(fā)體驗來說,除了提供必要的警告信息,還有很多其他方面可以作為切入口,可以進(jìn)一步提升用戶的開發(fā)體驗。例如在 Vue3 中當(dāng)我們在控制臺打印一個 Ref 數(shù)據(jù)時:

  
 
 
 
  1. const count = ref(0) 
  2. console.log(count) 

打開控制臺查看輸出,如下圖所示:

沒有任何處理的輸出

可以發(fā)現(xiàn)非常的不直觀,當(dāng)然我們可以直接打印 count.value ,這樣就只會輸出 0 ,但是有沒有辦法在打印 count 的時候讓輸出的信息更有好呢?當(dāng)然可以,瀏覽允許我們編寫自定義的 formatter ,從而自定義輸出的形式。在 Vue 的源碼中你可以搜索到名為 initCustomFormatter 的函數(shù),這個函數(shù)就是用來在開發(fā)環(huán)境下初始化自定義 formatter 的,以 chrome 為例我們可以打開 devtool 的設(shè)置,然后勾選 Console -> Enable custom formatters :

然后刷新瀏覽器后查看控制臺,會發(fā)現(xiàn)輸出的內(nèi)容變得非常直觀:

控制框架代碼的體積

框架的大小也是衡量框架的標(biāo)準(zhǔn)之一,在實現(xiàn)同樣功能的情況下當(dāng)然是用越少的代碼越好,這樣體積就會越小,最后瀏覽器加載資源的時間也就越少。這時我們不禁會想,提供越完善的警告信息就意味著我們要編寫更多的代碼,這不是與控制代碼體積相駁嗎?沒錯,所以我們要想辦法解決這個問題。

如果我們?nèi)タ?Vue 的源碼會發(fā)現(xiàn),每一個 warn() 函數(shù)的調(diào)用都會配合 __DEV__ 常量的檢查,例如:

  
 
 
 
  1. if (__DEV__ && !res) { 
  2.   warn( 
  3.     `Failed to mount app: mount target selector "${container}" returned null.` 
  4.   ) 

可以看到,打印警告信息的前提是: __DEV__ 這個常量一定要為真,這里的 __DEV__ 常量就是達(dá)到目的的關(guān)鍵。

Vue 使用的是 rollup.js 對項目進(jìn)行構(gòu)建的,這里的 __DEV__ 常量實際上是通過 rollup 的配置來預(yù)定義的,其功能類似于 webpack 中的 DefinePlugin 插件。

Vue 在輸出資源的時候,會輸出兩個版本的資源,其中一個資源用于開發(fā)環(huán)境,如 vue.global.js ;另一個與其對應(yīng)的用于生產(chǎn)環(huán)境,如: vue.global.prod.js ,通過文件名稱我們也能夠區(qū)分。

當(dāng) Vue 構(gòu)建用于開發(fā)環(huán)境的資源時,會把 __DEV__ 常量設(shè)置為 true ,這時上面那段輸出警告信息的代碼就等價于:

  
 
 
 
  1. if (true && !res) { 
  2.   warn( 
  3.     `Failed to mount app: mount target selector "${container}" returned null.` 
  4.   ) 

可以看到這里的 __DEV__ 被替換成了字面量 true ,所以這段代碼在開發(fā)環(huán)境是肯定存在的。

當(dāng) Vue 構(gòu)建用于生產(chǎn)環(huán)境的資源時,會把 __DEV__ 常量設(shè)置為 false ,這時上面那段輸出警告信息的代碼就等價于:

  
 
 
 
  1. if (false && !res) { 
  2.   warn( 
  3.     `Failed to mount app: mount target selector "${container}" returned null.` 
  4.   ) 

可以看到 __DEV__ 常量被替換為字面量 false ,這時我們發(fā)現(xiàn)這段分支代碼永遠(yuǎn)都不會執(zhí)行,因為判斷條件始終為假,這段永遠(yuǎn)不會執(zhí)行的代碼被稱為 Dead Code ,它不會出現(xiàn)在最終的產(chǎn)物中,在構(gòu)建資源的時候就會被移除,因此在 vue.global.prod.js 中是不會存在這段代碼的。

這樣我們就做到了 在開發(fā)環(huán)境為用戶提供友好的警告信息的同時,還不會增加生產(chǎn)環(huán)境代碼的體積 。

框架要做到良好的 Tree-Shaking

上文中我們提到通過構(gòu)建工具設(shè)置預(yù)定義的常量 __DEV__ ,就能夠做到在生產(chǎn)環(huán)境使得框架不包含打印警告信息的代碼,從而使得框架自身的代碼量變少。但是從用戶的角度來看,這么做仍然不夠,還是拿 Vue 來舉個例子,我們知道 Vue 提供了內(nèi)置的組件例如  ,如果我們的項目中根本就沒有使用到該組件,那么  組件的代碼需要包含在我們項目最終的構(gòu)建資源中嗎?答案是當(dāng)然不需要,那如何做到這一點呢?這就不得不提到本節(jié)的主角 Tree-Shaking 。

那什么是 Tree-Shaking 呢?在前端領(lǐng)域這個概念因 rollup 而普及,簡單的說所謂 **Tree-Shaking **指的就是消除哪些永遠(yuǎn)不會執(zhí)行的代碼,也就是排除 dead-code ,現(xiàn)在無論是 rollup 還是 webpack 都支持 Tree-Shaking 。

想要實現(xiàn) Tree-Shaking 必須滿足一個條件,即模塊必須是 ES Module,因為 Tree-Shaking 依賴 ESM 的靜態(tài)結(jié)構(gòu)。我們使用 rollup 通過一個簡單的例子看看 Tree-Shaking 如何工作,我們 demo 的目錄結(jié)構(gòu)如下:

  
 
 
 
  1. ├── demo 
  2. │   └── package.json 
  3. │   └── input.js 
  4. │   └── utils.js 

首先安裝 rollup :

  
 
 
 
  1. yarn add rollup -D # 或者 npm install rollup -D 

下面是 input.js 和 utils.js 文件的內(nèi)容:

  
 
 
 
  1. // input.js 
  2. import { foo } from './utils.js' 
  3. foo() 


  
 
 
 
  1. // utils.js 
  2. export function foo(obj) { 
  3.   obj && obj.foo 
  4. export function bar(obj) { 
  5.   obj && obj.bar 

代碼很簡單,我們在 utils.js 文件中定義并導(dǎo)出了兩個函數(shù),分別是 foo 和 bar ,然后在 input.js 中導(dǎo)入了 foo 函數(shù)并執(zhí)行,注意我們并沒有導(dǎo)入 bar 函數(shù)。

接著我們執(zhí)行如下命令使用 rollup 構(gòu)建:

  
 
 
 
  1. npx rollup input.js -f esm -o bundle.js 

這句命令的意思是以 input.js 文件問入口,輸出 ESM 模塊,輸出的文件名叫做 bundle.js 。命令執(zhí)行成功后,我們打開 bundle.js 來查看一下它的內(nèi)容:

  
 
 
 
  1. // bundle.js 
  2. function foo(obj) { 
  3.   obj && obj.foo 
  4. foo(); 

可以看到,其中并不包含 bar 函數(shù),這說明 Tree-Shaking 起了作用,由于我們并沒有使用 bar 函數(shù),因此它作為 dead-code 被刪除了。但是如果我們仔細(xì)觀察會發(fā)現(xiàn), foo 函數(shù)的執(zhí)行也沒啥意義呀,就是讀取了對象的值,所以它執(zhí)行還是不執(zhí)行也沒有本質(zhì)的區(qū)別呀,所以即使把這段代碼刪了,也對我們的應(yīng)用沒啥影響,那為什么 rollup 不把這段代碼也作為 dead-code 移除呢?

這就涉及到 Tree-Shaking 中的第二個關(guān)鍵點,即副作用。如果一個函數(shù)調(diào)用會產(chǎn)生副作用,那么就不能將其移除。什么是副作用?簡單地說副作用的意思是當(dāng)調(diào)用函數(shù)的時候,會對外部產(chǎn)生影響,例如修改了全局變量。這時你可能會說,上面的代碼明顯是讀取對象的值怎么會產(chǎn)生副作用呢?其實是有可能的,想想一下如果 obj 對象是一個通過 Proxy 創(chuàng)建的代理對象那么當(dāng)我們讀取對象屬性時就會觸發(fā) Getter ,在 Getter 中是可能產(chǎn)生副作用的,例如我們在 Getter 中修改了某個全局變量。而到底會不會產(chǎn)生副作用,這個只有代碼真正運行的時候才能知道, JS 本身是動態(tài)語言,想要靜態(tài)的分析哪些代碼是 dead-code 是一件很有難度的事兒,上面只是舉了一個簡單的例子。

正因為靜態(tài)分析 JS 代碼很困難,所以諸如 rollup 等這類工具都會給我提供一個機(jī)制,讓我們有能力明確的告訴 rollup :”放心吧,這段代碼不會產(chǎn)生副作用,你可以放心移除它“,那具體怎么做呢?如下代碼所示,我們修改 input.js 文件:

  
 
 
 
  1. import {foo} from './utils' 
  2.  
  3. /*#__PURE__*/ foo() 

注意這段注釋代碼 /*#__PURE_*_/ ,該注釋的作用就是用來告訴 rollup 對于 foo() 函數(shù)的調(diào)用不會產(chǎn)生副作用,你可以放心的對其進(jìn)行 Tree-Shaking,此時再次執(zhí)行構(gòu)建命令并查看 bundle.js 文件你會發(fā)現(xiàn)它的內(nèi)容是空的,這說明 Tree-Shaking 生效了。

基于這個案例大家應(yīng)該明白的是,在編寫框架的時候我們需要合理的使用 /*#__PURE_*_/ 注釋,如果你去搜索 Vue 的源碼會發(fā)現(xiàn)它大量的使用了該注釋,例如下面這句:

  
 
 
 
  1. export const isHTMLTag = /*#__PURE__*/ makeMap(HTML_TAGS) 

也許你會覺得這會不會對編寫代碼帶來很大的心智負(fù)擔(dān)?其實不會,這是因為通常產(chǎn)生副作用的代碼都是模塊內(nèi)函數(shù)的頂級調(diào)用,什么是頂級調(diào)用呢?如下代碼所示:

  
 
 
 
  1. foo() // 頂級調(diào)用 
  2.  
  3. function bar() { 
  4.   foo() // 函數(shù)內(nèi)調(diào)用 

可以看到對于頂級調(diào)用來說是可能產(chǎn)生副作用的,但對于函數(shù)內(nèi)調(diào)用來說只要函數(shù) bar 沒有被調(diào)用,那么 foo 函數(shù)的調(diào)用當(dāng)然不會產(chǎn)生副作用。因此你會發(fā)現(xiàn)在 Vue 的源碼中,基本都是在一些頂級調(diào)用的函數(shù)上使用 /*#__PURE__*/ 注釋的。當(dāng)然該注釋不僅僅作用與函數(shù),它可以使用在任何語句上,這個注釋也不是只有 rollup 才能識別,webpack 以及壓縮工具如 terser 都能識別它。

框架應(yīng)該輸出怎樣的構(gòu)建產(chǎn)物

上文中我們提到 Vue 會為開發(fā)環(huán)境和生產(chǎn)環(huán)境輸出不同的包,例如 vue.global.js 用于開發(fā)環(huán)境,它包含了必要的警告信息,而 vue.global.prod.js 用于生產(chǎn)環(huán)境,不包含警告信息。實際上 Vue 的構(gòu)建產(chǎn)物除了有環(huán)境上的區(qū)分之外,還會根據(jù)使用場景的不同而輸出其他形式的產(chǎn)物,這一節(jié)我們將討論這些產(chǎn)物的用途以及在構(gòu)建階段如何輸出這些產(chǎn)物。

不同類型的產(chǎn)物一定是有對應(yīng)的需求背景的,因此我們從需求講起。首先我們希望用戶可以直接在 html 頁面中使用  

  •    
  •  
  • 為了能夠?qū)崿F(xiàn)這個需求,我們就需要輸出一種叫做 IIFE 格式的資源, IIFE 的全稱是 Immediately Invoked Function Expression ,即”立即調(diào)用的函數(shù)表達(dá)式“,可以很容易的用 JS 來表達(dá):

      
     
     
     
    1. (function () { 
    2.   // ... 
    3. }()) 

    如上代碼所示,這就是一個立即執(zhí)行的函數(shù)表達(dá)式。實際上 vue.globale.js 文件就是 IIFE 形式的資源,大家可以看一下它的代碼結(jié)構(gòu):

      
     
     
     
    1. var Vue = (function(exports){ 
    2.   // ... 
    3.  exports.createApp = createApp; 
    4.   // ... 
    5.   return exports 
    6. }({})) 

    這樣當(dāng)我們使用  

    為了輸出 ESM 格式的資源就需要我們配置 rollup 的輸出格式為: format: 'esm' 。

    你可能已經(jīng)注意到了,為什么 vue.esm-browser.js 文件中會有 -browser 字樣,其實對于 ESM 格式的資源來說,Vue 還會輸出一個 vue.esm-bundler.js 文件,其中 -browser 變成了 -bundler 。為什么這么做呢?我們知道無論是 rollup 還是 webpack 在尋找資源時,如果 package.json 中存在 module 字段,那么會優(yōu)先使用 module 字段指向的資源來代替 main 字段所指向的資源。我們可以打開 Vue 源碼中的 packages/vue/package.json 文件看一下:

      
     
     
     
    1.  "main": "index.js", 
    2.   "module": "dist/vue.runtime.esm-bundler.js", 

    其中 module 字段指向的是 vue.runtime.esm-bundler.js 文件,意思就是說如果你的項目是使用 webpack 構(gòu)建的,那你使用的 Vue 資源就是 vue.runtime.esm-bundler.js ,也就是說帶有 -bundler 字樣的 ESM 資源是給 rollup 或 webpack 等打包工具使用的,而帶有 -browser 字樣的 ESM 資源是直接給