react中的export什么函数能导出_esModuleInterop 到底做了什么?

2023-11-14

问题引入

很多 react 使用者在从 JS 迁移到 TS 时,可能会遇到这样一个问题:

JS 引入 react 是这样的:

// js
import React from 'react'

而 TS 却是这样的:

// ts
import * as React from 'react'

如果直接在 TS 里改成 JS 一样的写法,在安装了 @types/react 的情况下,编辑器会抛出一个错误:此模块是使用 "export =" 声明的,在使用 "esModuleInterop" 标志时只能与默认导入一起使用。

根据提示,在 tsconfig.json 中设置 compilerOptions.esModuleInterop 为 true,报错就消失了。

要搞清楚这个问题的原因,首先需要知道 JS 的模块系统。常用的 JS 的模块系统有三个:

  • CommonJS(后文简称 cjs)
  • ES module(后文简称 esm)
  • UMD

(AMD 现在用得比较少了,故忽略掉)

babel、TS 等编译器更加偏爱 cjs。默认情况下,代码里写的 esm 都会被 babel、TS 转成 cjs。这个原因我推测有以下几点:

  1. cjs 出现得比 esm 更早,所以已有大量的 npm 库是基于 cjs 的(数量远高于 esm),比如 react
  2. cjs 有着非常成熟、流行、使用率高的 runtime:Node.js,而 esm 的 runtime 目前支持非常有限(浏览器端需要高级浏览器,node 需要一些稀奇古怪的配置和修改文件后缀名)
  3. 有很多 npm 库是基于 UMD 的,UMD 兼容 cjs,但因为 esm 是静态的,UMD 无法兼容 esm

回到上面那个问题。打开 react 库的 index.js:

可以看到 react 是基于 cjs的,相当于:

module.exports = {
  Children: Children,
  Component: Component
}

而在 index.ts 中,写一段

import React from "react";
console.log(React);

默认情况下,经过 tsc 编译后的代码为:

"use strict";
exports.__esModule = true;
var react_1 = require("react");
console.log(react_1["default"]);

显然,打印出来的结果为 undefined,因为 react 的 module.exports 中根本就没有 default 和这个属性。所以后续获取 React.createElement、React.Component 自然都会报错。

这个问题引申出来的问题其实是,目前已有的大量的第三方库大多都是用 UMD / cjs 写的(或者说,使用的是他们编译之后的产物,而编译之后的产物一般都为 cjs),但现在前端代码基本上都是用 esm 来写,所以 esm 与 cjs 需要一套规则来兼容。

  • esm 导入 esm
    • 两边都会被转为 cjs
    • 严格按照 esm 的标准写,一般不会出现问题
  • esm 导入 cjs
    • 引用第三方库时最常见,比如本文举例的 react
    • 兼容问题的产生是因为 esm 有 default 这个概念,而 cjs 没有。任何导出的变量在 cjs 看来都是 module.exports 这个对象上的属性,esm 的 default 导出也只是 cjs 上的 module.exports.default 属性而已
    • 导入方 esm 会被转为 cjs
  • cjs 导入 esm (一般不会这样使用)
  • cjs 导入 cjs
    • 不会被编译器处理
    • 严格按照 cjs 的标准写,不会出现问题

TS 默认编译规则

TS 对于 import 变量的转译规则为:

 // before
 import React from 'react';
 console.log(React)
 // after
 var React = require('react');
 console.log(React['default'])


 // before
 import {Component} from 'react';
 console.log(Component);
 // after
 var React = require('react');
 console.log(React.Component)
 

 // before 
 import * as React from 'react';
 console.log(React);
 // after
 var React = require('react');
 console.log(React);

可以看到:

  • 对于 import 导入默认导出的模块,TS 在读这个模块的时候会去读取上面的 default 属性
  • 对于 import 导入非默认导出的变量,TS 会去读这个模块上面对应的属性
  • 对于 import *,TS 会直接读该模块

TS、babel 对 export 变量的转译规则为:(代码经过简化)

 // before
 export const name = "esm";
 export default {
   name: "esm default",
 };

 // after
 exports.__esModule = true;
 exports.name = "esm";
 exports["default"] = {
   name: "esm default"
 }

可以看到:

  • 对于 export default 的变量,TS 会将其放在 module.exports 的 default 属性上
  • 对于 export 的变量,TS 会将其放在 module.exports 对应变量名的属性上
  • 额外给 module.exports 增加一个 __esModule: true 的属性,用来告诉编译器,这本来是一个 esm 模块

TS 开启 esModuleInterop 后的编译规则

回到标题上,esModuleInterop 这个属性默认为 false。改成 true 之后,TS 对于 import 的转译规则会发生一些变化(export 的规则不会变):

 // before
 import React from 'react';
 console.log(React);
 // after 代码经过简化
 var react = __importDefault(require('react'));
 console.log(react['default']);


 // before
 import {Component} from 'react';
 console.log(Component);
 // after 代码经过简化
 var react = require('react');
 console.log(react.Component);
 
 
 // before
 import * as React from 'react';
 console.log(React);
 // after 代码经过简化
 var react = _importStar(require('react'));
 console.log(react);

可以看到,对于默认导入和 namespace(*)导入,TS 使用了两个 helper 函数来帮忙

// 代码经过简化
var __importDefault = function (mod) {
  return mod && mod.__esModule ? mod : { default: mod };
};

var __importStar = function (mod) {
  if (mod && mod.__esModule) {
    return mod;
  }

  var result = {};
  for (var k in mod) {
    if (k !== "default" && mod.hasOwnProperty(k)) {
      result[k] = mod[k]
    }
  }
  result["default"] = mod;

  return result;
};

首先看__importDefault。它做的事情是:

  1. 如果目标模块是 esm,就直接返回目标模块;否则将目标模块挂在一个对象的 defalut 上,返回该对象。

比如上面的

import React from 'react';

// ------

console.log(React);

编译后再层层翻译:

// TS 编译
const React = __importDefault(require('react'));

// 翻译 require
const React = __importDefault( {Children:Children,Component:Component} );

// 翻译 __importDefault
const React = { default: {Children:Children,Component:Component} };

// -------

// 读取 React:
console.log(React.default);

// 最后一步翻译:
console.log({Children:Children,Component:Component})

这样就成功获取了 react 模块的 modue.exports。

再看 __importStar。它做的事情是:

  1. 如果目标模块是 esm,就直接返回目标模块。否则
  2. 将目标模块上所有的除了 default 以外的属性挪到 result 上
  3. 将目标模块自己挂到 result.default 上

(类似上面 __importDefault 一样层层翻译分析过程略过)

babel 的规则

babel 默认的转译规则和 TS 开启 esModuleInterop 的情况差不多,也是通过两个 helper 函数来处理的

_interopRequireDefault 类似 __importDefault

_interopRequireWildcard 类似 __importStar

特殊的 webpack

一般开发中,babel 和 TS 都会配合 webpack 来使用。而 TS 和 webpack 的结合有两种方式:

  • ts-loader
  • @babel/preset-typescript

如果是使用 ts-loader,那么 webpack 会将源代码先交给 tsc 来编译,然后处理编译后的代码。经过 tsc 编译后,所有的模块都会变成 cjs,所以 babel 也不会处理,直接交给 webpack 来以 cjs 的方式处理模块。

如果是使用的 @babel/preset-typescript,那么 webpack 不会调用 tsc,tsconfig.json 也会被忽略掉。而是直接用 babel 去编译 ts 文件。这个编译过程相比调用 tsc 会轻量许多,因为 babel 只会简单的移除所有 ts 相关的代码,不会做类型检查。一般在这种情况下,一个 ts 模块经过 babel 的 @babel/preset-env 和 @babel/preset-typescript 两个 preset 处理。后者做的事情很简单,仅仅去掉所有 ts 相关的代码,不会处理模块,而前者会将 esm 转成 cjs。然而 webpack 的 babel-loader 在调用 babel.transform 时,传了这样一个 caller 选项:

从而导致 babel 保留了 esm 的 import export

因为 webpack 自己有一套模块机制,用来处理 cjs esm AMD UMD 等各种各样的模块。webpack 希望自己直接来处理用户写的模块,而不是让 babel 处理一遍再交给自己处理一遍。

对于 cjs 引用 esm,webpack 的编译机制比较特别:

// 代码经过简化
// before
import cjs from "./cjs";
console.log(cjs);
// after
var cjs = __webpack_require__("./src/cjs.js");
var cjsdefault = __webpack_require__.n(cjs);
console.log(cjsdefault.a);

// before
import esm from "./esm";
console.log(esm);
// after
var esm = __webpack_require__("./src/esm.js");
console.log(esm["default"]);

其中_webpack_require__ 类似于 require,返回目标模块的 module.exports 对象。_webpack_require__.n 这个函数接收一个参数对象,返回一个对象,该返回对象的 a 属性(我也不知道为什么属性名叫 a)会被设为参数对象。所以上面源代码的 console.log(cjs) 会打印出 cjs.js 的 module.exports

由于 webpack 为模块提供了一个 runtime,所以 webpack 处理模块对于 webpack 自己而言很自由,在模块闭包里注入代表 module require exports 的变量就可以了

总结:

目前很多常用的包是基于 cjs / UMD 开发的,而写前端代码一般是写 esm,所以常见的场景是 esm 导入 cjs 的库。但是由于 esm 和 cjs 存在概念上的差异,最大的差异点在于 esm 有 default 的概念而 cjs 没有,所以在 default 上会出问题。

TS babel webpack 都有自己的一套处理机制来处理这个兼容问题,核心思想基本都是通过 default 属性的增添和读取

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

react中的export什么函数能导出_esModuleInterop 到底做了什么? 的相关文章

  • Redis学习:Redis缓存穿透和雪崩

    缓存穿透和雪崩 大规模的缓存击穿 两者的区别就是 缓存穿透是一个key缓存没数据 数据库也没有数据 请求全部打到数据库层面上 数据库也没数数据 无能为力 缓存层和数据库层都没有 真的是透了 雪崩先理解缓存击穿 就是很多请求一个key 这个k
  • obsidian配合hugo的使用,让markdown本地编辑软件与在线化无缝衔接

    下载安装 点击打开obsidian官方网站 下载并安装 安装地址为 C Users 当前账号 AppData Local Obsidian C Users 当前账号 AppData Local obsidian updater 创建新库 起
  • Elasticsearch 之 数据索引

    对于提供全文检索的工具来说 索引时一个关键的过程 只有通过索引操作 才能对数据进行分析存储 创建倒排索引 从而让使用者查询到相关的信息 本篇就ES的数据索引操作相关的内容展开 更多内容参考 Elasticsearch资料汇总 索引操作 最简
  • Are you running in a Servlet container that supports JSR-356

    最近使用websocke出现以下问题 No javax websocket server ServerContainer ServletContext attribute Are you running in a Servlet conta
  • Docker-compose安装mysql

    介绍 本系列文章主要介绍使用docker compose部署mysql nginx redis等中间件 前后分离微服务项目部署流程 不介绍docker安装以及基础命令 话不多说首先进入mysql安装教学 操作 首先创建个目录用来存放dock
  • unity让一个物体从上到下消失_Unity3D中隐藏与显示物体的一些操作

    在使用unity3d开发游戏的过程中 我们常常会遇到需要隐藏或者显示的操作 针对这一点 下面做了一些总结 一 设置Renderer状态 在游戏的开发中 所有能够被渲染的物体都包含有一个Renderer组件 使用它可以将物体渲染到屏幕上 那么
  • linux shell脚本无法执行,报错syntax error near unexpected token `$'\r''解决方法

    最近一直忙于项目上的要求 在编写各种自动化安装的脚本 正好有一个需求是在之前的项目上编写过的 目前只需要拿来修改下即可 之前的shell脚本是在服务器上编写的 后来又已复制的方式存在在了电脑上 以txt文件的形式存放的 于是复制到了编辑工具
  • Spring知识点总结

    一 Spring框架介绍 Spring 是分层的 Java SE EE 应用 full stack 轻量级开源框架 以 IoC Inverse Of Control 反转控制 和 AOP Aspect Oriented Programmin
  • mybatis-mate相关配置

    Mybatis Mate 配置 mybatis mate cert grant 请添加微信wx153666购买授权 不白嫖从我做起 license SM hy2HGmqRZIw7NE5i1vKIiHOQLGXGymokyRCkR TYuNZ
  • html+jQuery自定义报告单

    html jQuery实现自定义报告单 功能 用户按需求自定义拼接生成报告单 支持打印功能 支持导出HTML CSS代码 截图 源码地址 customize report
  • java中byte和Byte详解

    写程序时 误把byte写作Byte 调试了许久 便将二者的区别及用法详细理解一遍 1 byte标题和Byte详解 byte是java的基本数据类型 存储整型数据 占据1个字节 8 bits 能够存储的数据范围是 128 127 Byte是j
  • Windows环境下ARM集成开发环境的搭建与使用

    Windows环境下能够使用Eclipse IDE for C C Developers来搭建ARM开发环境 本文地址 http blog csdn net u011833609 article details 30290655 安装过程例
  • 继续教育自动听课软件_2017继续教育挂机软件下载

    继续教育自动挂机软件2017是一款帮助用户挂机学习的辅助工具 有了它你就不用一直呆在电脑前了 它可以自动更新学习时间 自动换课计时 安全无病毒 方便快捷 省时省力省心 全国中小教师继续教育网的各种远程培训学习 全国各省份都能使用 使用教程
  • Valid注解在嵌套对象中校验失效

    接收对象声明 import lombok Data import javax validation constraints NotBlank import javax validation constraints NotNull impor
  • 数学的幽默打油诗

    1 常微分学常没分 数理方程没天理 实变函数学十遍 泛函分析心犯寒 微分拓扑躲不脱 随机过程随机过 微机原理闹危机 汇编语言不会编 量子力学量力学 机械制图机械制 2 高数 拉格朗日 傅立叶旁 我凝视你凹函数般的脸庞 微分了忧伤 积分了希望
  • 宁波到西塘可以坐火车去吗?

    宁波 嘉善 西塘宁波 嘉善火车 N406 N407 空调快速宁波15 10出发嘉善19 27到达4小时17分270公里硬座 42元硬卧 93元然后从嘉善打车到西塘 9公里 15元左右 坐快客3元也可以乘坐T794 空调特快宁波 10 41出
  • python 实现自动批量下载腾讯在线excel

    python 批量下载腾讯在线文档 如需要源代码供参考 可以留言邮箱 看到的话就发一下 pthon自动批量下载腾讯在线文档 对于大量实时更新维护的在线文档 可以随时轻松自动化批量下载在线文档 无需耗费人工下载 腾讯在线文档标签有时候会随版本
  • BP神经网络算法基本原理,BP神经网络算法流程图

    伤寒 副伤寒流行预测模型 BP神经网络 的建立 由于目前研究的各种数学模型或多或少存在使用条件的局限性 或使用方法的复杂性等问题 预测效果均不十分理想 距离实际应用仍有较大差距 NNT是Matlab中较为重要的一个工具箱 在实际应用中 BP
  • 爬虫中有关验证码的问题处理

    在爬虫中 经常要处理登陆的相关事宜 有时候登陆界面会需要提交验证码 如何处理验证码 解决办法 若是自己编写模块 需要涉及深度学习 这就是另一块大的内容了 在这里简单调用已经封装好的模块来实现获取验证码 本文以超级鹰为例 网址 http ww

随机推荐

  • PADS 原理图如何自动编号

    PADS原理图如何自动编号 PADS 原理图工具 PowerLogic exe 不支持元件位号重名 原生不提供自动编号功能 虽然 PowerPCB exe 可以支持元件位号自动编号功能再同步到原理图 但是其局限性太大没啥实际意义 另外 PA
  • YUV格式学习:YUYV、YVYU、UYVY、VYUY格式转换成RGB

    YUYV YVYU UYVY VYUY格式 它们都是YUV422的打包格式 即在内存中 Y U V都是挨着排序的 它们的名称就表示了Y U V的顺序 像YUYV 就是Y U Y V Y U Y V 在做转换时 就显得很容易 简单了 因为极其
  • 【卷积核设计】10、Scaling Up Your Kernels to 31x31

    文章目录 一 背景 二 方法 三 RepKLNet a Large Kernel Architecture 3 1 结构 3 2 尽可能的让卷积核变大 3 3 图像分类 3 4 语义分割 3 5 目标检测 四 分析 五 限制 六 结论 论文
  • 【笔试强训选择题】Day43.习题(错题)解析

    作者简介 大家好 我是未央 博客首页 未央 303 系列专栏 笔试强训选择题 每日一句 人的一生 可以有所作为的时机只有一次 那就是现在 文章目录 前言 一 Day43习题 错题 解析 总结 前言 一 Day43习题 错题 解析 1 解析
  • Elasticsearch笔记4 基础入门

    执行分布式检索 一个查询操作 在ES分布式环境中分为两步 查询与合并 查询阶段 ES集群向所有分片传递查询语句 分片接收到请求后 执行搜索并建立一个长度为top n的优先队列 存储结果 top n 的大小取决于分页参数 top n from
  • 链表c语言stl,C++STL之List容器

    1 再谈链表 List链表的概念再度出现了 作为线性表的一员 C 的STL提供了快速进行构建的方法 为此 在前文的基础上通过STL进行直接使用 这对于程序设计中快速构建原型是相当有必要的 这里的STL链表是单链表的形式 2 头文件 头文件
  • Vue3+Vite项目配置Eslint+Prettier+Husky+Lint-Staged+Commitlint

    Eslint 配置 ESLint 是一个插件化并且可配置的 JavaScript 语法规则和代码风格的检查工具 ESLint 能够帮你轻松写出高质量的 JavaScript 代码 1 建议 vscode 安装 Eslint 的插件 这个插件
  • 用JAVASCRIPT从弹出的窗口中获取值

    设三个页面 Father aspx SubOpen aspx SubModalDialog aspx 在Father aspx中
  • Excel构建决策分析模型

    特点 探讨使用 Excel 构建决策模型的价值和重要性 以及对 Excel 复杂性的非常详细和深入的解释 使用 Excel 的图形功能来有效地呈现定量数据 比率和间隔 来通知和影响目标对象 利用 Excel 的内置数据可视化和操作功能准备数
  • kubernetes session保持等设置

    session保持 如何在service内部实现session保持呢 当然是在service的yaml里进行设置啦 在service的yaml的sepc里加入以下代码 sessionAffinity ClientIP sessionAffi
  • matplotlib怎么在一张图上画多条曲线?

    问题 多个plot画不到一张图上 解决方法 多个plot用一个plt show 即可 一次plt show 就会有一次输出 如何让函数画在同一张画布上 for i in range 1 15 3 train score test score
  • bh1750c语言程序,BH1750FVI数字光线强度传感器 51单片机源程序

    BH1750FVI IIC测试程序 使用单片机STC89C51 晶振 11 0592M 显示 LCD1602 编译环境 Keil uVision2 参考宏晶网站24c04通信程序 时间 2011年4月20日 include include
  • vue3表格导出excel

    下载依赖 npm install xlsx 引入依赖 import as XLSX from xlsx 使用
  • Python内置类型转换函数

    chr i chr 函数返回ASCII码对应的字符串 gt gt gt print chr 65 A gt gt gt print chr 66 B gt gt gt print chr 65 chr 66 AB complex real
  • stm32f103zet6移植标准库的sdio驱动

    sdio移植 st官网给的标准库有给一个用于st出的评估板的sdio外设实现 但一是文件结构有点复杂 二是相比于国内正点原子和野火的板子也有点不同 因此还是需要移植下才能使用 当然也可以直接使用正点原子或野火提供的实例 但为了熟悉下sdio
  • C#委托、C++委托实现、C回调函数

    C 委托 C 中的委托 Delegate 定义了方法的类型 使得可以将方法当作另一个方法的参数来进行传递 这种将方法动态地赋给参数的做法 可以避免在程序中大量使用if else switch 语句 同时使得程序具有更好的可扩展性 委托特别用
  • 从NSGA到 NSGA II

    NSGA 非支配排序遗传算法 NSGAII 带精英策略的非支配排序的遗传算法 都是基于遗传算法的多目标优化算法 都是基于pareto最优解讨论的多目标优化 遗传算法已经做过笔记 下面介绍pareto 帕累托 最优解的相关概念 本文是基于参考
  • Qt实现俄罗斯方块

    在经过基础内容的学习和简单的小示例练习后 这节我们用Qt做个俄罗斯方块小游戏 本文会描述实现俄罗斯方块的部分思路 代码开源 详细可见代码 链接获取方式 开源代码已上传gitee 关注 程序媛讲QT 公众号 回复 俄罗斯方块 获取链接 俄罗斯
  • 三分钟理解Java中字符串(String)的存储和赋值原理

    可能很多Java的初学者对String的存储和赋值有迷惑 以下是一个很简单的测试用例 你只需要花几分钟时间便可理解 1 在看例子之前 确保你理解以下几个术语 栈 由JVM分配区域 用于保存线程执行的动作和数据引用 栈是一个运行的单位 Jav
  • react中的export什么函数能导出_esModuleInterop 到底做了什么?

    问题引入 很多 react 使用者在从 JS 迁移到 TS 时 可能会遇到这样一个问题 JS 引入 react 是这样的 js import React from react 而 TS 却是这样的 ts import as React fr