巧用 exports 和 typeVersions 提升 npm 包用户使用体验

news2024/11/15 10:58:00

默认导出

对于开发一个 JavaScript 三方库供外部使用而言,package.json是其中不可缺少的一部分

一般而言,对于库开发者来说,我们会在package.json中指定我们的导出入口。一般而言会涉及两个字段mainexport,它们会涉及到当前模块在被导入的行为。通常我们会将main字段指向 cjs 产物,module字段指向 ES 产物

main

main字段指定了该模块的主入口文件,即 require 该模块时加载的文件。该字段的值应为相对于模块根目录的路径或者是一个模块名(如index.jslib/mymodule.js,如果是模块名,则需要保证在该模块根目录下存在该模块)。主入口文件可以是 JavaScript 代码、JSON 数据或者是 Node.js C++扩展

module

module字段是 ES 模块规范下的入口文件,它被用于支持 import 语法。当使用 esm 或 webpack 等工具打包时,会优先采用 module 字段指定的入口文件。如果没有指定 module 字段,则会使用 main 字段指定的入口文件作为默认的 ES 模块入口文件

指定导出

一般情况下,我们使用mainmodule在大部分场景下对于开发一个库来说已经足够。但是如果想实现更精细化的导出控制就无法满足

当我们一个库本身同时包含运行时和编译时的导出时,如果我们导出的模块在编译时(node 环境)包含副作用,如果运行时模块也从同一入口导出就会出现问题

// 例如编译时入口存在以下编译时副作用
// buildtime.ts
console.log(process.env.xxx)
export const buildLog = () => console.log("build time")
// runtime.ts
export const runLog = () => console.log("run time")
// index.ts
export * from "./buildtime.ts"
export * from "./runtime.ts"

当前,可以通过解决掉副作用规避这个问题,但是很可能我们依赖的第三方模块也是有复作用的这个时候就无解了。此时最好的办法是将这个库的运行时和编译时从两个入口进行导出,这样子就不存在某一方影响到另一方。库使用者也不需关心从统一入口导入的方法到底是编译时方法还是运行时方法

这个时候就可以利用package.jsonexports字段进行导出,当存在该字段时会忽略mainmodule字段。该字段在 Node.js 12 版本中引入,可用来大幅简化模块的导出方式,支持同时支持多个环境下的导出方式,提供了更好的可读性和可维护性

支持以下用法

  1. 多文件导出
  "name": "pkg",
  "exports": {
    ".": "./dist/index.js",
    "./runtime": "./dist/runtime.js",
    "./buildtime": "./dist/buildtime.js"
  }

这样当运行require('pkg') 时会加载dist/index.js,而当运行 require('pkg/runtime')时会加载dist/runtime.jsrequire('pkg/buildtime') 则会加载 dist/buildtime.js

  1. 多条件导出
{
  "name": "pkg",
  "version": "1.0.0",
  "main": "dist/index.js",
  "exports": {
    ".": {
      "require": "./dist/index.cjs",
      "import": "./dist/index.mjs",
      "node": "./dist/index.cjs",
      "default": "./dist/index.js"
    },
    "./runtime": {
      "require": "./dist/runtime.cjs",
      "import": "./dist/runtime.mjs",
      "node": "./dist/runtime.cjs",
      "default": "./dist/runtime.js"
    },
    "./buildtime": {
      "require": "./dist/buildtime.cjs",
      "import": "./dist/buildtime.mjs",
      "node": "./dist/buildtime.cjs",
      "default": "./dist/buildtime.js"
    }
  }
}

对于条件,目前 node 支持importrequirenodenode-addonsdefault。同时社区对于其它环境也定义了如typesdenobrowser等供不同环境使用。具体规范可见

  1. 目录导出
    支持目录的整体导出
{
  "exports": {
    "./lib/*": "./lib/*.js"
  }
}

类型

按照上述操作完成后,打包就能符合相关预期,但是对于 typescript 文件的导入如果使用runtime路径是会找不到相应的类型文件,typescript 并不会去识别该字段,已有的讨论issues

此时需要借助package.jsontypeVersions字段进行声明供 ts 识别

对于这个例子,我们在库的package.json中增加如下,表示各路径分别导出的类型文件路径

  "typesVersions": {
    "*": {
      ".": ["./dist/index.d.ts"],
      "runtime": ["dist/runtime.d.ts"],
      "buildtime": ["dist/dist/runtime.d.ts"]
    }
  },

此时我们就能看见能正确找到相应的类型提示

实现

目前 Node.js 12+和主流的打包工具都已经支持exports字段的解析,下面来简单看下webpack的实现

Webpack

webpack已经内置支持对于exports的解析,它的解析由enhance-resolve实现

createResolverenhance-resolve导出的create函数,用法如下

// https://github.com/webpack/enhanced-resolve/blob/main/README.md
const fs = require("fs");
const { CachedInputFileSystem, ResolverFactory } = require("enhanced-resolve");

// create a resolver
const myResolver = ResolverFactory.createResolver({
	// Typical usage will consume the `fs` + `CachedInputFileSystem`, which wraps Node.js `fs` to add caching.
	fileSystem: new CachedInputFileSystem(fs, 4000),
	extensions: [".js", ".json"]
	/* any other resolver options here. Options/defaults can be seen below */
});

// resolve a file with the new resolver
const context = {};
const lookupStartPath = "/Users/webpack/some/root/dir";
const request = "./path/to-look-up.js";
const resolveContext = {};
myResolver.resolve(context, lookupStartPath, request, resolveContext, (
	err /*Error*/,
	filepath /*string*/
) => {
	// Do something with the path
});

通过创建一个自定义resolver函数后可调用resolve函数根据当前的模块路径和一些配置查找一个模块的绝对路径

相关自定义resolver选项含义

  • extensions 查找的文件扩展名
  • conditionNames 对应package.json中的exports条件
  • exportsFields 指定从package.json哪个字段读取exports条件
  • fullySpecified 为 true 时,解析器会优先尝试使用完全指定的路径来解析模块请求,而忽略其他任何条件。如果找到了对应的模块文件,则直接返回该路径;否则抛出错误

通过相关上述代码我们可以知道

  • 对于解析es导入,webpack会尝试读取exports字段的导出,依次读取importnode字段。并且这里也是直接配置了fullySpecified。即处理相对路径的导入如import foo from './foo';时,Webpack在解析模块请求时会直接将 ./foo.js 当作完整路径来处理,而不进行路径的拼接和解析
  • 对于解析cjs导入,webpack会尝试读取exports字段的导出,依次读取requirenode字段。并且会尝试使用各种解析策略来解析该路径

由于enhance-resolve是一个完全独立于webpack的模块,当我们自己实现一个三方打包器或者插件时,如果想实现类似的模块解析能力,也可以完全独立使用enhance-resolve来实现

总结

为了实现一个库更友好的导出,我们可以借助 package.json 的exports字段指定多条件的导出方式,主流打包工具以及 Node.js 都已经支持;对于 ts 类型,我们可以结合typeVersions进行配置

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

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

相关文章

开关电源基础03:正激和反激开关电源拓扑(2)-半桥和全桥拓扑

说在开头:关于薛定谔的波动方程(3) 波动方程在矩阵派的内部也大受欢迎,首先是海森堡的老师索末菲,然后是建立矩阵力学的核心人物之一的另一位老师:马克思.玻恩。玻恩在薛定谔方程刚出来时就赞扬了他的成就…

宕机了?!DolphinScheduler 高可用和 Failover 机制关键时刻保命

点击蓝字 关注我们 高可用性是 Apache DolphinScheduler 的特性之一。它通过冗余来避免单点问题,所有组件天然支持横向扩容;但仅仅保证了冗余还不够,当系统中有节点宕机时,还需要有故障转移机制能够自动将宕机节点正在处理的工作转…

【react 全家桶】高级指引(上)

本人大二学生一枚&#xff0c;热爱前端&#xff0c;欢迎来交流学习哦&#xff0c;一起来学习吧。 <专栏推荐> &#x1f525;&#xff1a;js专栏 &#x1f525;&#xff1a;vue专栏 &#x1f525;&#xff1a;react专栏 文章目录 12 【react高级指引&#xff08;上&…

I.MX6Q-SDB开发板移植ubuntu

I.MX6Q-SDB开发板移植ubuntu 0.前言一、准备工作二、ubuntu移植1.下载ubuntu发布的根文件系统2.根文件系统的简单修改3.板卡适配设置4.打包根文件系统 三、烧写镜像1.dd命令2.uuu工具3.mfgtool工具4.i.mx6q-sdb的拨码设置&#xff1a; 四、大无语事件 0.前言 这两天收拾杂货堆&…

密码学【java】初探究加密方式之数字签名

文章目录 前言1 数字签名简介2 基本原理3 数字证书4 网页加密5 edge的网站连接图标6 代码实现7 keytool工具使用7.1 常用命令&#xff1a;7.2 生成私钥公钥[未实践成功]7.3 导出公钥 前言 有关keytool的使用部分&#xff0c;未实现&#xff0c;先记录下来&#xff01;&#xf…

『python爬虫』12. 模拟登陆之cookie的使用(保姆级图文)

目录 session1. 模拟登陆取得cookie2. 在登录的情况下继续取得书架上的数据3. 在已经有cookie的情况下直接请求总结 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 session session和我们之前用的request十分相似&…

AWS Lambda - 同步/异步调用,事件源,目标

Hello大家好&#xff0c;我们今天继续讨论AWS Lambda的内容。 同步调用 Lambda函数有三种调用方式。 第一种方式是同步调用。 当我们使用API、CLI以及API网关等调用函数时&#xff0c;就是同步调用。 当您同步调用函数时&#xff0c;Lambda会运行该函数并等待响应&#xff…

微服务---Redis入门篇-Redis的常见命令和客户端使用

Redis快速入门 Redis的常见命令和客户端使用 1.初识Redis Redis是一种键值型的NoSql数据库&#xff0c;这里有两个关键字&#xff1a; 键值型 NoSql 其中键值型&#xff0c;是指Redis中存储的数据都是以key、value对的形式存储&#xff0c;而value的形式多种多样&#xf…

易视腾iS-E5-NGH_3798MV100_MT7601_卡刷固件包_当贝纯净桌面

易视腾iS-E5-NGH_3798MV100_MT7601_卡刷固件包_当贝纯净桌面 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精简内置的没用的软件&#xff0…

单链表OJ题:LeetCode--206.反转链表

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;今天给大家带来的是LeetCode中206题&#xff1a;反转链表 数 据 结 构&#xff1a;数据结构专栏 作 者&#xff1a;stackY、 C 语 言 &#xff1a;C语言专栏 LeetCode &#xff1a;LeetCode刷题训练营 LeetCod…

TiDB实战篇-数据库热点问题

形成热点的原因 主要是因为数据插入进去的时候是按顺序加数据的。 数据分裂以后还是在一个store上面&#xff0c;就会形成读写热点。 没有走索引全表扫描的情况。 定位热点 如果有热点&#xff0c;那么它的查询语句应该是比较多的&#xff0c;容易在这个地方找到对应的热点问…

Leetcode434. 字符串中的单词数

Every day a leetcode 题目来源&#xff1a;434. 字符串中的单词数 解法1&#xff1a;istringstream 我们知道&#xff0c;C默认通过空格&#xff08;或回车&#xff09;来分割字符串输入&#xff0c;即区分不同的字符串输入。 istringstream类用于执行C风格的串流的输入操…

Flowable入门

Flowable初体验 Flowable是什么 Flowable 是一个使用 Java 编写的轻量级业务流程引擎&#xff0c;常用于需要人工审批相关的业务&#xff0c;比如请假、报销、采购等业务。 为什么要使用工作流呢&#xff1f; 对于复杂的业务流程&#xff0c;通过数据库的状态字段难以控制和…

软考信管高级——质量管理

质量管理内容 质量保证QA(过程符合要求/过程改进&#xff09; (1)按项目计划开展质量活动&#xff0c;使项目过程和产品符合质量要求&#xff0c;即按计划做质量&#xff1b; (2)提高项目干系人对项目将要满足质量要求的信心&#xff1b; (3)按过程改进计划进行过程改进&…

金兰组织 | 2023金兰解决方案集经营管理篇正式发布

为助力企业创新管理、提质增效&#xff0c;人大金仓携手金兰组织成员单位&#xff0c;于近期发布多项经营管理领域的联合解决方案&#xff0c;共享创新应用成果。 /人大金仓高级副总裁宋瑞/ 人大金仓高级副总裁宋瑞在致辞中表示&#xff1a;“联合解决方案创新是指通过把不同领…

利用谷歌云Pub/Sub 实现多任务并行分发处理方案

背景 目前老梁团队负责的Global Data Integration Platform每天有大量文件需要从来自不同地区的上游下载文件并进行处理后再发送到不同下游。老梁的数据集成平台集群有6个服务器节点&#xff0c;老梁希望所有机器的资源都能利用上&#xff0c;提升大量文件并行处理能力&#x…

C# Microsoft.ClearScript.V8脚本使用

1、ClearScript支持的功能和适用场景 微软的.net是非常强大和灵活的&#xff0c;除了C#体系脚本扩展&#xff0c;也支持其他流行的脚本扩展&#xff0c;Microsoft.ClearScript.V8就是一个.NET绑定到Google V8的脚本引擎。它允许.NET应用程序直接从JavaScript代码中调用函数&am…

Redis布隆过滤器的原理和应用场景,解决缓存穿透

目录 专栏导读一、布隆过滤器BloomFilter是什么二、布隆过滤器BloomFilter能干嘛?三、布隆过滤器使用场景1、解决缓存穿透问题2、黑名单3、网页爬虫对URL的去重,避免爬取相同的URL地址四、操作布隆过滤器BloomFilter1、使用布隆过滤器2、删除key3、判断是否存在五、代码实例1…

黑客开始使用双 DLL 侧载来逃避检测

一个名为“Dragon Breath”、“Golden Eye Dog”或“APT-Q-27”的 APT 黑客组织正在展示一种新趋势&#xff0c;即使用经典 DLL 旁加载技术的多种复杂变体来逃避检测。 这些攻击变体从一个初始向量开始&#xff0c;该向量利用一个干净的应用程序&#xff0c;最常见的是 Telegr…

vue_组件基础

单文件组件 Vue 单文件组件&#xff08;又名 *.vue 文件&#xff0c;缩写为 SFC&#xff09;是一种特殊的文件格式&#xff0c;它允许将 Vue 组件的模板、逻辑 与 样式封装在单个文件中 <template><h3>单文件组件</h3> </template><script> ex…