Vite: 高阶特性 Pure ESM

news2024/7/2 21:03:02

概述

  • ESM 已经逐步得到各大浏览器厂商以及 Node.js 的原生支持,正在成为主流前端模块化方案。
    而 Vite 本身就是借助浏览器原生的 ESM 解析能力( type=“module” )实现了开发阶段的 no-bundle ,即不用打包也可以构建 Web 应用。不过我们对于原生 ESM 的理解仅仅停留在 type=“module” 这个特性上面未免有些狭隘了,一方面浏览器和 Node.js 各自提供了不同的 ESM 使用特性,如 import maps 、package.json 的 imports 和exports 属性等等,另一方面前端社区开始逐渐向 ESM 过渡,有的包甚至仅留下 ESM产物, Pure ESM 的概念随之席卷前端圈,而与此同时,基于 ESM 的 CDN 基础设施也如雨后春笋般不断涌现,诸如 esm.sh 、 skypack 、 jspm 等等。
  • 因此你可以看到,ESM 已经不仅仅局限于一个模块规范的概念,它代表了前端社区生态的走向以及各项前端基础设施的未来,不管是浏览器、Node.js 还是 npm 上第三方包生态的发展,无一不在印证这一点。那么,作为一名 2022 年的前端,我觉得深入地了解ESM 的高级特性、社区生态都是有必要的,一方面弥补自己对于 ESM 认知上的不足,另一方面也能享受到社区生态带给我们的红利。在接下来的内容中,我将给你详细介绍浏览器和 Node.js 中基于 ESM 实现的一些 高级特性 ,然后分析什么是 Pure ESM 模式,这种模式下存在哪些痛点,以及我们作为开发者,如何去拥抱 Pure ESM 的趋势。

高阶特性


1 )import map

  • 在浏览器中我们可以使用包含 type=“module” 属性的 script 标签来加载 ES 模块,而模块路径主要包含三种:
    • 绝对路径,如 https://cdn.skypack.dev/react
    • 相对路径,如 ./module-a
    • bare import 即直接写一个第三方包名,如 react 、 lodash
  • 对于前两种模块路径浏览器是原生支持的,而对于 bare import ,在 Node.js 能直接执行,因为 Node.js 的路径解析算法会从项目的 node_modules 找到第三方包的模块路径,但是放在浏览器中无法直接执行。而这种写法在日常开发的过程又极为常见,除了将bare import 手动替换为一个绝对路径,还有其它的解决方案吗?
  • 答案是有的。现代浏览器内置的 import map 就是为了解决上述的问题,我们可以用一个简单的例子来使用这个特性
    <!DOCTYPE html>
    <html lang="en">
    <head>
     <meta charset="UTF-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Document</title>
    </head>
    <body>
     <div id="root"></div>
     <script type="importmap">
    	 {
    		 "imports": {
    			 "react": "https://cdn.skypack.dev/react"
    		 }
    	 }
     </script>
     <script type="module">
    	 import React from 'react';
    	 console.log(React)
     </script>
    </body>
    </html>
    
  • 在浏览器中执行这个 HTML,如果正常执行,那么你可以看到浏览器已经从网络中获取
    了 react 的内容,如下图所示
  • 注意: importmap 可能存在浏览器兼容性问题,这里出现浏览器报错也属于正常情况,后文会介绍解决方案。

在这里插入图片描述

  • 在支持 import map 的浏览器中,在遇到 type=“importmap” 的 script 标签时,浏览器会记录下第三方包的路径映射表,在遇到 bare import 时会根据这张表拉取远程的依赖代码。如上述的例子中,我们使用 skypack 这个第三方的 ESM CDN 服务,通过 https://cdn.skypack.dev/react 这个地址我们可以拿到 React 的 ESM 格式产物。import map 特性虽然简洁方便,但浏览器的兼容性却是个大问题,在 CanIUse 上的兼容性数据如下:

请添加图片描述

  • 它只能兼容市面上 68% 左右的浏览器份额,而反观 type=“module” 的兼容性(兼容 95%
    以上的浏览器), import map 的兼容性实属不太乐观。但幸运的是,社区已经有了对应的
    Polyfill 解决方案——es-module-shims,完整地实现了包含 import map 在内的各大ESM 特性,还包括:
    • dynamic import 。即动态导入,部分老版本的 Firefox 和 Edge 不支持

    • import.meta 和 import.meta.url 。当前模块的元信息,类似 Node.js 中的 __dirname 、 __filename

    • modulepreload 。以前我们会在 link 标签中加上 rel=“preload” 来进行资源预加载,即在浏览器解析 HTML 之前就开始加载资源,现在对于 ESM 也有对应的modulepreload 来支持这个行为

    • JSON Modules 和 CSS Modules ,即通过如下方式来引入 json 或者 css :

      <script type="module">
      // 获取 json 对象
      import json from 'https://site.com/data.json' assert { type: 'json' };
      // 获取 CSS Modules 对象
      import sheet from 'https://site.com/sheet.css' assert { type: 'css' };
      </script>
      
  • 值得一提的是, es-module-shims 基于 wasm 实现,性能并不差,相比浏览器原生的行
    为没有明显的性能下降, 可以去这个地址查看具体的 benchmark 结果
  • 由此可见, import map 虽然并没有得到广泛浏览器的原生支持,但是我们仍然可以通过Polyfill 的方式在支持 type=“module” 的浏览器中使用 import map

2 ) Nodejs 包导入导出策略

  • 在 Node.js 中( >=12.20 版本 )有一般如下几种方式可以使用原生 ES Module:

    • 文件以 .mjs 结尾;
    • package.json 中声明 type: “module” 。
  • 那么,Nodejs 在处理 ES Module 导入导出的时候,如果是处理 npm 包级别的情况,其中的细节可能比你想象中更加复杂。

  • 首先来看看如何导出一个包,你有两种方式可以选择: main 和 exports 属性。这两个属性均来自于 package.json ,并且根据 Node 官方的 resolve 算法,exports 的优先级 main 更高,也就是说如果你同时设置了这两个属性,那么 exports 会优先生效。

  • main 的使用比较简单,设置包的入口文件路径即可,如: "main": "./dist/index.js"

  • 需要重点梳理的是 exports 属性,它包含了多种导出形式: 默认导出 、 子路径导出 和 条件导出 ,这些导出形式如以下的代码所示:

    // package.json
    {
       "name": "package-a",
       "type": "module",
       "exports": {
    	 // 默认导出,使用方式: import a from 'package-a'
    	 ".": "./dist/index.js",
    	 // 子路径导出,使用方式: import d from 'package-a/dist'
    	 "./dist": "./dist/index.js",
    	 "./dist/*": "./dist/*", // 这里可以使用 `*` 导出目录下所有的文件
    	 // 条件导出,区分 ESM 和 CommonJS 引入的情况
    	 "./main": {
    		 "import": "./main.js",
    		 "require": "./main.cjs"
    	 },
      }
    }
    
  • 其中,条件导出可以包括如下常见的属性:

    • node : 在 Node.js 环境下适用,可以定义为嵌套条件导出,如:
      {
        "exports": {
          {
            ".": {
             "node": {
               "import": "./main.js",
               "require": "./main.cjs"
              }     
            }
          }
        },
      }
      
    • import : 用于 import 方式导入的情况,如 import(“package-a”) ;
    • require : 用于 require 方式导入的情况,如 require(“package-a”) ;
    • default ,兜底方案,如果前面的条件都没命中,则使用 default 导出的路径
  • 当然,条件导出还包含 types 、 browser 、 develoment 、 production 等属性,大家可以参考 Node.js 的详情文档,这里就不一一赘述了。

  • 在介绍完"导出"之后,我们再来看看 “导入” ,也就是 package.json 中的 imports 字段,一般是这样声明的:

    {
      "imports": {
        // key 一般以 # 开头
        // 也可以直接赋值为一个字符串: "#dep": "lodash-es"
        "#dep": {
          "node": "lodash-es",
          "default": "./dep-polyfill.js"
        },
      },
      "dependencies": {
        "lodash-es": "^4.17.21"
      }
    }
    
  • 这样你可以在自己的包中使用下面的 import 语句:

    // index.js
    import { cloneDeep } from "#dep";
    const obj = { a: 1 };
    // { a: 1 }
    console.log(cloneDeep(obj));
    
  • Node.js 在执行的时候会将 #dep 定位到 lodash-es 这个第三方包,当然,你也可以将其定位到某个内部文件。这样相当于实现了 路径别名 的功能,不过与构建工具中的 alias 功能不同的是,“imports” 中声明的别名必须全量匹配,否则 Node.js 会直接抛错。

Pure ESM


首先,什么是 Pure ESM ? Pure ESM 最初是在 Github 上的一个帖子中被提出来的,其中有两层含义,一个是让 npm 包都提供 ESM 格式的产物,另一个是仅留下 ESM 产物,抛弃 CommonJS 等其它格式产物


1 ) 对 Pure ESM 的态度

  • 当这个概念被提出来之后社区当中出现了很多不同的声音,有人赞成,也有人不满。但不
    管怎么样,社区中的很多 npm 包已经出现了 ESM First 的趋势,可以预见的是越来越多的包会提供 ESM 的版本,来拥抱社区 ESM 大一统的趋势,同时也有一部分的 npm包做得更加激进,直接采取 Pure ESM 模式,如大名鼎鼎的 chalk 和 imagemin ,最新版本中只提供 ESM 产物,而不再提供 CommonJS 产物。对于 Pure ESM,我们到底应该支持还是反对呢?

  • 首先抛出结论:

    • 对于没有上层封装需求的大型框架,如 Nuxt、Umi,在保证能上 Pure ESM 的情况下,直接上不会有什么问题
    • 但如果是一个底层基础库,最好提供好 ESM 和 CommonJS 两种格式的产物
  • 接下来,我们就来分析这个结论是怎么得出来的, 在 ESM 中,我们可以直接导入 CommonJS 模块,如

    // react 仅有 CommonJS 产物
    import React from 'react';
    console.log(React)
    
  • Node.js 执行以上的原生 ESM 代码并没有问题,但反过来,如果你想在 CommonJS 中
    require 一个 ES 模块,就行不通了:
    在这里插入图片描述

  • 其根本原因在于 require 是同步加载的,而 ES 模块本身具有异步加载的特性,因此两者
    天然互斥,即我们无法 require 一个 ES 模块

  • 那是不是在 CommonJS 中无法引入 ES 模块了呢? 也不尽然,我们可以通过 dynamic import 来引入:
    在这里插入图片描述

  • 不知道你注意到没有,为了引入一个 ES 模块,我们必须要将原来同步的执行环境改为 异步 的,这就带来如下的几个问题:

    • 如果执行环境不支持异步,CommonJS 将无法导入 ES 模块;
    • jest 中不支持导入 ES 模块,测试会比较困难;
    • 在 tsc 中,对于 await import() 语法会强制编译成 require 的语法(详情),只能靠 eval(‘await import()’) 绕过去。
  • 总而言之,CommonJS 中导入 ES 模块比较困难。因此,如果一个基础底层库使用 Pure ESM ,那么潜台词相当于你依赖这个库时(可能是直接依赖,也有可能是间接依赖),你自己的库/应用的产物最好为 ESM 格式。也就是说, Pure ESM 是具有传染性的,底层的库出现了 Pure ESM 产物,那么上层的使用方也最好是 Pure ESM,否则会有上述的种种限制。

  • 但从另一个角度来看,对于大型框架(如 Nuxt)而言,基本没有二次封装的需求,框架本身如果能够使用 Pure ESM ,那么也能带动社区更多的包(比如框架插件)走向 Pure ESM,同时也没有上游调用方的限制,反而对社区 ESM 规范的推动是一件好事情。

  • 当然,上述的结论也带来了一个潜在的问题: 大型框架毕竟很有限,npm 上大部分的包还是属于基础库的范畴,那对于大部分包,我们采用导出 ESM/CommonJS 两种产物的方案,会不会对项目的语法产生限制呢?

  • 我们知道,在 ESM 中无法使用 CommonJS 中的 __dirname 、 __filename 、require.resolve 等全局变量和方法,同样的,在 CommonJS 中我们也没办法使用ESM 专有的 import.meta 对象,那么如果要提供两种产物格式,这些模块规范相关的语法怎么处理呢?在传统的编译构建工具中,我们很难逃开这个问题,但新一代的基础库打包器 tsup 给了我们解决方案

2 ) 新一代的基础库打包器

  • tsup 是一个基于 Esbuild 的基础库打包器,主打无配置(no config)打包。借助它我们可以轻易地打出 ESM 和 CommonJS 双格式的产物,并且可以任意使用与模块格式强相关的一些全局变量或者 API,比如某个库的源码如下:
    export interface Options {
     data: string;
    }
    export function init(options: Options) {
     console.log(options);
     console.log(import.meta.url);
    }
    
  • 由于代码中使用了 import.meta 对象,这是仅在 ESM 下存在的变量,而经过 tsup 打包后的 CommonJS 版本却被转换成了下面这样:
    var getImportMetaUrl = () =>
      typeof document === "undefined" ?
      new URL("file:" + __filename)
      .href :
      (document.currentScript && document.currentScript.src) ||
      new URL("main.js", document.baseURI)
      .href;
    var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
    // src/index.ts
    function init(options) {
      console.log(options);
      console.log(importMetaUrl);
    }
    
  • 可以看到,ESM 中的 API 被转换为 CommonJS 对应的格式,反之也是同理。最后,我们可以借助之前提到的条件导出,将 ESM、CommonJS 的产物分别进行导出,如下所示
    {
      "scripts": {
        "watch": "npm run build -- --watch src",
        "build": "tsup ./src/index.ts --format cjs,esm --dts --clean"
      },
      "exports": {
        ".": {
          "import": "./dist/index.mjs",
          "require": "./dist/index.js",
          // 导出类型
          "types": "./dist/index.d.ts"
        }
      }
    }
    
  • tsup 在解决了双格式产物问题的同时,本身利用 Esbuild 进行打包,性能非常强悍,也能生成类型文件,同时也弥补了 Esbuild 没有类型系统的缺点,还是非常推荐大家使用的
  • 当然,回到 Pure ESM 本身,我觉得这是一个未来可以预见的趋势,但对于基础库来说,现在并不适合切到 Pure ESM ,如今作为过渡时期,还是发 ESM/CommonJS 双格式的包较为靠谱,而 tsup 这种工具能降低基础库构建上的成本。当所有的库都有 ESM 产物的时候,我们再来落地 Pure ESM 就轻而易举了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1880354.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据倾斜优化:Hive性能提升的核心

文章目录 1. 定义2. 数据倾斜2.1 Map2.2 Join2.3 Reduce 3. 写在最后 1. 定义 数据倾斜&#xff0c;也称为Data Skew&#xff0c;是在分布式计算环境中&#xff0c;由于数据分布不均匀导致某些任务处理的数据量远大于其他任务&#xff0c;从而形成性能瓶颈的现象。这种情况在H…

==和equals的区别(面试题)

和equals有什么区别 对于基本数据类型&#xff0c;比较的是值是否相等&#xff0c;对于引用类型则是比较的地址是否相等&#xff1b;对于equals来说&#xff0c;基本数据类型没有equals方法&#xff0c;对于引用类型equals比较的是引用对象是否相同 那针对以上结论&#xff0c…

RabbitMq教程【精细版一】

一、引言 模块之间的耦合度过高&#xff0c;导致一个模块宕机后&#xff0c;全部功能都不能用了&#xff0c;并且同步通讯的成本过高&#xff0c;用户体验差。 RabbitMQ引言 二、RabbitMQ介绍 MQ全称为Message Queue&#xff0c;消息队列是应用程序和应用程序之间的通信方法。…

python工作目录与文件目录

工作目录 文件目录&#xff1a;文件所在的目录 工作目录&#xff1a;执行python命令所在的目录 D:. | main.py | ---data | data.txt | ---model | | model.py | | train.py | | __init__.py | | | ---nlp | | | bert.py | …

DIVE INTO DEEP LEARNING 56-60

文章目录 56 Gated Recurrent Unit(GRU)56.1 Motivation: How to focus on a sequence56.2 The concept of doors56.3 Candidate hidden state56.4 hidden state56.5 summarize56.6 QA 57 Long short-term memory network57.1 Basic concepts57.2 Long short-term memory netwo…

Linux 进程信号篇

文章目录 1. 生活中的信号2. 信号的概念3. 信号的产生3.1 系统调用3.2 软件条件3.2 异常3.3 Core和Term的区别 4. 信号的保存5. 信号的处理5.1 地址空间的进一步理解5.2 键盘输入数据的过程5.3 理解OS如何正常运行5.3.1 OS如何运行5.3.2 如何理解系统调用 5.4 内核态和用户态 6…

YOLO-V1

一、YOLO-V1整体思想与网络架构 1.1 YOLO算法整体思路解读 YOLO-V1: 经典的one-stage方法 把检测问题转化成回归问题&#xff0c;一个CNN就搞定了&#xff01; 可以对视频进行实时检测&#xff0c;应用领域非常广&#xff01; 核心思想&#xff1a; 1、预测一张图像中有哪些物…

AI大模型的崛起:第四次工业革命的前奏?

在当今这个信息爆炸的时代&#xff0c;人工智能&#xff08;AI&#xff09;大模型的崛起引起了广泛的关注和讨论。有人将其视为第四次工业革命的前奏&#xff0c;然而&#xff0c;这真的可能吗&#xff1f;本文将探讨这一问题&#xff0c;并对中国AI大模型的发展进行简要分析。…

鸿蒙开发Ability Kit(程序框架服务):【向用户申请授权】

向用户申请授权 当应用需要访问用户的隐私信息或使用系统能力时&#xff0c;例如获取位置信息、访问日历、使用相机拍摄照片或录制视频等&#xff0c;应该向用户请求授权&#xff0c;这部分权限是user_grant权限。 当应用申请user_grant权限时&#xff0c;需要完成以下步骤&a…

推荐系统三十六式学习笔记:原理篇.模型融合14|一网打尽协同过滤、矩阵分解和线性模型

目录 从特征组合说起FM模型1.原理2.模型训练3.预测阶段4.一网打尽其他模型5.FFM 总结 在上一篇文章中&#xff0c;我们讲到了使用逻辑回归和梯度提升决策树组合的模型融合办法&#xff0c;用于CTR预估&#xff0c;给这个组合起了个名字&#xff0c;叫“辑度组合”。这对组合中&…

鸿蒙开发设备管理:【@ohos.multimodalInput.inputEventClient (注入按键)】

注入按键 InputEventClient模块提供了注入按键能力。 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。本模块接口均为系统接口&#xff0c;三方应用不支持调用。 导入模块 import inputEventCli…

小白学webgl合集-绘制有透视颜色不一样的立方体

效果 原理 结合透视矩阵和视觉矩阵进行绘制 知识点 01透视矩阵 透视矩阵将视图空间中的坐标转换为裁剪空间中的坐标&#xff0c;使得更远的物体看起来更小。 function perspectiveMatrix(fov, aspect, near, far) {const f 1.0 / Math.tan(fov / 2);const nf 1 / (near …

C++旋转点坐标计算

/// 获取A点绕B点旋转P度后的新坐标/// </summary>/// <param name"Angle">角度</param>/// <param name"CirPoint">圆心坐标</param>/// <param name"MovePoint">移动点的坐标</param>/// <param…

(单机架设教程)3D剑踪

前言 今天给大家带来一款单机游戏的架设&#xff1a;3D剑踪 如今市面上的资源参差不齐&#xff0c;大部分的都不能运行&#xff0c;本人亲自测试&#xff0c;运行视频如下&#xff1a; 3D剑踪 搭建教程 此游戏架设不需要虚拟机&#xff0c; 我们先解压 “3D剑踪.zip” &…

【ArcGIS AddIn插件】【可用于全国水旱灾害风险普查】全网最强洪水淹没分析插件-基于8邻域种子搜索算法-有源淹没分析算法

最近有很多GIS小伙伴咨询我关于基于8邻域种子搜索算法的有源淹没分析插件的使用方法及原理&#xff0c;咱们通过这篇文章给大家详细介绍下这款插件的运行机制。 一、插件类型及适用版本 本插件属于ArcGIS AddIn工具条插件&#xff0c;基于ArcGIS Engine10.2.2的开发环境开发的&…

某度,网盘免费加速,复活!

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 有小伙伴反馈之前如下夸克网盘脚本的加速方法失效&#xff0c;小武今天测试&#xff0c;依旧正常使用&#xff01; 百度/迅雷/夸克&#xff0c;网盘免费加速&#xff0c;已破&#xf…

最强文生图模型Stable Diffusion 3 Medium 正式开源

Stability AI 宣布 Stable Diffusion 3 Medium 现已开源&#xff0c;是 Stable Diffusion 3 系列中最新、最先进的文本生成图像 AI 模型 —— 官方声称是 “迄今为止最先进的开源模型”&#xff0c;其性能甚至超过了 Midjourney 6。 Stable Diffusion 3 Medium 模型规格参数达到…

科普文:八大排序算法(JAVA实现)+ 自制动画 (袁厨的算法小屋)

我将我仓库里的排序算法给大家汇总整理了一下&#xff0c;写的非常非常细&#xff0c;还对每个算法制作了动画&#xff0c;一定能够对大家有所帮助&#xff0c;欢迎大家阅读。另外我也对 leetcode 上面可以用排序算法秒杀的算法题进行了总结&#xff0c;会在后面的文章中进行发…

[知识点篇]《计算机组成原理》之数据信息的表示

1、数据表示的作用 &#xff08;1&#xff09;定义&#xff1a;将数据按照某种方式组织&#xff0c;以便机器硬件能直接识别和使用。现代计算机采用二进制进行数据表示。 &#xff08;2&#xff09;数据表示考虑因素&#xff1a; 数据的类型&#xff1a; 数值/非数值、小数、…

团队任务管理跟踪软件有哪些?分享2024年值得关注的10款

本文将分享2024年值得关注的10款团队任务管理跟踪软件&#xff1a;Worktile、PingCode、Zoho Projects、Wrike、ProofHub、Connecteam、MeisterTask、Nifty、BIGContacts、Hive。 无论是小型初创企业还是庞大的跨国公司&#xff0c;高效的任务管理都能显著提升工作效率&#xf…