浏览器中的 ESM
大家好,我是若川。最近組織了源碼共讀活動,感興趣的可以加我微信?ruochuan12
早期的web應(yīng)用非常簡單,可以直接加載js的形式去實(shí)現(xiàn)。隨著需求的越來越多,應(yīng)用越做越大,需要模塊化去管理項(xiàng)目中的js、css、圖片等資源。這里有很多大家熟悉的模塊化標(biāo)準(zhǔn), CJS、AMD、CMD、UMD 等等。模塊化提供了我們更好的方式來組織和維護(hù)函數(shù)以及變量。而在 npm 生態(tài)開發(fā)的背景下,CJS 模塊是開發(fā)過程中接觸最多也是無法避免的。但由于瀏覽器并不能直接執(zhí)行基于 CJS 打包的模塊,因此類似 webpack 等打包工具便應(yīng)運(yùn)而生。隨著webpack 大有一統(tǒng)構(gòu)建工具的趨勢下,JavaScript 官方的標(biāo)準(zhǔn)化模塊系統(tǒng)ESM完成了。本文主要介紹下模塊化標(biāo)準(zhǔn)間差異、基本加載原理。
模塊化發(fā)展
1、為何要模塊化
仔細(xì)想想,用 JavaScript 編碼就是管理變量。這一切都是關(guān)于為變量賦值,或?yàn)樽兞刻砑訑?shù)字,或?qū)蓚€(gè)變量組合在一起并將它們放入另一個(gè)變量中。
如果代碼中僅有少量的變量,那么組織起來其實(shí)是很簡單的。一旦有很多的變量,我們會通過函數(shù)作用域去組織變量。因?yàn)楹瘮?shù)作用域的緣故,一個(gè)函數(shù)無法訪問另一個(gè)函數(shù)中定義的變量。
如果只維護(hù)少量變量非常簡單。但是如果有很多的變量,我們就需要用一種方法來幫助做到這一點(diǎn),叫做作用域。由于作用域在 JavaScript 中的工作方式,函數(shù)不能訪問在其他函數(shù)中定義的變量。
這種方式是很有效的。在寫一個(gè)函數(shù)的時(shí)候,只需要考慮當(dāng)前函數(shù),而不必?fù)?dān)心其它函數(shù)可能會改變當(dāng)前函數(shù)的變量。如果想要函數(shù)之間共享變量要怎么辦呢?一種通用的做法是全局作用域。
在 jQuery 時(shí)代這種提升做法相當(dāng)普遍。在我們加載任何 jQuery 插件之前,我們必須確保 jQuery 已經(jīng)存在于全局作用域。
所有的 <script> 必須以正確的順序排列,必須保證被依賴的變量先加載。如果排列錯(cuò)了,那么在運(yùn)行過程中,應(yīng)用將會拋出錯(cuò)誤,并且停止繼續(xù)運(yùn)行。
代碼之間的依賴是不透明的。這使得代碼維護(hù)變得困難。代碼變得充滿不確定性。任何函數(shù)都可能依賴全局作用域中的任何變量。
其次,由于變量存在于全局作用域,所以任何代碼都可以改變它。
2、模塊化的作用
模塊化為你提供了一種更好的方式來組織變量和函數(shù)。可以把相關(guān)的變量和函數(shù)放在一起組成一個(gè)模塊。這種實(shí)現(xiàn)形式可以把函數(shù)和變量放在模塊作用域中。模塊作用域還提供一種暴露變量給其他模塊使用的方式。模塊可以明確地指定哪些變量、類或函數(shù)對外暴露。
對外暴露的過程稱為導(dǎo)出。一旦導(dǎo)出,其他模塊就可以明確地聲稱它們依賴這些導(dǎo)出的變量、類或者函數(shù)。
因?yàn)檫@是一種明確的關(guān)系,所以你可以很簡單地辨別哪些代碼能移除,哪些不能移除。
擁有了在模塊之間導(dǎo)出和導(dǎo)入變量的能力之后,你就可以把代碼分割成更小的、可以獨(dú)立運(yùn)行地代碼塊了。基于這些代碼塊,你就可以像搭樂高積木一樣,創(chuàng)建所有不同類型的應(yīng)用。比較流程的規(guī)范有CommonJS,AMD,CMD,ES,UMD等
3、現(xiàn)有模塊標(biāo)準(zhǔn)
CJS?是?CommonJS?的縮寫。只適用于node端:
const _ = require('lodash'); module.exports = function doSomething(n) {}AMD?代表異步模塊定義。在瀏覽器端有效:
define(['dep1', 'dep2'], function (dep1, dep2) {return function () {}; });UMD?代表通用模塊定義(Universal Module Definition):
(function (root, factory) {if (typeof define === 'function' && define.amd) {// AMD. Register as an anonymous module.define([], factory);} else if (typeof module === 'object' && module.exports) {// Node. Does not work with strict CommonJS, but// only CommonJS-like environments that support module.exports,// like Node.module.exports = factory();} else {// Browser globals (root is window)root.returnExports = factory();} }(typeof self !== 'undefined' ? self : this, function () {// Just return a value to define the module export.// This example returns an object, but the module// can return a function as the exported value.return {}; }));什么是 ESM
簡介
ESM是ES6提出的標(biāo)準(zhǔn)模塊系統(tǒng),ECMAScript modules。JS自己的模塊體系
<script type="module">import { html, Component, render } from 'https://unpkg.com/htm/preact/standalone.module.js';class App extends Component {state = {count: 0}add = () => {this.setState({ count: this.state.count + 1 });}render() {return html`<div class="app"><div>count: ${this.state.count}</div><button onClick=${this.add}>Add Todo</button></div>`;}}render(html`<${App} page="All" />`, document.body); </script>思考:上述代碼和在webpack中開發(fā)有啥區(qū)別?
2、瀏覽器端技術(shù)實(shí)現(xiàn)
回顧下Webpack執(zhí)行流程
本地模塊化解析(通過webpack或者babel,將import解析成cjs)
將各個(gè)庫打包成一個(gè)js boundle
開啟服務(wù),托管資源
瀏覽器獲取資源
執(zhí)行代碼
瀏覽器端ESM執(zhí)行流程
開啟服務(wù),托管資源(ES源碼)
加載入口文件,瀏覽器模塊化解析
構(gòu)建
遍歷依賴樹,先解析文件,然后找出依賴,最后又定位并加載這些依賴,如此往復(fù)。(下載所有的js)
模塊映射
當(dāng)加載器要從一個(gè) URL 加載文件時(shí),它會把 URL 記錄到模塊映射中,并把它標(biāo)記為正在下載的文件。然后它會發(fā)出這個(gè)文件請求并繼續(xù)開始獲取下一個(gè)文件。
解析模塊
所有的模塊都按照嚴(yán)格模式來解析的。不同文件類型按照不同的解析方式稱。在瀏覽器中,通過 type="module" 屬性告訴瀏覽器這個(gè)文件需要被解析為一個(gè)模塊。不過在 Node 中,我們并不使用 HTML 標(biāo)簽,所以也沒辦法通過 type 屬性來辨別。社區(qū)提出一種解決辦法是使用 .mjs 拓展名。
運(yùn)行
采用深度優(yōu)先的后序遍歷方式,順著關(guān)系圖到達(dá)最底端沒有任何依賴的模塊,然后設(shè)置它們的導(dǎo)出。模塊映射會以 URL 為索引來緩存模塊,以確保每個(gè)模塊只有一個(gè)模塊記錄。這保證了每個(gè)模塊只會運(yùn)行一次。
3、為什么火起來
ES語法基本確定
http2普及
新瀏覽器普及
開發(fā)與發(fā)布代碼一致
啟動快
全新加載模式
目前瀏覽器支持:
目前只有5%的瀏覽器不兼容es相關(guān)規(guī)范。
4、為什么還沒火起來
部分瀏覽器的兼容性
歷史包袱悠久
生態(tài)不完善
實(shí)戰(zhàn)
當(dāng)我們在項(xiàng)目中使用需要考慮以下幾個(gè)問題點(diǎn)
1. 代碼開發(fā)需要基于es開發(fā)
let a = 1; new Promise() () => {} ...2. 依賴庫加載
node_modules代碼服務(wù)化
兼容cjs
加載包內(nèi)部es目錄
cjstoesm
CDN(network for npm)
https://unpkg.com/
https://www.skypack.dev/
3. 兼容不支持的瀏覽器
type="module"實(shí)現(xiàn)
如果瀏覽器不支持,他只識別type="text/javascript"不識別 type="module" ,故不下載js;如果支持,則會下載js
如果瀏覽器不支持,則會忽略nomodule,下載js;如果支持,則不會下載js
systemjs實(shí)現(xiàn)https://github.com/systemjs/systemjs
4. jsx支持
通過其他開源庫
本地語法糖解析
現(xiàn)有腳手架
1. snowpack
托管node_modules
支持圖片、css等資源
JSX 和 Typescript 編譯
HMR
...
2. vite
https://cn.vitejs.dev/guide/
目前snowpack的作者后續(xù)可能不再維護(hù)了,所以推薦大家使用vite
ESM 未來
2018 年 5 月 Firefox 60 發(fā)布后,所有的主流瀏覽器就都默認(rèn)支持 ESM 了。Node 也正在添加 ESM 支持,為此還成立了工作小組來專門研究 CJS 和 ESM 之間的兼容性問題。所以,在未來你可以直接在 <script> 標(biāo)簽中使用 type="module",并且在代碼中使用 import 和 export 。同時(shí),更多的模塊功能也正在研究中。比如動態(tài)導(dǎo)入提案已經(jīng)處于 Stage 3 狀態(tài);import.meta也被提出以便 Node.js 對 ESM 的支持;模塊定位提案 也致力于解決瀏覽器和 Node.js 之間的差異。
相信在不久的未來,跟模塊一起玩耍將會變成一件更加愉快的事!
node v10以上版本全部支持ESM https://kentcdodds.com/blog/super-simple-start-to-es-modules-in-node-js
相關(guān)參考
ECMAScript modules in browsers https://jakearchibald.com/2017/es-modules-in-browsers/
JavaScript 模塊現(xiàn)狀 https://zhuanlan.zhihu.com/p/26567790
基于esm、html、unpkg的前端開發(fā)模式:https://github.com/developit/htm
How I Build JavaScript Apps In 2021:https://timdaub.github.io/2021/01/16/web-principles/
Find out how much turning on modern JS could save. https://estimator.dev/
什么是amd、commonjs、umd、esm? https://zhuanlan.zhihu.com/p/96718777
ES modules: A cartoon deep-dive:https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/
import.map:https://github.com/WICG/import-maps
面對 ESM 的開發(fā)模式,webpack 還有還手之力嗎? https://topic.atatech.org/articles/202736
總結(jié)
- 上一篇: 前端学习(3302):类组件父组件和子组
- 下一篇: 前端学习(3290):react hoo