exports使用 package.json字段控制如何访问你的 npm 包

目录

想象一下你正在开发一个 npm 包……

术语

什么是exports领域?

exports好处

保护内部文件

多格式包

将子路径映射到dist目录

子路径导出

单一入口点

多个入口点

公开软件包文件的子集

有条件出口

设置使用条件

默认条件

句法

针对 Node.js 和浏览器


想象一下你正在开发一个 npm 包……

您希望提供多个入口点,但同时限制对内部文件的访问。您需要同时支持 CJS 和 ESM,包含类型定义,甚至可能还要确保浏览器兼容性。您如何管理所有这些需求?

在早期版本的 Node.js 中,包使用main字段 in package.json来定义单个入口点这种方法虽然简单,但存在局限性:它只允许一个入口点,并且包中的所有文件都可访问,无法保护内部文件。随着生态系统的发展(尤其是 ESM 的兴起和对多格式包的需求),这种方法很快就显得力不从心。

术语

  • ECMAScript 模块(ESM):JavaScript 使用原生import&export语法的标准化模块格式。

  • CommonJS (CJS):Node.js 的遗留模块格式,用于require()导入和module.exports导出。

  • 包入口点:访问包的入口路径(例如pkg-apkg-a/file)。

  • 包子路径:包名称后面的路径(例如,/this/is/subpath.jspkg-a/this/is/subpath.js)。

什么是exports领域?

该字段在Node.js v12.7.0(2019 年 7 月)中引入,通过两个核心功能满足了这些需求:exports package.json

  1. 子路径导出:包可以定义多个入口点,只允许公开特定文件,同时阻止对包内部的访问。

  2. 条件导出:包可以切换入口点,以针对不同的环境(例如,Node.js 与浏览器)和模块类型(例如,CJS 与 ESM)解析不同的文件。

从那时起,exports它得到了主要 JavaScript 工具和构建系统的广泛支持,例如 TypeScript、Deno、Vite、Webpack、esbuild 等。

exports好处

保护内部文件

以前,用户可以导入软件包中的任何文件,甚至是内部文件。这导致软件包维护者难以更新或重构软件包,因为他们无法判断用户是否依赖这些内部文件。现在exports,维护者可以明确定义哪些文件可以访问,从而建立清晰的公共 API,并防止意外导入内部文件。这有助于维护者管理更新,而不会给用户带来损坏的风险。


您可以使用子路径模式 ( ) 使软件包中的所有文件均可访问*。此模式会捕获子路径(包括嵌套路径)中的任何字符串,并将其映射到目标文件路径。使用此设置,用户可以通过引用路径来导入软件包中的任何文件。

*  匹配一切

*字符的行为与 glob 语法不同。它会捕获嵌套路径,并可能暴露比您预期更多的文件

{"name": "pkg-a","exports": {"./*": "./*" // 公开所有文件,包括嵌套路径}
}

尽可能避免暴露所有文件

允许用户导入任何文件意味着即使对界面进行微小的更改(包括您不希望用户访问的文件(例如,捆绑块))也会成为重大更改,并且需要进行重大的 semver 更新。

import foo from 'pkg-a' // 🚫 已阻止(入口点未定义)
import { name } from 'pkg-a/package.json' // ✅

多格式包

如今,软件包经常面临支持多种环境的挑战——Node.js、浏览器、ESM、CJS 和 TypeScript 定义。exports中的字段package.json允许您为每个环境和模块格式指定不同的文件。这确保了兼容性,并通过仅包含与每个目标相关的内容来优化导入。


为了让您的包同时支持 ESM 和 CommonJS 使用者,您可以根据包的导入方式指定需要加载的文件。这样,Node.js 和 TypeScript 就能在各自的上下文中解析正确的代码 ( import vs require) 和合适的类型定义 ( .d.mtsvs .d.cts)。

{"name": "pkg-a","exports": {"require": {"types": "./dist/index.d.cts",     // Types for require('pkg-a')"default": "./dist/index.cjs"      // Code for require('pkg-a')},"import": {"types": "./dist/index.d.mts",     // Types for import 'pkg-a'"default": "./dist/index.mjs"      // Code for import 'pkg-a'}}
}

将其与子路径导出相结合,您可以为每个入口点导出不同的类型,同时仍然支持 ESM 和 CommonJS 消费者:

{"name": "pkg-a","exports": {".": {"require": {"types": "./dist/index.d.cts",    // Types for require('pkg-a')"default": "./dist/index.cjs"     // Code for require('pkg-a')},"import": {"types": "./dist/index.d.mts",    // Types for import 'pkg-a'"default": "./dist/index.mjs"     // Code for import 'pkg-a'}},"./feature": {"require": {"types": "./dist/feature.d.cts",  // Types for require('pkg-a/feature')"default": "./dist/feature.cjs"   // Code for require('pkg-a/feature')},"import": {"types": "./dist/feature.d.mts",  // Types for import 'pkg-a/feature'"default": "./dist/feature.mjs"   // Code for import 'pkg-a/feature'}}}
}

常问问题

  • 我是否需要为require和提供单独的类型文件import

    是的。TypeScript 使用文件的扩展名.d.ts来推断其描述的模块格式。一个.d.cts文件代表一个 CommonJS.cjs文件,一个.d.mts文件代表一个 ESM.mjs文件。如果将两个文件放在同一个.d.ts文件里,TypeScript 会错误地解释模块格式,并可能导致代码在运行时失败。

    请参阅 Andrew Branch (TypeScript 核心团队) 在类型错误吗?中解释这种不匹配→ 🎭 伪装成 CJS。

  • 每个条件块内的键的顺序重要吗?

    是的。该types字段必须放在前面default,TypeScript 才能正确识别。如果放在后面,TypeScript 会忽略它。

  • 消费者需要哪些 TypeScript 设置?

    它们必须在其 中设置 moduleResolution Node16NodeNext或。这些模式启用条件导出解析和正确的模块格式检测。Bundler,tsconfig.json

要了解更多信息,请参阅TypeScript 文档。其中深入介绍了配置exportsTypeScript 的其他方法(例如,跨 TypeScript 版本导出不同类型)。

将子路径映射到dist目录

JavaScript 项目经常将目录中的代码编译src到 中dist,从而产生类似 的导入import foo from 'pkg-a/dist/util'。包作者可能不希望dist在导入路径中包含 ,以获得更简单的 API,但将文件输出到包根目录需要复杂的发布步骤,这可能会污染开发环境。

通过该exports字段,包子路径可以直接映射到dist目录内部,从而允许消费者使用更清洁的导入,而import foo from 'pkg-a/util'无需为维护者提供复杂的发布脚本。


exports字段的 subpaths 对象允许您将任意子路径定义为映射到包中文件路径的键。这使您可以使用更简单、更短的子路径来公开深度嵌套的路径。

{"name": "pkg-a","exports": {"./deep-file": "./dist/deep/deep/file.js", // 直接映射到文件"./*": "./dist/*" // 在根级别公开 dist 中的所有内容}
}
import foo from 'pkg-a' // 🚫 已阻止(入口点未定义)
import bar from 'pkg-a/deep-file' // ✅ - 解析为 dist/deep/deep/file.js
import baz from 'pkg-a/file.js' // ✅ - 解析为 dist/file.js

子路径导出

子路径导出允许您定义包的入口点并将它们映射到包内的文件路径。


要定义多个入口点,exports可以将该字段设置为子路径对象,其中每个键都以 开头..键表示主包入口,子路径以 开头./。键可以映射到包内的文件路径,也可以映射到条件对象(我们稍后会讨论)。

单一入口点

该字段最简单的用法exports是指向包入口文件的字符串。虽然它与 字段类似main,但有一个显著的区别:一旦使用exports它就会将您的包黑框起来。这意味着除非明确指定,否则默认情况下任何子路径(甚至package.json)都无法访问。

{"name": "pkg-a","exports": "./index.js" // Package entry point
}
import foo from 'pkg-a' // ✅ Resolves to pkg-a/index.js
import { name } from 'pkg-a/package.json' // 🚫 Blocked

多个入口点

要定义多个入口点,请设置exports为一个子路径对象——该对象的每个键都以 开头.,值是包内某个文件的相对路径。如上所述,.键表示主包入口,子路径以 开头./

{"name": "pkg-a","exports": {".": "./index.js", // Package entry point"./package.json": "./package.json" // Allow importing pkg-a/package.json}
}
import foo from 'pkg-a' // ✅
import { name } from 'pkg-a/package.json' // ✅


公开软件包文件的子集

要仅公开特定目录,请将子路径模式放置在该子目录中。此方法允许使用者仅从指定目录导入文件。此外,您还可以通过将子路径映射到 来阻止对子路径的访问null

{"name": "pkg-a","exports": {"./dist/*": "./dist/*", // Only expose the dist directory"./dist/internal/*": null // Blocks access to dist/internal}
}

import foo from 'pkg-a' // 🚫 Blocked (entry point not defined)
import bar from 'pkg-a/dist/file.js' // ✅
import baz from 'pkg-a/dist/dir/file.js' // ✅
import qux from 'pkg-a/dist/internal/file.js' // 🚫 Blocked
import quux from 'pkg-a/dist/internal/dir/file.js' // 🚫 Blocked

 

有条件出口

条件导出是一个非常强大的功能。它使你的包能够根据使用者提供的条件动态加载不同的文件。利用此功能,你可以针对各种环境优化你的包。

举个简单的例子,假设你希望你的包入口点在两个不同的文件之间切换。为此,请在你的 字段中设置一个条件导出对象exports package.json

{"name": "pkg-a","exports": {// Ordered by priority"condition-a": "./file-a.js","condition-b": "./file-b.js"}
}

导入此包时,加载的文件取决于运行时提供的条件:

import foo from 'pkg-a' // ❓ 根据提供的条件可以是file-a.js或file-b.js

设置使用条件

node

现在你已经为你的包设置了条件和入口点,那么如何在用户端切换它们呢?这取决于谁在解析导入。

  • 如果您使用的是 Node.js,则可以使用标志指定条件。例如,这将加载,因为我们指定了:--conditions, -Cfile-a.js condition-a

$ node --conditions=condition-a ./load-pkg-a.js
  • 如果您使用捆绑器,则可以在配置中传入条件。例如,使用 Vite 时,您可以传入条件(下面列出了支持条件的工具的文档)。resolve.conditions

  • 如果没有提供条件,则将无法解决并引发错误,因为没有default定义条件。

vite

 包的情景模式

// package.json"exports": {".": {"custom": "./index.custom.js","import": "./index.mjs","require": "./index.cjs"}}

配置 

// vite.config.tsresolve: {conditions: ['custom'],},

 

 

默认条件

每个运行时/解析器通常设置自己的默认条件(这些不是按顺序排列的):

  • Node.js:node,,,import require​ default
  • Vite:import,,,,,,或require​  default module​​browser production development
  • esbuild:import,,,,,,require default​ browser node​ module

句法

与子路径对象相反,条件导出对象exports字段内的任何对象,其键并非全部以 开头.

条件(对象键)按优先级排序,并解析为第一个匹配的条目。(这可能感觉不直观,因为 JavaScript 中的对象在技术上是无序的。)对象也可以嵌套,以指定解析文件所需的条件组合。

条件键的顺序很重要

由于解析器具有默认条件,并且返回其匹配的第一个条件,因此应始终首先指定您的自定义条件(例如,无法达到requireimport、以下的任何内容)。default

    "exports": {"import": "./prod.mjs","require": "./prod.cjs",// 这将永远不会匹配,因为它低于默认条件"this-will-never-match": "./dev.ts"}
}

针对 Node.js 和浏览器

exports字段可以定义一个适应 Node.js 或浏览器环境的入口点。在 Node.js 运行时中,用于解析的默认条件包括nodedefault和导入类型(import对于 ESM 为 ,require对于 CJS 为 )。

条件的优先级取决于包的条件导出对象中的关键顺序。

{"name": "pkg-a","exports": {"node": "./dist/for-node.js", // Resolved by Node.js"default": "./dist/for-browsers.js" // Resolved by other environments}
}

default条件适用于非 Node 环境。或者,您可以使用browser Vite、Webpack 和 Parcel 等 Web 应用打包工具能够识别的条件。

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

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

相关文章

AngularJS 安装使用教程

一、AngularJS 简介 AngularJS 是 Google 开发的一款前端 JavaScript 框架,采用 MVVM 架构,提供了数据双向绑定、依赖注入、模块化、路由管理等强大功能,适合构建单页面应用(SPA)。注意:AngularJS&#xf…

基于python和neo4j构建知识图谱医药问答系统

一、pyahocorasick1.安装 pyahocorasick 包: pip install pyahocorasick -i https://pypi.tuna.tsinghua.edu.cn/simple/pip install pyahocorasick :安装名为 pyahocorasick 的第三方库👉 这个库是一个 Aho-Corasick 多模匹配算法 的 Python…

片上网络(NoC)拓扑结构比较

1. 拓扑结构拓扑结构延迟吞吐量跳数功耗面积开销可扩展性容错性布线复杂度适合通信模式Mesh(网格)低(O(√N))高(多路径并行)O(√N)中高(路由器多)中高(规则布线&#xff…

git merge 命令有什么作用?具体如何使用?

回答重点git merge 命令主要用于将两个分支的历史和内容合并在一起。简而言之,它会将一个分支的更改引入到当前分支中。常见的使用场景是将功能分支(feature branch)的修改合并回主分支(main branch)或者开发分支&…

【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - jieba库分词简介及使用

大家好,我是java1234_小锋老师,最近写了一套【NLP舆情分析】基于python微博舆情分析可视化系统(flaskpandasecharts)视频教程,持续更新中,计划月底更新完,感谢支持。今天讲解 jieba库分词简介及使用 视频在线地址&…

大模型的后训练与逻辑能力

《DeepSeek原生应用与智能体开发实践》【摘要 书评 试读】- 京东图书 在人工智能与机器学习领域,模型的后训练阶段不仅是技术流程中的关键环节,更是提升模型性能,尤其是数学逻辑能力的“黄金时期”。这一阶段,通过对已初步训练好…

pycharm安装教程-PyCharm2025安装步骤【MAC版】附带安装包

pycharm安装教程-PyCharm2025安装详细步骤【MAC版】安装安装包获取(文章末尾)今天来给大家分享 Mac 系统安装 PyCharm,附带安装包资源安装, PyCharm 相关就不叙述了,直接开始安装! 安装 2024版本、2025年…

【React Native】路由跳转

Link 跳转的路径,就在href里写/details。路径都是相对于app目录来写的,也就是说app目录就是/。很多时候,需要跳转的组件比较复杂。比方说,要在里面要嵌套按钮,或者其他东西。这种情况下,就可以在Link组件里…

使用 Spring Boot + AbstractRoutingDataSource 实现动态切换数据源

1. 动态切换数据源的原理AbstractRoutingDataSource 是 Spring 提供的一个抽象类,它通过实现 determineCurrentLookupKey 方法,根据上下文信息决定当前使用的数据源。核心流程如下:定义多数据源配置:注册多个数据源。实现动态数据…

Kubernetes (K8S)知识详解

Kubernetes (K8S) 是什么? Kubernetes 是 Google 在 2014 年开源的生产级别的容器编排技术(编排也可以简单理解为调度、管理),用于容器化应用的自动化部署、扩展和管理。它的前身是 Google 内部的 Borg 项目,Borg 是 …

在github上传python项目,然后在另外一台电脑下载下来后如何保障成功运行

如何在 GitHub 上传并在另一台电脑成功运行 Python 项目✅ 一、上传前(本地准备) 在你的项目文件夹中进行以下准备: 1. 确保结构清晰 my_project/ ├── main.py ├── utils.py ├── config.yaml ├── requirements.txt └── README…

详解Mysql Order by排序底层原理

MySQL 的 ORDER BY 子句实现排序是一个涉及查询优化、内存管理和磁盘 I/O 的复杂过程。其核心目标是高效地将结果集按照指定列和顺序排列。一、确定排序模式 (Sort Mode)MySQL 根据查询特性和系统变量决定采用哪种排序策略&#xff1a;1.1 Rowid 排序<sort_key, rowid> 模…

SpringBoot的介绍和项目搭建

SpringBoot是简化Spring应用开发的一个框架&#xff0c;他是Spring技术栈的整合。优点&#xff1a;能够快速创建独立运行的Spring项目以及与主流框架集成使用嵌入式的Servlet容器&#xff0c;应用无需打成war包&#xff0c;内嵌tomcatStarters自动依赖和版本控制大量的自动装配…

Selenium 攻略:从元素操作到 WebDriver 实战

在自动化测试、网页数据爬取、批量操作网页等场景中&#xff0c;Selenium 无疑是最受欢迎的工具之一。作为一款强大的 Web 自动化工具&#xff0c;它能模拟人类操作浏览器的行为&#xff0c;实现点击、输入、跳转等一系列动作。本文将从基础到进阶&#xff0c;全面解析 Seleniu…

【算法训练营Day14】二叉树part4

文章目录找树左下角的值路径总和总结&#xff1a;递归函数的返回值路径总和 II总结&#xff1a;二叉树递归的思考从中序与后序遍历序列构造二叉树找树左下角的值 题目链接&#xff1a;513. 找树左下角的值 解题逻辑&#xff1a; 使用层序遍历&#xff0c;将最后一层的第一个元…

工资系统如何计算工资

工资系统计算工资是一个集成数据收集、规则应用、自动核算和合规审核的自动化过程&#xff0c;以下是其核心原理和步骤&#xff0c;结合技术实现与法规要求进行说明&#xff1a;⚙️ 一、工资系统的基本框架与数据准备系统初始化与规则配置企业信息设置&#xff1a;录入公司名称…

车载通信架构 --- DoIP协议通信

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…

基于Event Sourcing和CQRS的微服务架构设计与实战

基于Event Sourcing和CQRS的微服务架构设计与实战 业务场景描述 在电商系统中&#xff0c;订单的高并发写入与复杂的状态流转&#xff08;下单、支付、发货、退货等&#xff09;给传统的CRUD模型带来了挑战&#xff1a; 数据一致性难保证&#xff1a;跨服务事务处理复杂&#x…

初级安全课第二次作业

&#xff08;一&#xff09;xss-labs 1~8关 1、前期准备 &#xff08;1&#xff09;打开小皮面板&#xff0c;并启动Apache和MySQL&#xff08;2&#xff09;将 xss-labs放到 phpstudy_pro 的 WWW 目录下&#xff08;3&#xff09;访问连接&#xff1a;http://localhost/xss-la…

从零搭建智能搜索代理:LangGraph + 实时搜索 + PDF导出完整项目实战

传统的AI聊天系统往往局限于预训练数据的知识范围&#xff0c;无法获取实时信息。本文将详细阐述如何构建一个基于LangGraph的智能代理系统&#xff0c;该系统能够智能判断何时需要进行网络搜索、有效维护对话上下文&#xff0c;并具备将对话内容导出为PDF文档的功能。 本系统…