摘要:在中,你可以使用關鍵字輸出任何東西等。新的和語法僅限于在模塊腳本中使用,不能用在常規腳本中。開發者工具的代碼覆蓋率檢查能幫助你檢測源碼中是否存在無用代碼。以達到無需加載其他無用函數的目的。
本文由云+社區發表譯者序作者:
原文:《Using JavaScript modules on the web》 https://developers.google.com...
JS modules,即ES6的模塊化特性,通過
先看看
本文將介紹JS模塊化;怎樣在不經過打包的情況下直接在瀏覽器中使用模塊化;以及Chrome團隊在JS模塊化的優化和普及上正在做的一些事情。
JS模塊化你可能用過命名空間、CommonJS或者AMD規范進行JS模塊化,但所有的這些模塊解決方案萬變不離其宗:引入(import)其他模塊,作為一個模塊輸出(export)。如果說命名空間、CommonJS、AMD都是野路子,那ES6的JS modules則是正規軍,將模塊化語法統一起來(一統江湖,千秋萬代)。
在JS modules中,你可以使用 export關鍵字輸出任何東西: const、 function等。
// lib.mjsexport const repeat = (string) => `${string} ${string}`;export function shout(string) { return `${string.toUpperCase()}!`;}
然后你可以用 import關鍵字從另一個模塊中引進來。下面代碼將lib模塊中的 repeat和 shout函數引到了我們的主模塊main中。
// main.mjsimport {repeat, shout} from "./lib.mjs";repeat("hello");// → "hello hello"shout("Modules in action");// → "MODULES IN ACTION!"
你也可以通過 default關鍵字,輸出一個默認值。
// lib.mjsexport default function(string) { return `${string.toUpperCase()}!`;}
而通過上面的 default輸出的模塊,在引入時可以用其他任何變量名。
// main.mjsimport shout from "./lib.mjs";// ^^^^^
模塊腳本與常規腳本有所區別:
模塊腳本默認開啟了嚴格模式
不支持HTML風格的注釋
模塊具有詞法頂級作用域。也就是說在模塊中 varfoo=42;并不會像傳統腳本一樣,創建一個全局變量 foo,可以通過 window.foo訪問。
新的 import和 export語法僅限于在模塊腳本中使用,不能用在常規腳本中。
正因為這些差異,模塊腳本和傳統腳本顯然需要各自不同的解析方式。因此JS解析器需要標識出哪些腳本屬于是模塊類型的。
瀏覽器如何識別模塊腳本你可以通過設置 元素的 type屬性為 module,以此告訴瀏覽器這段script需要以模塊進行處理。
那些支持 type=module的瀏覽器會忽略掉 nomodule的腳本,而不兼容也會優雅降級,執行fallback.js。
譯者注:親測在IE7+到edge,oppo手機自帶的瀏覽器都能夠降級而執行fallback.js。不過加載fallback的同時,也會把index.mjs一并加載,而支持module的瀏覽器則不會加載fallback。
IE系列均會執行fallback.js
加載fallback的同時,也會把index.mjs一并加載
而支持module的瀏覽器則只會加載模塊
有沒想過另外一個好處:既然瀏覽器能夠識別module,那它必然也能夠支持ES67的其他特性,如箭頭函數、async-await。你不需要為這些特性進行babel編譯,現代瀏覽器跑著更小和最大部分未編譯的模塊化代碼,而不兼容的則使用nomodule的降級代碼。
瀏覽器加載方面的異同:模塊腳本vs傳統腳本上面介紹了模塊腳本和傳統腳本在語言層面的異同,除此之外,在瀏覽器加載過程中也有所不同。
同樣的模塊腳本只會執行一次,而傳統腳本會聲明多次。 模塊腳本跨域需要加跨域頭模塊腳本及其依賴是通過CORS來獲取的,也就是說模塊腳本一旦跨域就需要加上適當的返回頭,比如 Access-Control-Allow-Origin:*。而眾所周知,傳統腳本則不需要(譯者注:還記得傳說中的JSONP嗎)。
async屬性對內聯腳本有效加了async屬性會使得腳本在下載過程中不阻塞DOM渲染,而下載完成后立即執行,兩個async腳本之間的執行時序不確定,執行時機也不確定,有可能在domContentLoaded之前或者之后。但這一屬性對傳統的內聯腳本是無效的,而對模塊的內聯腳本卻是有效的。
關于 .mjs文件后綴你可能會對前面的 .mjs后綴感到好奇,但是在互聯網的世界里,文件后綴并不重要,只要服務器下發的MIME類型( Content-Type:text/javascript)正確就可以。瀏覽器是通過script標簽上的type屬性來識別模塊腳本的,而不是后綴名。
所以無論使用 .js還是 .mjs都是可以的。但是我們還是建議使用 .mjs,原因有兩個:
在開發的時候,可以不需要看代碼,通過后綴名非常直觀地看出哪些是模塊腳本。
nodejs中,ES6的模塊化特性仍在實驗性階段,而該特性只支持 .mjs后綴的腳本。
模塊資源標識符 - module specifier在import一個模塊時,后面的相對或絕對路徑字符串稱為module specifier或import specifier,也就是模塊資源路徑。
import {shout} from "./lib.mjs";// ^^^^^^^^^^^
瀏覽器對于模塊資源路徑做了一些限制。不支持類似下面這種只有模塊名或部分文件名的資源路徑(稱之為bare module specifiers)。這樣的限制是為了以后瀏覽器在支持自定義模塊加載器之后,加載器能夠自行決定bare module specifiers的解析方式。
// Not supported (yet):import {shout} from "jquery";import {shout} from "lib.mjs";import {shout} from "modules/lib.mjs";
目前,模塊資源路徑必須是完整的URL,或者以 /, ./, ../開頭的相對URL
// Supported:import {shout} from "./lib.mjs";import {shout} from "../lib.mjs";import {shout} from "/modules/lib.mjs";import {shout} from "https://simple.example/modules/lib.mjs";模塊script默認是defer
傳統腳本的加載和解析會阻塞html的解析,可以通過添加 defer屬性解決(讓腳本加載和html解析并行)
但這里想告訴你的是,模塊腳本默認具備defer的并行功能,因此無需畫蛇添足加上defer屬性。還有不僅僅只有主模塊與html解析并行,其他子模塊也一樣。
JS模塊化的其他特性 動態引入: import()我們之前僅僅用到了靜態的 import,它需要在首屏就把全部模塊資源都下載下來。但有時候按需加載或異步加載會更為合理,這有助于提高首次加載時間,而 import()可以用來解決這個問題。
不像靜態 import只能用在
NOTE: Webapck自己實現了一套 import()方案,可以動態將import()進去的模塊抽離出來,生成多帶帶的文件。import.meta
另一個和JS modules相關的新特性是 import.meta,它能提供關于當前模塊的meta信息。準確的meta信息并不是ECMAScript規范指定的部分,它取決于宿主環境。在瀏覽器拿到的meta信息和在nodejs里面拿到的是有區別的。
下面的例子中,圖片的相對路徑默認是基于HTML所在位置來解析的,但通過 import.meta.url可以實現基于當前模塊來解析。
function loadThumbnail(relativePath) { const url = new URL(relativePath, import.meta.url); const image = new Image(); image.src = url; return image;}const thumbnail = loadThumbnail("../img/thumbnail.png");container.append(thumbnail);性能優化建議 繼續使用打包工具
通過模塊腳本,開發時我們可以無需再用webpack、Rollup、Parcel等打包工具就可以享受原生的模塊化福利,在以下場景建議可以直接使用原生的模塊腳本:
開發環境下
不超過100個模塊且相對較淺的依賴層級關系(小于5)的小型web應用
然而,我們在性能瓶頸分析中發現,加載一個模塊化庫(大約300個模塊),經過打包的性能數據要比未經過打包直接使用原生模塊腳本的好。
其中一個原因是 import/ export語法是可以靜態分析的,因此打包工具在打包過程中就可以進行靜態分析并移除冗余未使用的模塊。從這可以看出,靜態的 import/ export不僅僅只是語法特性,還具備關鍵的工具屬性(可靜態分析)!
我們的總體建議是繼續使用打包工具進行上線前的模塊打包處理。畢竟從某種程度上,打包可以幫助你盡可能減少代碼體積,用戶不必要加載無用的腳本,更有利于頁面性能。
開發者工具的代碼覆蓋率檢查能幫助你檢測源碼中是否存在無用代碼。我們同時也建議通過代碼分割對模塊進行合理拆分,以及延遲加載非首屏關鍵路徑的腳本。
打包與使用模塊腳本的權衡取舍
通常在web開發領域,所有方案都有利弊,需要權衡取舍。與加載一個未經過代碼拆分的打包腳本相比,使用模塊腳本也許會降低首次加載性能(cold cache),但是可以提升用戶再次加載(warm cache)的速度。比如對于總大小200KB的代碼,在修改一個細顆粒化的模塊之后,那么用戶只需要更新有變更的代碼,這總比重新加載所有代碼(打包腳本)要強。
如果相對于首次訪問體驗來說,你更關注用戶再次訪問體驗,并且你的應用不超過數百個細顆粒化模塊的話,你不妨嘗試下使用模塊腳本,通過性能數據對比之后再做出最后的選擇。
瀏覽器工程師們正努力提升模塊腳本的性能,我們希望模塊腳本以后能夠適用于更多的應用場景。
使用細顆粒化的模塊盡可能讓你的代碼以細顆粒化的模塊進行組織。當在開發時,每個模塊最好不要輸出過多的內容。
下面的 ./util.mjs模塊,輸出了 drop pluck和 zip三個函數。
export function drop() { /* … */ }export function pluck() { /* … */ }export function zip() { /* … */ }
如果你的代碼僅僅只需要 pluck,你也許會這樣引入:
import { pluck } from "./util.mjs";
在這種情況下,如果沒有構建打包編譯,瀏覽器會還是會下載、解析和編譯整個 ./util.js模塊,即使只僅僅需要其中一個export。
如果 pluck不與 drop和 zip有引用或依賴關系的話,最好還是將它獨立成一個模塊 ./pluck.mjs。以達到無需加載其他無用函數的目的。
export function pluck() { /* … */ }
這不僅能夠讓你的源碼簡潔,還能夠減少對打包工具(移除冗余代碼)的依賴。如果在你的應用中其中一個模塊從未被 import過,那么瀏覽器就不會去下載。而那些真正有用的模塊則會被瀏覽器緩存起來。
此外,使用細顆粒化的模塊也有助于對接未來的瀏覽器原生打包功能。
預加載模塊通過
這對于有復雜依賴關系模塊的應用尤為重要。沒有 rel="modulepreload",瀏覽器需要發出多個HTTP請求來計算出整個依賴關系。而如果你把所有依賴模塊通過 rel="modulepreload"提前告訴瀏覽器,那么瀏覽器則無需再漸進式地去計算。
采用HTTP/2協議HTTP/2支持多路復用,多個請求及響應信息可以同時進行傳輸,這有助于提高模塊樹的加載效率。
Chrome團隊還預研了服務器推送——另一個HTTP/2特性,是否能夠作為部署高度模塊化應用的一個可行方案。但結局令人失望,HTTP/2的服務器推送比想象中要難以應用,并且web服務器及瀏覽器的對其實現目前并沒有針對高度模塊化web應用進行優化。另一方面,服務器很難只推送未被緩存的資源。如果通過告知服務器完整的用戶緩存狀態來解決這個問題的話,又存在隱私泄露風險。
無論如何,采用HTTP/2協議吧!只要記住目前HTTP/2的服務器推送目前還不能作為一個好的解決方案。
目前的使用率JS modules正在緩慢地被接納使用。我們的使用統計顯示只有0.08%(不包括動態 import()或者worklets)的頁面目前使用了
Chrome團隊正在通過不同的方式,致力于提高基于JS modules的開發體驗。下面列舉其中的幾種。
更高效、確定性更高的模塊解析算法我們提交了一版對于目前模塊解析算法的優化。新算法目前已經被同時列入了HTML規范和ECMASciprt規范,并且已在Chrome 63版本中實現。希望這項優化能夠在更多的瀏覽器中落地。
新算法更快更高效,舊算法在計算依賴圖譜(dependency graph)大小的時間復雜度為O(n2),在Chrome中的實現也是一樣。而新算法則提升至O(n)。
此外,新算法在報解析錯誤時更加準確。如果一個依賴圖譜中有多個錯誤,那么基于舊算法,每次執行都會報不同的解析錯誤。這給開發調試帶來不必要的困難。新算法則保證每次執行都會報相同的解析錯誤。
Worklets 和 web workersChrome實現了worklets,允許web開發者自定義那些在瀏覽器底層的硬編碼邏輯。目前開發者可以將一個JS模塊引入到渲染管道(rendering pipeline)或者音頻處理管道。
Chrome65版本支持了 PaintWorklet,也稱為CSS繪制API(the CSS Paint API),用于控制如何繪制一個DOM元素。
const result = await css.paintWorklet.addModule("paint-worklet.mjs");
Chrome66版本支持了 AudioWorklet,允許開發者注入自定義的音頻處理代碼。同時這個版本開始了 AnimationWorklet的公測,開發者可以創造視差滾動效果(scroll-linked)以及其他高性能程序動畫(procedural animations)。
最后, LayoutWorklet,又稱為CSS布局API(the CSS Layout API)已在Chrome67版本中實現。
我們正在對Chrome中的web workers支持傳入模塊腳本。你可以通過輸入 chrome://flags/#enable-experimental-web-platform-features開啟這個特性。
const worker = new Worker("worker.mjs", { type: "module" });
在shared workers和service workers傳入模塊腳本也即將支持。
const worker = new SharedWorker("worker.mjs", { type: "module" });const registration = await navigator.serviceWorker.register("worker.mjs", { type: "module" });包名映射表 - Package name maps
在nodejs/npm中,我們經常會通過它們的包名引入模塊,比如:
import moment from "moment";import { pluck } from "lodash-es";
根據現行的HTML規范,類似上述的包名寫法(bare import specifiers)會拋出異常。我們提交的“包名映射表”提案將會支持上述寫法(包括在生產環境)。該映射表(JSON格式)將幫助瀏覽器將包名轉換為完整資源路徑(full URLs)。
包名映射表目前仍處于提案階段(proposal stage)。
Web packaging:瀏覽器原生打包Chrome loading團隊正在探索一種原生的web打包格式(下稱為web packaging),作為一種新模式來分發web應用。web packaging的主要特性如下:
Signed HTTP Exchanges:可以讓瀏覽器信任某個HTTP請求對(request/response)確實是來自于所聲明的源服務器。
Bundled HTTP Exchanges:是多個請求對的集合,不要求當中的每個請求都進行簽名(signed),只要攜帶某些元數據(metadata)用于描述如何將請求束作為一個整體來解析。
兩者結合起來,這種web打包格式就能夠將多個同源資源安全地整合到一個HTTP GET相應中。
市面上的打包工具如webpack、Rollup、Parcel,都會將多個模塊最終打包成一個或少數幾個bundle,這會導致源碼中進行的模塊拆分在上線后就喪失了它的意義。那么通過原生打包,瀏覽器可以將bundle反解成原樣。
簡單來說,你可以把一個HTTP請求對包(Bundled HTTP Exchange)理解為一個資源文件包,它可以通過目錄表(manifest)隨意訪問,并且里面的資源能夠被高效地緩存以及根據相對優先級的高低來標記。有了這個機制,原生模塊能夠提升開發調試的體驗。當你在Chrome開發者工具查看資源時,瀏覽器會精準定位到原生的模塊代碼中,而不需要復雜的source-map。
Chrome已經實現了一部分提案(SignedExchanges),但是打包格式(bundling format)以及在高度模塊化app中的應用仍在探索階段。
Layered APIs移植新的功能和API到瀏覽器中無可避免會帶來持續性的維護成本以及運行成本。每一個新特性都會污染瀏覽器的命名空間,增加啟動開銷,并且也增大引入bug的可能性。Layered APIs的目的是以一種更具擴展性的方式通過瀏覽器來實現或移植一些高級API。而模塊腳本是實現Layered APIs的一項關鍵技術。
由于模塊是顯式引入的,所以通過模塊來引入layered APIs可實現按需使用(不會默認內置)。
模塊的加載源可自定義,因此layered APIs實現了一套自動加載polyfill(當不支持時)的機制。
模塊腳本和layered APIs如何協同運作,具體細節仍在制定中,但目前的協議如下:
這個模塊腳本引入了 virtual-scrollerAPI,如果瀏覽器支持則會直接讀取內置layered APIs集合(std:virtual-scroller),反之則網絡加載對應的polyfill。
譯者:對于Layered APIs更多的中文介紹 https://zhuanlan.zhihu.com/p/...
此文已由騰訊云+社區在各渠道發布
獲取更多新鮮技術干貨,可以關注我們騰訊云技術社區-云加社區官方號及知乎機構號
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/102664.html
摘要:頁面搭建需要準備什么工具首先我們會和設計師溝通我們需要一些檢驗設計的工具自動裁圖自動測量工具我這里安利一下一個工具我用的可以使用阿里的工具拿到界面不要急著做看看有什么問題有些我都會問端問題如果要兼容我要考慮成本如果是一個人辦可能會出現時間的 web頁面搭建需要準備什么工具 首先我們會和設計師溝通 我們需要一些檢驗設計的工具 ps 自動裁圖 自動測量工具 (我這里安利一下一個工具 我用...
摘要:頁面搭建需要準備什么工具首先我們會和設計師溝通我們需要一些檢驗設計的工具自動裁圖自動測量工具我這里安利一下一個工具我用的可以使用阿里的工具拿到界面不要急著做看看有什么問題有些我都會問端問題如果要兼容我要考慮成本如果是一個人辦可能會出現時間的 web頁面搭建需要準備什么工具 首先我們會和設計師溝通 我們需要一些檢驗設計的工具 ps 自動裁圖 自動測量工具 (我這里安利一下一個工具 我用...
摘要:頁面搭建需要準備什么工具首先我們會和設計師溝通我們需要一些檢驗設計的工具自動裁圖自動測量工具我這里安利一下一個工具我用的可以使用阿里的工具拿到界面不要急著做看看有什么問題有些我都會問端問題如果要兼容我要考慮成本如果是一個人辦可能會出現時間的 web頁面搭建需要準備什么工具 首先我們會和設計師溝通 我們需要一些檢驗設計的工具 ps 自動裁圖 自動測量工具 (我這里安利一下一個工具 我用...
摘要:感謝使用框架本文檔涵蓋構建應用所需的基礎知識。用于數據校驗的組件及相關文件在此目錄進行管理。除了自定義中間件外,還是用了諸多第三方的中間件,它們是五測試我們使用組件對服務端代碼進行測試。識別當前導航從已有導航中刪除給定標識的導航配置。 本文同步至個人博客 MEAN.js 文檔,轉載請注明出處。 Overview 感謝使用 MEAN.js 框架! 本文檔涵蓋構建 MEAN 應用所需的基礎...
摘要:接下來安裝和,執行命令安裝很順利,沒有遇到任何問題。再總結一下我們遇到的坑初始化時的項目名稱要合規,特別是不能出現中劃線下劃線。另外再增加,這樣刷新的速度會大大加快最終的文件目錄結構為各文件的最終內容本文也同步發表在我的公眾號“我的天空” 從零開始,用最少的配置、最少的代碼、最少的依賴來搭建一個最簡單的webpack+react環境。 最近在玩webpack+rea...
閱讀 2020·2019-08-30 15:52
閱讀 2975·2019-08-29 16:09
閱讀 1323·2019-08-28 18:30
閱讀 2452·2019-08-26 12:24
閱讀 1089·2019-08-26 12:12
閱讀 2272·2019-08-26 10:45
閱讀 565·2019-08-23 17:52
閱讀 810·2019-08-23 16:03