成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

帶你了解前端模塊化的今生

開發 前端
眾所周知,早期 JavaScript 原生并不支持模塊化,直到 2015 年,TC39 發布 ES6,其中有一個規范就是 ES modules(為了方便表述,后面統一簡稱 ESM)。

背景

眾所周知,早期 JavaScript 原生并不支持模塊化,直到 2015 年,TC39 發布 ES6,其中有一個規范就是 ES modules(為了方便表述,后面統一簡稱 ESM)。但是在 ES6 規范提出前,就已經存在了一些模塊化方案,比如 CommonJS(in Node.js)、AMD。ESM 與這些規范的共同點就是都支持導入(import)和導出(export)語法,只是其行為的關鍵詞也一些差異。

CommonJS 

  1. // add.js  
  2. const add = (a, b) => a + b  
  3. module.exports = add  
  4. // index.js  
  5. const add = require('./add')  
  6. add(1, 5) 

AMD 

  1. // add.js  
  2. define(function() {  
  3.   const add = (a, b) => a + b  
  4.   return add  
  5. })  
  6. // index.js  
  7. require(['./add'], function (add) {  
  8.   add(1, 5)  
  9. }) 

ESM 

  1. // add.js  
  2. const add = (a, b) => a + b  
  3. export default add  
  4. //index.js  
  5. import add from './add'  
  6. add(1, 5) 

關于 JavaScript 模塊化出現的背景在上一章(《前端模塊化的前世》))已經有所介紹,這里不再贅述。但是 ESM 的出現不同于其他的規范,因為這是 JavaScript 官方推出的模塊化方案,相比于 CommonJS 和 AMD 方案,ESM采用了完全靜態化的方式進行模塊的加載。

ESM規范

模塊導出

模塊導出只有一個關鍵詞:export,最簡單的方法就是在聲明的變量前面直接加上 export 關鍵詞。 

  1. export const name = 'Shenfq' 

可以在 const、let、var 前直接加上 export,也可以在 function 或者 class 前面直接加上 export。 

  1. export function getName() {  
  2.   return name  
  3.  
  4. export class Logger {  
  5.     log(...args) {  
  6.     console.log(...args)  
  7.   }  

上面的導出方法也可以使用大括號的方式進行簡寫。 

  1. const name = 'Shenfq'  
  2. function getName() {  
  3.   return name  
  4.  
  5. class Logger {  
  6.     log(...args) {  
  7.     console.log(...args)  
  8.   }  
  9.  
  10. export { name, getName, Logger } 

最后一種語法,也是我們經常使用的,導出默認模塊。 

  1. const name = 'Shenfq'  
  2. export default name 

模塊導入

模塊的導入使用import,并配合 from 關鍵詞。 

  1. // main.js  
  2. import name from './module.js'  
  3. // module.js  
  4. const name = 'Shenfq'  
  5. export default name 

這樣直接導入的方式,module.js 中必須使用 export default,也就是說 import 語法,默認導入的是default模塊。如果想要導入其他模塊,就必須使用對象展開的語法。 

  1. // main.js  
  2. import { name, getName } from './module.js'  
  3. // module.js  
  4. export const name = 'Shenfq'  
  5. export const getName = () => name 

如果模塊文件同時導出了默認模塊,和其他模塊,在導入時,也可以同時將兩者導入。 

  1. // main.js  
  2. import name, { getName } from './module.js'  
  3. //module.js  
  4. const name = 'Shenfq'  
  5. export const getName = () => name  
  6. export default name 

當然,ESM 也提供了重命名的語法,將導入的模塊進行重新命名。 

  1. // main.js  
  2. import * as mod from './module.js'  
  3. let name = ''  
  4. name = mod.name  
  5. name = mod.getName()  
  6. // module.js  
  7. export const name = 'Shenfq'  
  8. export const getName = () => name 

上述寫法就相當于于將模塊導出的對象進行重新賦值: 

  1. // main.js  
  2. import { name, getName } from './module.js'  
  3. const mod = { name, getName } 

同時也可以對單獨的變量進行重命名: 

  1. // main.js  
  2. import { name, getName as getModName } 

導入同時進行導出

如果有兩個模塊 a 和 b ,同時引入了模塊 c,但是這兩個模塊還需要導入模塊 d,如果模塊 a、b 在導入 c 之后,再導入 d 也是可以的,但是有些繁瑣,我們可以直接在模塊 c 里面導入模塊 d,再把模塊 d 暴露出去。

 

  1. // module_c.js  
  2. import { name, getName } from './module_d.js'  
  3. export { name, getName } 

這么寫看起來還是有些麻煩,這里 ESM 提供了一種將 import 和 export 進行結合的語法。 

  1. export { name, getName } from './module_d.js' 

上面是 ESM 規范的一些基本語法,如果想了解更多,可以翻閱阮老師的 《ES6 入門》。

ESM 與 CommonJS 的差異

首先肯定是語法上的差異,前面也已經簡單介紹過了,一個使用 import/export 語法,一個使用 require/module 語法。

另一個 ESM 與 CommonJS 顯著的差異在于,ESM 導入模塊的變量都是強綁定,導出模塊的變量一旦發生變化,對應導入模塊的變量也會跟隨變化,而 CommonJS 中導入的模塊都是值傳遞與引用傳遞,類似于函數傳參(基本類型進行值傳遞,相當于拷貝變量,非基礎類型【對象、數組】,進行引用傳遞)。

下面我們看下詳細的案例:

CommonJS 

  1. // a.js  
  2. const mod = require('./b')  
  3. setTimeout(() => {  
  4.   console.log(mod)  
  5. }, 1000)  
  6. // b.js  
  7. let mod = 'first value'  
  8. setTimeout(() => {  
  9.   mod = 'second value'  
  10. }, 500)  
  11. modmodule.exports = mod  
  1. $ node a.js  
  2. first value 

ESM 

  1. // a.mjs  
  2. import { mod } from './b.mjs'  
  3. setTimeout(() => {  
  4.   console.log(mod)  
  5. }, 1000)  
  6. // b.mjs  
  7. export let mod = 'first value'  
  8. setTimeout(() => {  
  9.   mod = 'second value'  
  10. }, 500)  
  1. $ node --experimental-modules a.mjs  
  2. # (node:99615) ExperimentalWarning: The ESM module loader is experimental.  
  3. second value 

另外,CommonJS 的模塊實現,實際是給每個模塊文件做了一層函數包裹,從而使得每個模塊獲取 require/module、__filename/__dirname 變量。那上面的 a.js 來舉例,實際執行過程中 a.js 運行代碼如下: 

  1. // a.js  
  2. (function(exports, require, module, __filename, __dirname) {  
  3.     const mod = require('./b')  
  4.   setTimeout(() => {  
  5.     console.log(mod)  
  6.   }, 1000)  
  7. }); 

而 ESM 的模塊是通過 import/export 關鍵詞來實現,沒有對應的函數包裹,所以在 ESM 模塊中,需要使用 import.meta 變量來獲取 __filename/__dirname。import.meta 是 ECMAScript 實現的一個包含模塊元數據的特定對象,主要用于存放模塊的 url,而 node 中只支持加載本地模塊,所以 url 都是使用 file: 協議。 

  1. import url from 'url'  
  2. import path from 'path'  
  3. // import.meta: { url: file:///Users/dev/mjs/a.mjs }  
  4. const __filename = url.fileURLToPath(import.meta.url)  
  5. const __dirname = path.dirname(__filename) 

加載的原理 

步驟:

  1.  Construction(構造):下載所有的文件并且解析為module records。
  2.  Instantiation(實例):把所有導出的變量入內存指定位置(但是暫時還不求值)。然后,讓導出和導入都指向內存指定位置。這叫做『linking(鏈接)』。
  3.  Evaluation(求值):執行代碼,得到變量的值然后放到內存對應位置。

模塊記錄

所有的模塊化開發,都是從一個入口文件開始,無論是 Node.js 還是瀏覽器,都會根據這個入口文件進行檢索,一步一步找到其他所有的依賴文件。 

  1. // Node.js: main.mjs  
  2. import Log from './log.mjs'  
  1. <!-- chrome、firefox --> 
  2. <script type="module" src="./log.js"></script> 

值得注意的是,剛開始拿到入口文件,我們并不知道它依賴了哪些模塊,所以必須先通過 js 引擎靜態分析,得到一個模塊記錄,該記錄包含了該文件的依賴項。所以,一開始拿到的 js 文件并不會執行,只是會將文件轉換得到一個模塊記錄(module records)。所有的 import 模塊都在模塊記錄的 importEntries 字段中記錄,更多模塊記錄相關的字段可以查閱tc39.es。

模塊構造

得到模塊記錄后,會下載所有依賴,并再次將依賴文件轉換為模塊記錄,一直持續到沒有依賴文件為止,這個過程被稱為『構造』(construction)。

模塊構造包括如下三個步驟:

  1.  模塊識別(解析依賴模塊 url,找到真實的下載路徑);
  2.  文件下載(從指定的 url 進行下載,或從文件系統進行加載);
  3.  轉化為模塊記錄(module records)。

對于如何將模塊文件轉化為模塊記錄,ESM 規范有詳細的說明,但是在構造這個步驟中,要怎么下載得到這些依賴的模塊文件,在 ESM 規范中并沒有對應的說明。因為如何下載文件,在服務端和客戶端都有不同的實現規范。比如,在瀏覽器中,如何下載文件是屬于 HTML 規范(瀏覽器的模塊加載都是使用的 <script> 標簽)。

雖然下載完全不屬于 ESM 的現有規范,但在 import 語句中還有一個引用模塊的 url 地址,關于這個地址需要如何轉化,在 Node 和瀏覽器之間有會出現一些差異。簡單來說,在 Node 中可以直接 import 在 node_modules 中的模塊,而在瀏覽器中并不能直接這么做,因為瀏覽器無法正確的找到服務器上的 node_modules 目錄在哪里。好在有一個叫做 import-maps 的提案,該提案主要就是用來解決瀏覽器無法直接導入模塊標識符的問題。但是,在該提案未被完全實現之前,瀏覽器中依然只能使用 url 進行模塊導入。 

  1. <script type="importmap">  
  2.  
  3.   "imports": {  
  4.       "jQuery": "/node_modules/jquery/dist/jquery.js"  
  5.   }  
  6.  
  7. </script>  
  8. <script type="module">  
  9.     import $ from 'jQuery'  
  10.   $(function () {  
  11.     $('#app').html('init')  
  12.   })  
  13. </script> 

下載好的模塊,都會被轉化為模塊記錄然后緩存到 module map 中,遇到不同文件獲取的相同依賴,都會直接在 module map 緩存中獲取。 

  1. // log.js  
  2. const log = console.log  
  3. export default log  
  4. // file.js  
  5. export {   
  6.   readFileSync as read,  
  7.   writeFileSync as write  
  8. } from 'fs' 

模塊實例

獲取到所有依賴文件并建立好 module map 后,就會找到所有模塊記錄,并取出其中的所有導出的變量,然后,將所有變量一一對應到內存中,將對應關系存儲到『模塊環境記錄』(module environment record)中。當然當前內存中的變量并沒有值,只是初始化了對應關系。初始化導出變量和內存的對應關系后,緊接著會設置模塊導入和內存的對應關系,確保相同變量的導入和導出都指向了同一個內存區域,并保證所有的導入都能找到對應的導出。

模塊連接

由于導入和導出指向同一內存區域,所以導出值一旦發生變化,導入值也會變化,不同于 CommonJS,CommonJS 的所有值都是基于拷貝的。連接到導入導出變量后,我們就需要將對應的值放入到內存中,下面就要進入到求值的步驟了。

模塊求值

求值步驟相對簡單,只要運行代碼把計算出來的值填入之前記錄的內存地址就可以了。到這里就已經能夠愉快的使用 ESM 模塊化了。

ESM的進展

因為 ESM 出現較晚,服務端已有 CommonJS 方案,客戶端又有 webpack 打包工具,所以 ESM 的推廣不得不說還是十分艱難的。

客戶端

我們先看看客戶端的支持情況,這里推薦大家到 Can I Use 直接查看,下圖是 2019/11的截圖。

目前為止,主流瀏覽器都已經支持 ESM 了,只需在 script 標簽傳入指定的 type="module" 即可。 

  1. <script type="module" src="./main.js"></script> 

另外,我們知道在 Node.js 中,要使用 ESM 有時候需要用到 .mjs 后綴,但是瀏覽器并不關心文件后綴,只需要 http 響應頭的MIME類型正確即可(Content-Type: text/javascript)。同時,當 type="module" 時,默認啟用 defer 來加載腳本。這里補充一張 defer、async 差異圖。

我們知道瀏覽器不支持 script 的時候,提供了 noscript 標簽用于降級處理,模塊化也提供了類似的標簽。 

  1. <script type="module" src="./main.js"></script>  
  2. <script nomodule>  
  3.   alert('當前瀏覽器不支持 ESM !!!')  
  4. </script> 

這樣我們就能針對支持 ESM 的瀏覽器直接使用模塊化方案加載文件,不支持的瀏覽器還是使用 webpack 打包的版本。 

  1. <script type="module" src="./src/main.js"></script>  
  2. <script nomodule src="./dist/app.[hash].js"></script> 

預加載

我們知道瀏覽器的 link 標簽可以用作資源的預加載,比如我需要預先加載 main.js 文件: 

  1. <link rel="preload" href="./main.js"></link> 

如果這個 main.js 文件是一個模塊化文件,瀏覽器僅僅預先加載單獨這一個文件是沒有意義的,前面我們也說過,一個模塊化文件下載后還需要轉化得到模塊記錄,進行模塊實例、模塊求值這些操作,所以我們得想辦法告訴瀏覽器,這個文件是一個模塊化的文件,所以瀏覽器提供了一種新的 rel 類型,專門用于模塊化文件的預加載。 

  1. <link rel="modulepreload" href="./main.js"></link> 

現狀

雖然主流瀏覽器都已經支持了 ESM,但是根據 chrome 的統計,有用到 <script type="module"> 的頁面只有 1%。截圖時間為 2019/11。

服務端

瀏覽器能夠通過 script 標簽指定當前腳本是否作為模塊處理,但是在 Node.js 中沒有很明確的方式來表示是否需要使用 ESM,而且 Node.js 中本身就已經有了 CommonJS 的標準模塊化方案。就算開啟了 ESM,又通過何種方式來判斷當前入口文件導入的模塊到底是使用的 ESM 還是 CommonJS 呢?為了解決上述問題,node 社區開始出現了 ESM 的相關草案,具體可以在 github 上查閱。

2017年發布的 Node.js 8.5.0 開啟了 ESM 的實驗性支持,在啟動程序時,加上 --experimental-modules 來開啟對 ESM 的支持,并將 .mjs 后綴的文件當做 ESM 來解析。早期的期望是在 Node.js 12 達到 LTS 狀態正式發布,然后期望并沒有實現,直到最近的 13.2.0 版本才正式支持 ESM,也就是取消了 --experimental-modules 啟動參數。具體細節可以查看 Node.js 13.2.0 的官方文檔。

關于 .mjs 后綴社區有兩種完全不同的態度。支持的一方認為通過文件后綴區分類型是最簡單也是最明確的方式,且社區早已有類似案例,例如,.jsx 用于 React 組件、.ts 用于 ts 文件;而支持的一方認為,.js 作為 js 后綴已經存在這么多年,視覺上很難接受一個 .mjs 也是 js 文件,而且現有的很多工具都是以 .js 后綴來識別 js 文件,如果引入了 .mjs 方案,就有大批量的工具需要修改來有效的適配 ESM。

所以除了 .mjs 后綴指定 ESM 外,還可以使用 pkg.json 文件的 type 屬性。如果 type 屬性為 module,則表示當前模塊應使用 ESM 來解析模塊,否則使用 CommonJS 解析模塊。 

  1.  
  2.   "type": "module" // module | commonjs(default)  

當然有些本地文件是沒有 pkg.json 的,但是你又不想使用 .mjs 后綴,這時候只需要在命令行加上一個啟動參數 --input-type=module。同時 input-type 也支持 commonjs 參數來指定使用 CommonJS(-—input-type=commonjs)。

總結一下,Node.js 中,以下三種情況會啟用 ESM 的模塊加載方式:

  1.  文件后綴為.mjs;
  2.  pkg.json 中 type 字段指定為 module;
  3.  啟動參數添加 --input-type=module。

同樣,也有三種情況會啟用 CommonJS 的模塊加載方式:

  1.  文件后綴為.cjs;
  2.  pkg.json 中 type 字段指定為 commonjs;
  3.  啟動參數添加 --input-type=commonjs。

雖然 13.2 版本去除了 --experimental-modules 的啟動參數,但是按照文檔的說法,在 Node.js 中使用 ESM 依舊是實驗特性。   

  1. Stability: 1 - Experimental 

不過,相信等到 Node.js 14 LTS 版本發布時,ESM 的支持應該就能進入穩定階段了,這里還有一個 Node.js 關于 ESM 的整個計劃列表可以查閱。 

 

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2020-09-18 09:02:32

前端模塊化

2022-09-05 09:01:13

前端模塊化

2020-09-17 10:30:21

前端模塊化組件

2013-08-20 15:31:18

前端模塊化

2022-03-11 13:01:27

前端模塊

2023-05-24 10:35:11

Node.jsES模塊

2014-04-27 10:16:31

QCon北京2014Andrew Bett

2021-12-15 11:52:34

GPLLinuxGNU

2019-08-28 16:18:39

JavaScriptJS前端

2016-09-23 11:08:35

前端Javascript模塊化

2013-03-19 10:50:38

2013-03-11 10:00:13

前端模塊化

2021-01-06 05:23:15

ServiceMesh網絡阿帕網

2018-12-18 11:20:28

前端模塊化JavaScript

2016-10-09 11:03:41

Javascript模塊化Web

2019-08-05 10:08:25

軟件操作系統程序員

2013-03-11 10:10:03

2022-09-21 11:51:26

模塊化應用

2015-10-10 10:01:28

前端模塊化webpack

2017-05-18 10:23:55

模塊化開發RequireJsJavascript
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 99婷婷| 在线观看国产视频 | 一级二级三级在线观看 | 中文久久| 国产国产精品 | 精品入口麻豆88视频 | 欧美性a视频 | 国产乱码精品1区2区3区 | 成人性生交大片免费看中文带字幕 | 日日摸夜夜爽人人添av | 天堂国产 | 69av网| 成人av一区二区三区 | 在线观看亚洲一区二区 | 欧美一区二区综合 | 久久www免费视频 | 国产精品久久久久婷婷二区次 | www日韩高清 | 成人精品国产一区二区4080 | 精品综合 | 97人人澡人人爽91综合色 | 中文字幕日韩欧美一区二区三区 | 亚洲国产一区视频 | 欧美一区二区三区国产 | 亚洲第一网站 | va在线 | 在线看日韩 | 精品欧美色视频网站在线观看 | 中文字幕在线一区二区三区 | 精品九九九 | 一区二区三区免费观看 | 久久国产欧美日韩精品 | 欧美精品久久久 | 91在线播 | 男女爱爱福利视频 | 日韩在线精品视频 | 水蜜桃久久夜色精品一区 | 天堂在线www | 欧美视频免费在线 | 毛片com | 成年人网站免费视频 |