原生ES-Module在瀏覽器中的嘗試

来源:https://www.cnblogs.com/jiasm/archive/2018/06/09/9160691.html
-Advertisement-
Play Games

其實瀏覽器原生模塊相關的支持也已經出了一兩年了(我第一次知道這個事情實在2016年下半年的時候) 可以拋開webpack直接使用import之類的語法 但因為算是一個比較新的東西,所以現在基本只能自己鬧著玩 :p 但這並不能成為不去瞭解它的藉口,還是要體驗一下的。 首先是各大瀏覽器從何時開始支持mo ...


其實瀏覽器原生模塊相關的支持也已經出了一兩年了(我第一次知道這個事情實在2016年下半年的時候)
可以拋開webpack直接使用import之類的語法
但因為算是一個比較新的東西,所以現在基本只能自己鬧著玩 :p
但這並不能成為不去瞭解它的藉口,還是要體驗一下的。

首先是各大瀏覽器從何時開始支持module的:

  • Safari 10.1
  • Chrome 61
  • Firefox 54 (有可能需要你在about:config頁面設置啟用dom.moduleScripts.enabled)
  • Edge 16

數據來自jakearchibald.com/2017/es-mod…

使用方式

首先在使用上,唯一的區別就是需要在script標簽上添加一個type="module"的屬性來表示這個文件是作為module的方式來運行的。

<script type="module">
  import message from './message.js'

  console.log(message) // hello world
</script>

 

然後在對應的module文件中就是經常會在webpack中用到的那樣。
語法上並沒有什麼區別(本來webpack也就是為了讓你提前用上新的語法:)

message.js

export default 'hello world'

 

優雅降級

這裡有一個類似於noscript標簽的存在。
可以在script標簽上添加nomodule屬性來實現一個回退方案。

<script type="module">
  import module from './module.js'
</script>
<script nomodule>
  alert('your browsers can not supports es modules! please upgrade it.')
</script>

 

nomodule的處理方案是這樣的: 支持type="module"的瀏覽器會忽略包含nomodule屬性的script腳本執行。
而不支持type="module"的瀏覽器則會忽略type="module"腳本的執行。
這是因為瀏覽器預設只解析type="text/javascript"的腳本,而如果不填寫type屬性則預設為text/javascript
也就是說在瀏覽器不支持module的情況下,nomodule對應的腳本文件就會被執行。

一些要註意的細節

但畢竟是瀏覽器原生提供的,在使用方法上與webpack的版本肯定還是會有一些區別的。
(至少一個是運行時解析的、一個是本地編譯)

有效的module路徑定義

因為是在瀏覽器端的實現,不會像在node中,有全局module一說(全局對象都在window里了)。
所以說,from 'XXX'這個路徑的定義會與之前你所熟悉的稍微有些出入。

// 被支持的幾種路徑寫法

import module from 'http://XXX/module.js'
import module from '/XXX/module.js'
import module from './XXX/module.js'
import module from '../XXX/module.js'

// 不被支持的寫法
import module from 'XXX'
import module from 'XXX/module.js'

 

webpack打包的文件中,引用全局包是通過import module from 'XXX'來實現的。
這個實際是一個簡寫,webpack會根據這個路徑去node_modules中找到對應的module並引入進來。
但是原生支持的module是不存在node_modules一說的。
所以,在使用原生module的時候一定要切記,from後邊的路徑一定要是一個有效的URL,以及一定不能省略文件尾碼(是的,即使是遠端文件也是可以使用的,而不像webpack需要將本地文件打包到一起)。

module的文件預設為defer

這是script的另一個屬性,用來將文件標識為不會阻塞頁面渲染的文件,並且會在頁面載入完成後按照文檔的順序進行執行。

<script type="module" src="./defer/module.js"></script>
<script src="./defer/simple.js"></script>
<script defer src="./defer/defer.js"></script>

 

為了測試上邊的觀點,在頁面中引入了這樣三個JS文件,三個文件都會輸出一個字元串,在Console面板上看到的順序是這樣的:

 

 

行內script也會預設添加defer特性

因為在普通的腳本中,defer關鍵字是只指針對腳本文件的,如果是inline-script,添加屬性是不生效的。
但是在type="module"的情況下,不管是文件還是行內腳本,都會具有defer的特性。

可以對module類型的腳本添加async屬性

async可以作用於所有的module類型的腳本,無論是行內還是文件形式的。
但是添加了async關鍵字以後並不意味著瀏覽器在解析到這個腳本文件時就會執行,而是會等到這段腳本所依賴的所有module載入完畢後再執行。
import的約定,必須在一段代碼內的起始位置進行聲明,且不能夠在函數內部進行

也就是說下邊的log輸出順序完全取決於module.js載入的時長。

<script async type="module" >
  import * from './module.js'
  console.log('module')
</script>
<script async src="./defer/async.js"></script>

 

一個module只會載入一次

這個module是否唯一的定義是資源對應的完整路徑是否一致。
如果當前頁面路徑為https://www.baidu.com/a/b/c.html,則文件中的/module.js../../module.jshttps://www.baidu.com/module.js都會被認為是同一個module
但是像這個例子中的module1.jsmodule1.js?a=1就被認定為兩個module,所以這個代碼執行的結果就是會載入兩次module1.js

<script type="module" src="https://blog.jiasm.org/module-usage/example/modules/module1.js"></script>
<script type="module" src="/examples/modules/module1.js"></script>
<script type="module" src="./modules/module1.js"></script>
<script type="module" src="./modules/module1.js?a=1"></script>
<script type="module">
  import * as module1 from './modules/module1.js'
</script>

 

線上Demo

import和export在使用的一些小提示

不管是瀏覽器原生提供的版本,亦或者webpack打包的版本。
importexport基本上還是共通的,語法上基本沒有什麼差別。

下邊列出了一些可能會幫到你更好的去使用modules的一些技巧。

export的重命名

在導出某些模塊時,也是可以像import時使用as關鍵字來重命名你要導出的某個值。

// info.js
let name = 'Niko'
let age = 18

export {
  name as firstName,
  age
}

// import
import {firstName, age} from './info.js'

 

Tips: export的調用不像node中的module.exports = {}
可以進行多次調用,而且不會覆蓋(key重名除外)。

export { name as firstName }
export { age }

 

這樣的寫法兩個key都會被導出。

export導出的屬性均為可讀的

也就是說export導出的屬性是不能夠修改的,如果試圖修改則會得到一個異常。
但是,類似const的效果,如果某一個導出的值是引用類型的,對象或者數組之類的。
你可以操作該對象的一些屬性,例如對數組進行push之類的操作。

export {
  firstName: 'Niko',
  packs: [1, 2]
}
import * as results from './export-editable.js'

results.firstName = 'Bellic' // error

results.packs.push(3)        // success

 

這樣的修改會導致其他引用該模塊都會受到影響,因為使用的是一個地址。

export在代碼中的順序並不影響最終導出的結果

export const name = 'Niko'
export let age = 18

age = 20

 

const 或者 let 對於 調用方來說沒有任何區別

import {name, age} from './module'

console.log(name, age) // Niko 20

 

import獲取default模塊的幾種姿勢

獲取default有以下幾種方式都可以實現:

import defaultItem from './import/module.js'
import { default as defaultItem2 } from './import/module.js'
import _, { default as defaultItem3 } from './import/module.js'

console.log(defaultItem === defaultItem2) // true
console.log(defaultItem === defaultItem3) // true

 

預設的規則是第一個為default對應的別名,但如果第一個參數是一個解構的話,就會被解析為針對所有導出項的一個匹配了。
P.S. 同時存在兩個參數表示第一個為default,第二個為全部模塊

導出全部的語法如下:

import * as allThings from './iport/module.js'

 

類似index的export文件編寫

如果你碰到了類似這樣的需求,在某些地方會用到十個module,如果每次都import十個,肯定是一種浪費,視覺上也會給人一個不好的感覺。
所以你可能會寫一個類似index.js的文件,在這個文件中將其引入到一塊,然後使用時import index即可。
一般來說可能會這麼寫:

import module1 from './module1.js'
import module2 from './module2.js'

export default {
  module1,
  module2
}

 

將所有的module引入,並導出為一個Object,這樣確實在使用時已經很方便了。
但是這個索引文件依然是很醜陋,所以可以用下麵的語法來實現類似的功能:

export {default as module1} from './module1.js'
export {default as module2} from './module2.js'

 

然後在調用時修改為如下格式即可:

import * as modules from './index.js'

線上Demo

小記

想到了最近爆紅的deno,其中有一條特性也是提到了,沒有node_modules,依賴的第三方庫直接通過網路請求的方式來獲取。
然後瀏覽器中原生提供的module也是類似的實現,都是朝著更靈活的方向在走。
祝願拋棄webpack來進行開發的那一天早日到來 :)

參考資料

  1. es modules in browsers
  2. es6 modules in depth
  3. export - JavaScript | MDN
  4. import - JavaScript | MDN

文中示例代碼的GitHub倉庫:傳送陣


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在《Tinker + Bugly + Jenkins 爬坑之路》一文中講了在接入 Tinker 之後,Jenkins 中的一些坑,由此,熱修複算告一段落,但是,在直接 Run 模式運行時,程式會報出如下錯誤: 好吧,使用 TInker 時不能開啟 Instant Run  ̄□ ̄|| GitHub 上 ...
  • ButterKnife 環境搭建 在project的build.gradle文件中添加依賴的插件 在app的build.gradle文件中添加依賴,並添加插件 使用 在Activity中 小點點 + 在Activity中不需要解綁,但是在Fragment中卻需要在onDestroyView中進行解綁 ...
  • 頂頂頂頂頂頂頂頂頂頂 class ShoppingList extends React.Component { render() { return ( Shopping List for {this.props.name} Instagram WhatsApp Oculus ... ...
  • Set 對象允許你存儲任何類型的唯一值,無論是原始值或者是對象引用。 語法 參數 返回值 一個新的Set對象。 簡述 Set對象是值的集合,你可以按照插入的順序迭代它的元素。 Set中的元素只會出現一次,即 Set 中的元素是唯一的。 值的相等 因為 Set 中的值總是唯一的,所以需要判斷兩個值是否 ...
  • Map 對象保存鍵值對。任何值(對象或者原始值) 都可以作為一個鍵或一個值。 語法 參數 描述 一個Map對象以插入順序迭代其元素 — 一個 for...of 迴圈為每次迭代返回一個[key,value]數組。 鍵的相等(Key equality) 鍵的比較是基於 "SameValueZero" 算 ...
  • 由於現在的網頁要相容各種pc尺寸及ipad甚至手機屏幕,所以響應式的網頁變得尤為重要。手寫響應式網頁,具體技術點有: 1.聲明viewport元標簽(響應式網頁必備) <meta name="viewport" content="width=device-width, initial-scale=1 ...
  • 內容:普通函數,匿名函數,函數傳遞是如何讓HTTP伺服器工作的 ###普通函數例子: ###匿名函數 ####################################################################################函數傳遞是如何讓HTTP伺服器 ...
  • 前言 今天把自己寫的demo登錄寫完了,就想著試著走一下部署上線的流程。參考了很多的文檔,終於成功進行了部署。在這裡將伺服器的搭建和vue項目的 部署上線進行整理(都是基礎的知識,希望對大家有幫助。對我幫助是很大的) 2.流程 1.伺服器搭建 這裡我用的是騰訊雲的伺服器。買了一個功能變數名稱。沒有備案的功能變數名稱 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...