天天看點

Dojo Build 進階建立包靜态資源漸進式 web 應用程式建構時渲染按條件選取代碼外部依賴脫離 Dojo 建構管道

翻譯自 https://github.com/dojo/framework/blob/master/docs/en/building/supplemental.md

一個包就是一部分代碼,它用于表示一部分功能。可以按需異步、并行加載包。與不使用任何代碼拆分技術的應用程式相比,合理分包的應用程式可以顯著提高響應速度,需要請求的位元組數更少,加載的時間更短。在處理大型應用程式時,這一點尤其重要,因為這類應用程式的大部分表現層邏輯在初始化時是不需要加載的。

Dojo 嘗試使用路由和 outlet 智能地做出選擇,自動将代碼拆分為更小的包。通常各個包内的代碼都是緊密相關的。這是建構系統内置的功能,可直接使用。但是,對于有特殊分包需求的使用者,Dojo 還允許在 <code>.dojorc</code> 配置檔案中顯示定義包。

預設情況下,Dojo 應用程式隻建立一個應用程式包。但是 @dojo/cli-build-app 提供了很多配置選項,這些選項可将應用程式拆分為較小的、可逐漸加載的包。

預設情況下,Dojo 會基于應用程式的路由自動建立包。要做到這一點需要遵循以下幾條規則。

<code>src/routes.ts</code> 必須預設導出路由配置資訊

部件所屬的子產品必須預設導出部件

<code>Outlet</code> 的 <code>render</code> 函數必須使用内聯函數

src/routes.ts
src/App.ts

将會為應用程式的每個頂級路由生成單獨的包。在本例中,會生成一個應用程式的主包以及 <code>src/Home</code>、<code>src/About</code> 和 <code>src/Profile</code> 三個包。

使用 @dojo/cli-create-app 建立一個應用程式,然後運作 <code>npm run build</code>,就可看到自動分包的實際效果。Dojo 将自動為示例應用程式中的所有路由建立包。

可以在 <code>.dojorc</code> 配置檔案中手動分包,這就為應用程式提供了一種聲明式代碼拆分的手段。當自動根據路由分包無法滿足需求時,這對于将應用程式拆分為更小的包是極其有用的。

<code>bundles</code> 功能是 build app 指令的一部分。配置由由一組包名和緊随其後的檔案清單或比對符組成。

例如,以下配置将 <code>About</code> 和 <code>Profile</code> 合在一個包中,并命名為 <code>additional.[hash].js</code>。在 <code>w()</code> 中使用的部件子產品将被自動轉換為在父部件中延遲加載的本地注冊項。

.dojorc

如果我們想分地區建立國際化子產品,我們應該使用通配符以確定将每個語言目錄下的所有檔案都會包含在内。

在這種情況下,Dojo 将建立名為 <code>fr.[hash].js</code> 的包,和名為 <code>de.[hash].js</code> 的包。想了解更新資訊,請參閱國際化參考指南中的使用消息包。

有時,根據建構工具自動分包或者在 <code>.dojorc</code> 中手動定義的包中會重複包含被多個包共享的資源。有一些是無法避免的。一個避免重複的法則是嘗試将共享代碼移到應用程式依賴樹的最外圍。換句話說,就是盡可能減少共享代碼之間的依賴。如果大量的代碼在包之間共享(例如,公共的部件),請考慮将這些資源放在一個包中。

很多靜态資源,如在子產品中導入的 CSS 和圖檔,在建構過程中會被自動内聯。但是,有時還需要為網站圖示(favicon)或視訊檔案等靜态資源提供服務。

靜态資源存放在項目根目錄下的 <code>assets/</code> 檔案夾中。在建構時,這些資源會被複制到應用程式建構版本的 <code>assets/</code> 檔案夾中。

建構也會解析 <code>src/index.html</code> 中引用的 CSS、JavaScript 和圖檔資源等,對這些資源名進行哈希處理,并在輸出檔案夾中包含這些資源。可以将 favicon 存放在 <code>src</code> 檔案夾中,然後在 <code>src/index.html</code> 中引用。建構會自動對 favicon 檔案名進行哈希處理,并會将檔案複制到輸出檔案夾中,然後重命名為 <code>favicon.[hash].ico</code>。

漸進式 web 應用程式(PWA)由一系列技術和模式組成,主要用于改善使用者體驗,幫助建立更可靠和可用的應用程式。尤其是移動使用者,他們會發現應用程式能更好的內建到他們的裝置中,就跟本地安裝的應用程式一樣。

漸進式 web 應用程式主要由兩種技術組成:Service Worker 和 Manifest。Dojo 的建構指令通過 <code>.dojorc</code> 的 <code>pwa</code> 對象支援這兩種技術。

Manifest 在一個 JSON 檔案中描述一個應用程式,并提供了一些詳細資訊,是以可以直接從網際網路安裝到裝置的主螢幕上。

當提供了 manifest 資訊時,<code>dojo build</code> 将在應用程式的 <code>index.html</code> 中注入必需的 <code>&amp;lt;meta&amp;gt;</code> 标簽。

<code>mobile-web-app-capable="yes"</code>: 告知 Android 上的 Chrome 可以将此應用程式添加到使用者的主界面上。

<code>apple-mobile-web-app-capable="yes"</code>: 告知 iOS 裝置可以将此應用程式添加到使用者的主界面上。

<code>apple-mobile-web-app-status-bar-style="default"</code>: 告知 iOS 裝置,狀态欄使用預設外觀。

<code>apple-touch-icon="{{icon}}"</code>: 相當于 Manifest 中的 icons,因為 iOS 目前沒有從 Manifest 中讀取 icons,是以需要為 icons 數組中每張圖檔單獨注入一個 meta 标簽。

Service worder 是一種 web worker,能夠攔截網絡請求、緩存和提供資源。Dojo 的 build 指令能夠自動建構功能全面的 service worker,它會在啟動時激活,然後使用配置檔案完成預緩存和自定義路由處理。

例如,我們編寫一個配置檔案來建立一個簡單的 service worker,它會緩存除了 admin 包之外的所有應用程式包,也會緩存應用程式最近通路的圖像和文章。

在底層,<code>@dojo/webpack-contrib</code> 中的 <code>ServicerWorkerPlugin</code> 用于生成 service worker,它的所有選項都是有效的 <code>pwa.serviceWorker</code> 屬性。

屬性

類型

可選

描述

bundles

<code>string[]</code>

需要進行預緩存的一組包。預設是所有包。

cachePrefix

<code>string</code>

在運作時進行預緩存使用的字首。

clientsClaim

<code>boolean</code>

Service worker 是否要在開始激活時控制用戶端。預設為 <code>false</code>。

excludeBundles

要從預緩存中排除的一組包。預設為 <code>[]</code>。

importScripts

需要在 service worker 中加載的一組腳本的路徑。

precache

<code>object</code>

描述預緩存配置選項的對象(見下文)。

routes

<code>object[]</code>

一組描述要在運作時緩存的配置對象(見下文)。

skipWaiting

Service worker 是否要跳過“等待”生命周期。

<code>precache</code> 選項使用以下選項控制預緩存行為:

baseDir

比對 <code>include</code> 時使用的根目錄。

ignore

一組通配符模式的字元串,當生成預緩存項時用于比對需要忽略的檔案。預設為 <code>[ 'node_modules/**/*' ]</code>。

include

<code>string</code> or <code>string[]</code>

一個或者一組通配符模式的字元串,用于比對 precache 應該包含的檔案。預設是建構管道中的所有檔案。

index

如果請求以 <code>/</code> 結尾的 URL 失敗,則應該查找的 index 檔案名。預設為 <code>'index.html'</code>。

maxCacheSize

<code>number</code>

往預緩存中添加的每一個檔案不應超過的最大位元組數。預設為 <code>2097152</code> (2 MB)。

strict

如果為 <code>true</code>,則 <code>include</code> 模式比對到一個不存在的檔案夾時,建構就會失敗。預設為 <code>true</code>。

symlinks

當生成預緩存時是否允許軟連接配接(symlinks)。預設為 <code>true</code>。

除了預緩存之外,還可以為特定路由提供緩存政策,以确定它們是否可以緩存以及如何緩存。<code>routes</code> 選項是一組包含以下屬性的對象:

urlPattern

用于比對特定路由的模式字元串(會被轉換為正規表達式)。

strategy

緩存政策(見下文)。

options

一個描述附加選項的對象。每個選項的詳情如下。

cacheName

路由使用的緩存名稱。注意 <code>cachePrefix</code> 不會 添加到緩存名前。預設為主運作時緩存(<code>${cachePrefix}-runtime-${domain}</code>)。

cacheableResponse

使用 HTTP 狀态碼或者報頭(Header)資訊來決定是否可以緩存響應的内容。此對象有兩個可選屬性:<code>statuses</code> 和 <code>headers</code>。<code>statuses</code> 是一組對緩存生效的狀态碼。<code>headers</code> 是一組 HTTP 的 header 和 value 鍵值對;至少要與一個報頭比對,響應才會被視為有效。當 <code>strategy</code> 的值是 <code>'cacheFirst'</code> 時,預設為 <code>{ statuses: [ 200 ] }</code>;當 <code>strategy</code> 的值為 <code>networkFirst</code> 或者 <code>staleWhileRevalidate</code> 時,預設為 <code>{ statuses: [0, 200] }</code>

expiration

控制如何讓緩存失效。此對象有兩個可選屬性。<code>maxEntries</code> 是任何時間可以緩存的響應個數。一旦超過此最大值,就會删除最舊的條目。<code>maxAgeSeconds</code> 是一個響應能緩存的最長時間(以秒為機關),超過此時長就會被删除。

networkTimeoutSeconds

與 <code>networkFirst</code> 政策一起使用,指定當網絡請求的響應多久沒有傳回時就從緩存中擷取資源,機關為秒。

目前支援四種路由政策:

<code>networkFirst</code> 嘗試通過網絡加載資源,如果請求失敗或逾時才從緩存中擷取資源。對于頻繁更改或者可能頻繁更改(即沒有版本控制)的資源,這是一個很有用的政策。

<code>cacheFirst</code> 優先從緩存中加載資源,如果緩存中不存在,則通過網絡擷取。這對于很少更改或者能緩存很長一段時間的資源(受版本控制的資源)來說是最好的政策。

<code>networkOnly</code> 強制始終通過網絡擷取資源,對于無需離線處理的資源是很有用的政策。

<code>staleWhileRevalidate</code> 同時從緩存和網絡中請求資源。網絡成功響應後都會更新緩存。此政策最适用于不需要持續更新的資源,比如使用者資訊。但是,當擷取第三方資源時沒有發送 CORS 報頭,就無法讀取響應的内容或驗證狀态碼。是以,可能會緩存錯誤的響應。在這種情況下,<code>networkFirst</code> 政策可能更适合。

建構時渲染(Build-time rendering,簡稱 BTR)在建構過程中将一個路由渲染為一個 HTML,并将在初始視圖中顯示的、關鍵的 CSS 和資源嵌入到頁面中。Dojo 能預渲染路由使用的初始 HTML,并直接注入到頁面中,這樣會帶來很多與伺服器端渲染(SSR)相同的好處,如性能提升、搜尋引擎優化且沒有引入 SSR 的複雜性。

首先確定 <code>index.html</code> 中包含一個擁有 <code>id</code> 屬性的 DOM 節點。Dojo 的虛拟 DOM 會使用這個節點來比較和渲染應用程式的 HTML。BTR 需要此設定,這樣它就能渲染在建構階段生成的 HTML。這将會為路由建立一個響應非常快的初始渲染。

index.html

然後将應用程式挂載到指定的 DOM 節點上:

main.ts

然後更新項目的 <code>.dojorc</code> 配置檔案,設定根 DOM 節點的 <code>id</code> 和在建構時要渲染的路由。

此配置描述了兩個路由。一個是 <code>home</code> 路由,一個是較複雜的 <code>comments</code> 路由。<code>comments</code> 是一個比較複雜的路由,需要傳入參數。<code>match</code> 參數用于確定在建構時為此路由生成的 HTML 可以應用到與此正規表達式比對的任何路由上。

BTR 在建構時為每個渲染路徑(path)生成一份螢幕快照,存在 <code>./output/info/screenshots</code> 檔案夾中。

建構時渲染支援使用 <code>@dojo/framework/routing/history/HashHistory</code> 或 <code>@dojo/framework/routing/history/StateHistory</code> history 管理器的應用程式。當使用 <code>HashHistory</code> 時,確定所有的路徑都是以 <code>#</code> 字元開頭。

運作時渲染公開了一個 <code>build-time-render</code> 功能标記,可用于跳過在建構時不能執行的功能。這樣在建立一個初始渲染時,就可以避免對外部系統調用 <code>fetch</code>,而是提供靜态資料。

Dojo 提供了一個 block 系統,在建構階段的渲染過程中會執行 Node.js 代碼。執行的結果會被寫入到緩存中,然後在浏覽器運作階段會以相同的方式、透明的使用這些緩存。這就為使用一些浏覽器中無法實作或者性能不佳的操作開辟了新的機會。

例如,Dojo 的 block 子產品可以讀取一組 markdown 檔案,将其轉換為 VNode,并使它們可以在應用程式中渲染,所有這些都可在建構時執行。然後 Dojo block 子產品的建構結果會緩存在應用程式的包中,以便在運作時在浏覽器中使用。

Dojo block 子產品的用法與在 Dojo 部件中使用其它 meta 的用法類似。是以無需大量的配置或其他編寫模式。

例如,block 子產品讀取一個文本檔案,然後将内容傳回給應用程式。

src/blocks/read-file.ts
src/widgets/MyBlockWidget.tsx

這個部件會在建構階段運作 <code>src/blocks/read-file.ts</code> 子產品來讀取指定檔案的内容。然後将内容作為文本節點,用作部件 VDOM 輸出的子節點。

建構工具的靜态代碼分析工具能夠從它建立的包中移除無用的代碼。命名的條件塊是使用 dojo 架構的 <code>has</code> 子產品定義的,并且可以在 <code>.dojorc</code> 中靜态設定為 true 或 false,然後在建構階段移除。

上述的 <code>production</code> 功能将建構生産版本(<code>dist</code> 模式)設定為 <code>true</code>。建構系統使用 <code>@dojo/framework/has</code> 将代碼标記為無法通路,并在建構時移除這些無用的代碼。

比如,上述代碼将重寫為:

static-build-loader 輸出

然後,建構工具的無用分支移除工具将移除無用的代碼。

Uglify 輸出

任何沒有被靜态斷言的功能都不會被重寫。這就允許在運作時來确定是否存在這個功能。

建構系統已提供以下功能(feature),用于幫助識别特定的環境或操作模式。

功能标記

<code>debug</code>

提供了一種為代碼建立代碼路徑的方法,該代碼路徑僅在調試或者提供更強的診斷時有用,在為 生産 建構時是不需要的。預設為 <code>true</code>,但在建構生産版本時應該靜态地配置為 <code>false</code>。

<code>host-browser</code>

确定目前環境下 global 上下文中是否包含 <code>window</code> 和 <code>document</code> 對象,是以通常可以安全地假設代碼運作在浏覽器環境下。

<code>host-node</code>

嘗試檢測目前環境是不是 node 環境。

<code>build-time-render</code>

在建構期間渲染時由 BTR 系統靜态定義。

通常不能被打包的非子產品化庫或者獨立的應用程式,如果需要引入到 dojo 應用程式中,則可以通過提供一個 <code>require</code> 或 <code>define</code> 實作,并在項目的 <code>.dojorc</code> 檔案中做一些配置。

要配置外部依賴項,則需要為 <code>build-app</code> 配置對象設定 <code>externals</code> 屬性。<code>externals</code> 是一個對象,包含以下兩個屬性:

<code>outputPath</code>: 一個可選屬性,指定一個将檔案複制到何處的輸出路徑。

<code>dependencies</code>: 一個必填的數組,定義哪些子產品應該通過外部加載器加載,以及在建構時應該包含哪些檔案。每個記錄可以是以下兩種類型之一:

一個字元串,表示應該使用外部加載器加載此路徑及其所有子路徑。

一個對象,為需要複制到建構版應用程式的依賴提供附加配置項。此對象具有以下屬性:

<code>from</code>

相對于項目根目錄的路徑,指定位于何處的檔案夾或目錄要複制到已建構應用程式中。

<code>to</code>

一個路徑,表示将 <code>from</code> 路徑下的依賴複制到何處的目标路徑。預設情況下,依賴會被複制到 <code>${externalsOutputPath}/${to}</code>;如果沒有設定 <code>to</code>,依賴會被複制到 <code>${externalsOutputPath}/${from}</code>。如果路徑中包含 <code>.</code> 字元或者路徑表示的是一個檔案夾,則需要以正斜杠結尾。

<code>name</code>

在應用程式代碼中引用的子產品 id 或者全局變量名。

<code>inject</code>

<code>string, string[], or boolean</code>

此屬性表示這個依賴定義的(或者包含的),要在頁面中加載的腳本或樣式檔案。如果 <code>inject</code> 的值為 <code>true</code>,那麼就會在頁面中加載 <code>to</code> 或 <code>from</code> 指定位置的檔案。如果依賴的是檔案夾,則 <code>inject</code> 可以被設定為一個或者一組字元串,來定義一個或多個要注入的檔案。<code>inject</code> 中的每個路徑都應該是相對于 <code>${externalsOutputPath}/${to}</code> 或 <code>${externalsOutputPath}/${from}</code>(具體取決于是否指定了 <code>to</code>)。

<code>type</code>

<code>'root' or 'umd' or 'amd' or 'commonjs' or 'commonjs2'</code>

強制子產品用指定的方法解析。如果是 AMD 風格,則必須使用 <code>umd</code> 或 <code>amd</code>。如果是 node 風格則必須使用 <code>commonjs</code>,并且值為 <code>root</code> 時以全局的方式通路對象。

例如,以下配置會将 <code>src/legacy/layer.js</code> 注入到應用程式頁面中;注入定義了 <code>MyGlobal</code> 全局變量的檔案;聲明子產品 <code>a</code> 和 <code>b</code> 為外部依賴,且要委托給外部層;然後複制 <code>node_modules/legacy-dep</code> 下的檔案,并将其中的幾個檔案注入到頁面中。所有檔案都将被複制到 <code>externals</code> 檔案夾中,也可以使用 <code>externals</code> 配置中的 <code>outputPath</code> 屬性來重新指定檔案夾。

<code>externals</code> 中包含的依賴項的類型會被安裝到 <code>node_modules/@types</code> 中,這跟其它依賴項是一樣的。

因為這些檔案位于主建構(main build)之外,是以在生産建構中不會執行版本控制或哈希處理(在 <code>inject</code> 中指定資源的連結除外)。可以在 <code>to</code> 屬性中指定版本号,将依賴複制到對應版本的檔案夾下,這樣就能避免緩存不同版本的檔案。

Dojo 的建構管道為項目提供了一個端到端的工具鍊,但是,在極少數情況下,可能需要自定義工具鍊。隻要将項目脫離 Dojo 的建構管道,就可以自定義工具鍊。

将項目脫離建構管道,是一個不可逆的、單向過程,它會導出 Webpack、Intern 以及 <code>dojo</code> 指令使用的其他項目的底層配置檔案。如果提供的生成工具無法提供所需的功能或特性,推薦的方法是 fork 標明的建構指令,然後往工具中添加額外的功能。Dojo 的 CLI 本質上是專門按子產品化設計的,考慮到了這個用例。

要将一個項目脫離出 dojo 建構管道,請使用 <code>dojo eject</code> 指令,它将提示你确實已明白過程是不可逆的。這個導出過程将所有已安裝的 dojo 指令中導出的配置資訊存到 <code>config</code> 檔案夾中。這個過程也會安裝一些項目需要的附加依賴。

現在項目已經是一個 webpack 項目。可以通過修改 <code>config/build-app/base.config.js</code> 來更改建構配置。

然後,可以通過運作 webpack 的建構指令并提供配置項來觸發一個建構。此外,使用 webpack 的 env 标記(例如 --env.mode=dev)來指定模式,預設為 dist。

繼續閱讀