<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【模塊化】ESModule 系列 ㈠ :演進

          共 7021字,需瀏覽 15分鐘

           ·

          2021-06-01 12:45

          1ESModule 規(guī)范

          簡介

          在 ES6 之前,社區(qū)自發(fā)的生產(chǎn)了一些模塊加載方案,最主流的有 CommonJS 和 AMD 兩種。前者用于服務(wù)器,后者用于瀏覽器(借助第三方庫的實現(xiàn))。

          ES6 在語言標(biāo)準(zhǔn)的層面上,實現(xiàn)了模塊功能,而且實現(xiàn)得相當(dāng)簡單,能滿足絕大部分 CommonJS 和 AMD 的需求,成為瀏覽器和服務(wù)器通用的模塊解決方案。

          在 ES6 提出了 ESModule 作為語言標(biāo)準(zhǔn)以后,瀏覽器會原生支持這種規(guī)范來實現(xiàn) JavaScript 的模塊系統(tǒng)。同樣地,作為跑在服務(wù)端的 JavaScript,Node.js 也會支持這一規(guī)范。

          // commonjs
          const axios = require('axios')

          // amd
          require(['axios'], function (axios{})

          // esmodule
          import axios from './node_modules/axios/axios.js'

          注:ECMAScript 最新提案已經(jīng)通過,可以通過 importMap 來管理 module import paths,參考內(nèi)容可通過識別下方二維碼訪問。

          服務(wù)端實現(xiàn)

          CommonJS

          在 ESModule 規(guī)范提出以前,Node.js 核心基于內(nèi)置的 Module 模塊來實現(xiàn)了 CommonJS 的模塊系統(tǒng),但是這個模塊系統(tǒng)不是 JavaScript 官方標(biāo)準(zhǔn)提出的,而是 Node.js 的開發(fā)者給 Node.js 的實現(xiàn),用于實現(xiàn)在服務(wù)器上將 JavaScript 原生模塊化。這一標(biāo)準(zhǔn)并沒有在瀏覽器中得到支持。

          ESModule

          在 JavaScript 本身的模塊系統(tǒng)進標(biāo)準(zhǔn)后,Node.js 勢必要去實現(xiàn)這個標(biāo)準(zhǔn)。但是,在 ESModule 標(biāo)準(zhǔn)沒有被提出之前,此前基于 CommonJS 模塊規(guī)范實現(xiàn)的包不可能就此放棄,而此前幾乎所有的第三方依賴的包都是基于 CommonJS 的標(biāo)準(zhǔn)去實現(xiàn),這時,如果 Node.js 為了兼容 ESModule 而放棄 CommonJS,就會導(dǎo)致整個 Node.js 的生態(tài)出現(xiàn)破壞性的兼容問題,所以最好的方案就是新的模塊系統(tǒng)即實現(xiàn)新的 ESModule 規(guī)范,又可以兼容原先的 CommonJS 模塊方案

          兼容問題

          由于 CommonJS 本質(zhì)上與 ESModule 還是存在很有底層的設(shè)計沖突,所以在兼容層的實現(xiàn)上還是存在很多問題,這里不做贅述,有興趣的讀者可以自行搜索,下面也提供了一篇參考文章,可通過識別下方二維碼訪問。

          經(jīng)過一系列的討論,最終兼容的方案確定了下來:ESModule 標(biāo)準(zhǔn)的模塊以后綴名 .mjs 為結(jié)尾, CommonJS 的模塊依然以 .js 文件作為后綴,同時,在 Node.js >= V13 中,在 package.json 中引入了新的模塊定義字段

          // esmA/index.mjs
          export default null

          // esmB/index.js
          export default null

          // esmB/package.json
          {
              "type""module"
          }

          即,后綴為 .mjs 的文件和后綴為 .js 但是在 package.json 中聲明了 "type": "module" 的模塊都會在 Node.js 中被解析為 ESModule 模塊

          瀏覽器實現(xiàn)

          現(xiàn)代瀏覽器支持情況

          一個?? https://github.com/mdn/js-examples/tree/master/modules/basic-modules

          通過以上可以看到,現(xiàn)今主流的瀏覽器,除了IE,基本上在兩三年就已經(jīng)默默實現(xiàn)了基于 ESModule 規(guī)范的模塊系統(tǒng)。

          到這里,看似 Node.js 的已經(jīng)兼容了 CommonJS 和 ESModule 規(guī)范,而且主流瀏覽器也已經(jīng)基于 ESModule 規(guī)范實現(xiàn)了原生的模塊系統(tǒng),那么是不是意味著可以在瀏覽器上直接加載此前基于 CommonJS 開發(fā)的 npm 包了呢?

          2第三方依賴?yán)Ь?/span>

          Node.js  的模塊解析機制

          在 Node.js 中,引入第三方依賴不需要寫相對路徑,只需要依賴名,而且也不需要指定入口文件。

          const axios = require('axios')
          // => 自動拼接 node_modules 路徑
          const axios = require('/User/xxxx/workspace/node_modules/axios')
          // => 自動解析 package.json,拼接入口
          const axios = require('/User/xxxx/workspace/node_modules/axios/axios.js')

          這是因為 Node.js 模塊系統(tǒng)的核心代碼中做了對第三方依賴的路徑解析有很多的兼容性處理,包括但不限于:

          1. 自動拼接 node_moduoles 中第三方依賴的完整路徑
          2. 自動解析 package.json 中的入口字段,拼接入口路徑到上一步的完整路徑中
          3. 自動解析是否需要添加后綴或者補全 index 路徑

          Webpack 的模塊解析機制

          由于 Webpack 的存在,過去 Web App 的開發(fā)都是基于 Bundle 的開發(fā)模式。即,所有的第三方依賴最終都會被打成一個大的 bundle.js。

          早先沒有 ESModule 規(guī)范前,大部分包都是基于 CommonJS 開發(fā),而 Webpack bundle 的過程中會自動注入 Node.js 的 CommonJS 模塊處理邏輯,因此帶給開發(fā)者的體驗是,使用 Webpack 開發(fā) Web App 時使用的 CommonJS 語法,與開發(fā) Node.js App 時使用的 CommonJS 語法,帶來感官上的體驗是一致的。

          但是其實這兩者在底層上的實現(xiàn)是完全不同的, Node.js 的 CommonJS 實現(xiàn)是基于內(nèi)置的 Module 等模塊,在 Node.js Core 層實現(xiàn)的底層邏輯;而 Webpack 中處理 CommonJS 的邏輯是 Webpack 自己實現(xiàn)的一套解析邏輯,只是 Webpack 的維護團隊為了使其在處理 CommonJS 時與 Node.js 中 CommonJS 處理邏輯保持一致,刻意做了一些兼容實現(xiàn),來保證暴露給用戶的頂層接口與 Node.js 保持一致。

          因此,傳統(tǒng)的第三方依賴能夠在 Webpack CLI App 中正常使用 CommonJS 語法得益于 Webpack 實現(xiàn)的 CommonJS 模塊解析邏輯與 Node.js 的 CommonJS 模塊解析邏輯保持了一致。

          瀏覽器的模塊解析機制

          通過 Node.js 和 Webpack 的模塊解析機制,大家應(yīng)該能明白,其實 Node.js 和 Webpack 之所以能加載 CommonJS 的插件,是因為它們各自做了大量的底層實現(xiàn)。而在瀏覽器中,是不支持原生的 CommonJS 模塊加載機制的,因此,哪怕瀏覽器已經(jīng)實現(xiàn)了基于 ESModule 規(guī)范的模塊加載機制,也依然不能使用 Node.js 生態(tài)中的 CommonJS 模塊。

          出于這些原因,為了能夠在瀏覽器中繼續(xù)使用基于 CommonJS 規(guī)范開發(fā)的第三方依賴,下一代 Web App 開發(fā)工具應(yīng)運而生。

          3下一代 Web App 開發(fā)模式

          為了避免因為實現(xiàn) ESModule 規(guī)范帶來的 CommonJS 生態(tài)的第三方依賴包無法在瀏覽器使用的問題,許多工具隨之產(chǎn)生。

          CommonJS To ESModule

          瀏覽器無法使用 CommonJS 模塊的最根本原因就是瀏覽器本身并沒有實現(xiàn)任何 CommonJS 模塊加載機制,而在主流瀏覽器紛紛支持 ESModule 規(guī)范之后,解決這個問題的根本辦法就是將 CommonJS 模塊轉(zhuǎn)化為 ESModule 模塊,現(xiàn)今社區(qū)主流的轉(zhuǎn)化方案大概如下:

          • JSPM (https://jspm.dev/@babel/core)
          • Skypack(前 Pika)(http://cdn.skypack.dev/@babel/core)
          • ESM.sh(http://esm.sh/@babel/core)

          這些工具的處理方案大致都是利用轉(zhuǎn)換(構(gòu)建)工具,比如 Rollup、Esbuild 等,去給模塊語法做升級操作,將 CommonJS 的語法升級為 ESModule 規(guī)范的語法,同時將其中引入另外第三方依賴的路徑轉(zhuǎn)化拼接為對應(yīng)的 ESModule 路徑

          const axios = reuqire('axios')
          module.exports = axios
          // =>
          import axios from '/esm/axios.js'
          export default axios

          當(dāng)有了這些工具之后,我們就能在瀏覽器中直接原生的去使用這些第三方依賴。

          //index.html
          <script type="module">
              import axiso from 'http://cdn.skypack.dev/axios'
              // TODO...
          </script>

          但是筆者實踐過一段時間后,用這種最簡單的方式來使用第三方依賴還是會遇到很多問題,最明顯的就是請求爆炸。

          比如一個 Antd 組件庫,里面使用到的第三方依賴就有成百上千個,由于沒有了 Bundle,加載這些依賴的過程就直接變成了網(wǎng)絡(luò)請求,這就導(dǎo)致下載一個簡簡單單的 Antd 組件庫,需要發(fā)送接近 2000 個網(wǎng)絡(luò)請求,這一方面加重了首屏渲染的壓力,另一方面給整個開發(fā)鏈路加重了負(fù)擔(dān)。

          基于這種背景和發(fā)展趨勢,下一代 Web App 開發(fā)工具也隨之誕生。

          Snowpack & Vite

          由于上述說到加載第三方依賴時可能會遇到的請求爆炸的問題,因此在本地開發(fā)時對于第三方依賴的 Bundle 這個過程其實還是不可省略的。

          在 Webpack 原先的理念中,所有的文件都會被當(dāng)作一個模塊,并打包到最終的 Bundle 中,這就導(dǎo)致了整個 Bundle 的過程幾乎不可復(fù)用,即每一次啟動 Dev Server 時,都會重新做一次 Bundle,而這是非常耗費性能和時間的。但是在開發(fā)的過程中,第三方依賴的代碼基本上是維持不變的,只用下載一次,就不會再發(fā)生變化,而且瀏覽器已經(jīng)原生支持基于了 ESModule 規(guī)范的模塊加載機制,開發(fā)工具完全可以不用再接管第三方依賴的加載,而是可以將這個過程全部交給瀏覽器本身來進行。

          基于此,第三方依賴和源代碼可以拆分成兩個維度的概念,開發(fā)工具可以不用再將所有的源代碼和第三方依賴全部 Bundle 到一起,而是可以將第三方依賴和源代碼拆分開來單獨進行處理。將不會再發(fā)生變化的第三方依賴單獨 Bundle 為一個 Dep Chunks,處理一次,到處使用。

          基于這個理念,Snowpack 和 Vite也逐漸地嶄露頭角,這里對這兩種新一代的 ESM 開發(fā)工具不再展開做介紹,有興趣的讀者可以自行去了解。

          4推進困難

          上述說到,新一代的開發(fā)工具和開發(fā)模式是利用瀏覽器原生的模塊加載機制去加載第三方依賴,開發(fā)工具只需要將依賴 Bundle 一次,后續(xù)就不用再多做處理。

          node_modules
              depA
              depB
              depC
              ...
              ...
          // => bundle
          node_modules
              depA.js
              depB.js
              depC.js
              chunks
                  ...

          這種新的開發(fā)模式一方面降低了開發(fā)工具的復(fù)雜性(利用瀏覽器原生的模塊加載機制,開發(fā)工具不再接管依賴的加載),另一方面大大提升的應(yīng)用的啟動速度,和本地開發(fā)的調(diào)試體驗(不用在每次啟動 dev server 時去重新處理 node_modules)。

          但是從整個生態(tài)的大環(huán)境來看,這種新一代的開發(fā)模式推進情況卻不容樂觀。

          者經(jīng)常這段時間的使用下來,也發(fā)現(xiàn)了這些開發(fā)工具目前的通?。?/span>

          生態(tài)問題

          生態(tài)不完善,很多 Webpack 已經(jīng)支持很好的功能還沒有得到官方的支持。

          CommonJS 向 ESModule 的轉(zhuǎn)換問題

          對很多第三方依賴的轉(zhuǎn)化處理不完善,缺失完善的解決機制。

          上面也說到,要將第三方依賴的加載全部交給瀏覽器本身來接管,那么首先開發(fā)工具要做的就是將第三方依賴全部轉(zhuǎn)換為 ESModule 的模塊,而現(xiàn)在 npm 上的絕大部分包都是只支持 CommonJS 版本的,因此這里的轉(zhuǎn)換過程通常都是由開發(fā)工具自己來接管,而這其中有很多底層的問題并沒有得到好的解決。

          動態(tài)引入與靜態(tài)解析

          CommonJS 中,模塊的引入與導(dǎo)出是動態(tài)的語法,因此要想拿到一個模塊完整的導(dǎo)入或者導(dǎo)出,必須得等到那個模塊具體執(zhí)行的時候才能確認(rèn)。

          // mnoduleA.js
          init()

          function init ({
              exports.a = 1
          }
          // index.js
          require('./moduleA'// { a: 1 }

          而在 ESModule 中,模塊語法是需要被解釋器靜態(tài)解析的,這就要求導(dǎo)出語法必須聲明在作用域的最頂層。

          // Success
          export const a = 1

          // Error
          init ()
          function init({
              export const a = 1
          }

          具名導(dǎo)出與默認(rèn)導(dǎo)出

          在 CommonJS 中,對具名導(dǎo)出和默認(rèn)導(dǎo)出是沒有具體做限定的,nodejs的一個模塊中,全局的 exports === module.exports。

          而在 ESModule 中,對具名導(dǎo)出和默認(rèn)導(dǎo)出做出了一個具體的限定,而且在導(dǎo)入語法中也有明顯的區(qū)別。

          // module.js
          export const a = 1
          export default { a2 }
          // index.js
          import { a } from './module.js' // 1

          import a from './module.js'
          console.info(a.a) // 2

          import * as a from './module./js'
          console.info(a.a) // 1
          console.info(a.default) // 2

          同時,在 ESModule 規(guī)范推進的過程中,有許多如 exports.default、exports.__esModule 等利用語法來兼容 ESModule 和 CommonJS 的廢案往往也都被 babel 實現(xiàn),而且被許多開發(fā)者使用并且發(fā)布到了 npm 上,這就導(dǎo)致了現(xiàn)在 npm 上的許多包中有大量的廢棄兼容性代碼,而這些代碼往往會對開發(fā)工具的轉(zhuǎn)化造成阻礙


          瀏覽 98
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产伦精品一区二区三区视频痴汉 | 国产精品高潮呻吟久久 | 日本成人三级91精品电影 | 中文有码人妻熟女久久 | 黄色三级A片 |