Modularization
A summary of frontend modularization
Updated
The Evolution of Modularization
Goals
- Reduce naming conflicts
- Split code and load on demand
- High reusability
- High maintainability
The Global function Pattern
Form
function module1() {
return {
method1() {},
}
}
function module2() {
return {
method1() {},
}
}
Advantages
- Simple to use; dependent modules are easy to call, reducing duplicate code
- Isolates scope, avoiding conflicts between variable and function names that could cause variables or functions to be accidentally overwritten
Disadvantages
- Pollutes the global scope at the function-name level; a large number of functions get attached to
window - Getting a variable requires calling a function every time, making it hard to isolate variables (each call resets them)
- Lacks access control; internal data may be accidentally modified by other modules due to things like typos
The namespace Pattern
Form
const myModule = {
data: 'Naprte',
foo() {
console.log(`foo() ${this.data}`)
},
bar() {
console.log(`bar() ${this.data}`)
},
}
myModule.data = 'the data inside the module can be modified directly'
myModule.foo()
Advantages
- Simple to use; dependent modules are easy to call, reducing duplicate code
- Isolates scope, avoiding conflicts between variable and function names that could cause variables or functions to be accidentally overwritten
- The module can remain a singleton — no need to call a function every time
- Compared to the global
functionform, it reduces global pollution
Disadvantages
- Pollutes the global scope at the variable-name level; a large number of global variables get attached to
window - Lacks access control; internal data may be accidentally modified by other modules due to things like typos
The IIFE Pattern
Form
// module.js file
(function (window) {
const data = 'www.naparte.com'
// Functions that operate on the data
function foo() {
// Functions to be exposed
console.log(`foo() ${data}`)
}
function bar() {
// Functions to be exposed
console.log(`bar() ${data}`)
otherFun() // Internal call
}
function otherFun() {
// An internal private function
console.log('otherFun()')
}
// Expose the behavior
window.myModule = { foo, bar } // ES6 syntax
})(window)
Advantages
- Creates an independent scope, avoiding global pollution
- Can perform internal initialization and stay a singleton
- Tighter access control — thanks to the nature of anonymous functions, the outside cannot easily modify the inside of an
IIFEdirectly
Disadvantages
- The tight access control makes reuse difficult; calling and reusing requires nested
IIFEs, resulting in an unclear code structure - Hard to handle scenarios where multiple modules use the same module
CommonJS Modules
Form
// example.js
const x = 5
function addX(value) {
return value + x
}
module.exports.x = x
module.exports.addX = addX
// index.js
const example = require('./example.js')
console.log(example.x) // 5
console.log(example.addX(1)) // 6
Advantages
- Scope is isolated at the file level; internals are private, and public content is exported via
module.exportsandexports - No pollution of the global scope
- While access is restricted, reuse by other modules is still supported
- Natively supported by Node.js, server-friendly
Disadvantages
- Modules are parsed with
DFSand loaded synchronously, which may block the page - Cumbersome to use in browsers; requires a build tool
- Sensitive to module loading order; an abnormal order may cause module initialization errors that are hard to troubleshoot
- When binding a primitive to
module.exports, you must modifymodule.exportsrather than the original variable, otherwise the outside cannot detect the change - Reusing modules is costly and may pull in large amounts of unused code (no
tree-shakingsupport)
The AMD Specification
Form
// Configure module paths (must be set before loading the library)
const requireConfig = {
paths: {
math: 'js/modules/math',
add: 'js/utils/add',
multiply: 'js/utils/multiply'
}
}
// Define the module math.js, depending on the 'add' and 'multiply' modules
define(['add', 'multiply'], (add, multiply) => {
return {
calculate(a, b) {
return multiply(add(a, 5), b)
}
}
})
// Load the module
require(['math'], (math) => {
console.log(math.calculate(2, 3)) // Output the calculation result
})
Advantages
- Supports asynchronous module loading
- Dependencies upfront — all dependencies must be declared before the module loads
- Supports on-demand loading; a module is only loaded when
requireis executed - Scope is isolated at the file level; internals are private, and public content is exported via
module.exportsandexports - No pollution of the global scope
- While access is restricted, reuse by other modules is still supported
Disadvantages
- Complex syntax, hard to read and write
- Requires an extra
require.jslibrary at runtime - No dynamic dependency loading — only static declarations
- AMD exports can be any value, lacking constraints
- Reusing modules is costly and may pull in large amounts of unused code (no
tree-shakingsupport) - AMD dependency loading and module initialization happen at runtime, which may increase the page's initial load latency (especially with long dependency chains)
- Asynchronous loading can make code execution order hard to trace, increasing debugging difficulty
The CMD Specification
Form
// Define a module without dependencies
define((require, exports, module) => {
exports.xxx = value
module.exports = value
})
// Define a module with dependencies
define((require, exports, module) => {
// Import a dependency module (synchronous)
const module2 = require('./module2')
// Import a dependency module (asynchronous)
require.async('./module3', (m3) => {})
// Expose the module
exports.xxx = value
})
// Import the modules to use
define((require) => {
const m1 = require('./module1')
const m4 = require('./module4')
m1.show()
m4.show()
})
Advantages
- Supports both synchronous and asynchronous code loading
- Supports dynamic import
- Controls exports via
module.exports - Easy to reuse
- Independent scope, avoiding global pollution
Disadvantages
- Depends on the
sea.jslibrary at runtime - Reusing modules is costly and may pull in large amounts of unused code (no
tree-shakingsupport)
ESM
Form
// The export command defines a module's public interface; the import command brings in functionality provided by other modules
/** Define the module math.js **/
var basicNum = 0;
var add = function (a, b) {
return a + b;
};
export { basicNum, add };
/** Reference the module **/
import { basicNum, add } from "./math";
function test(ele) {
ele.textContent = add(99 + basicNum);
}
// When using the import command, users need to know the name of the variable or function to load, otherwise it cannot be loaded. To make things easier for users — so they can load a module without reading the documentation — the export default command is used to specify a default export for the module.
// export-default.js
export default function () {
console.log("foo");
}
// import-default.js
import customName from "./export-default";
customName(); // 'foo'
Advantages
- Official ECMA standard with concise syntax
- Supports static analysis optimization and tree-shaking
- Module-level scope, avoiding global pollution
- Cross-platform compatibility — natively supported by browsers, nodejs, and other environments
Disadvantages
- Upgrading legacy code is relatively difficult
- Importing via
importmapin browser environments may have cross-origin issues - In some scenarios, a build tool like
webpackis needed to convert to other formats before the code can be used by other libraries
The UMD Specification
Form
(function (root, factory) {
if (typeof module === 'object' && typeof module.exports === 'object') {
console.log('This is the CommonJS module spec, nodejs environment')
module.exports = factory()
} else if (typeof define === 'function' && define.amd) {
console.log('This is the AMD module spec, e.g. require.js')
define(factory)
} else if (typeof define === 'function' && define.cmd) {
console.log('This is the CMD module spec, e.g. sea.js')
define((require, exports, module) => {
module.exports = factory()
})
} else {
console.log('No module environment; mount directly on the global object')
root.umdModule = factory()
}
})(this, () => {
return {
name: 'I am a UMD module',
}
})
Advantages
- Highly reusable — supports reuse across runtime environments, satisfying the CommonJS, AMD, and CMD specs at the same time
- Strong compatibility, covering mainstream runtime environments
- Supports progressive upgrades from older specs
Disadvantages
- High code complexity
- May pollute global variables, e.g. in browser environments
- No
tree-shakingsupport
Summary
- The
CommonJSspec is mainly used for server-side programming. It loads modules synchronously, which does not suit browser environments — synchronous means blocking loads, while browser resources load asynchronously — hence theAMDandCMDsolutions; - The
AMDspec loads modules asynchronously in browser environments and can load multiple modules in parallel. However,AMDhas high development costs — the code is hard to read and write, and the semantics of its module definition style are awkward; - The
CMDspec is very similar toAMD; both are used for browser programming, with nearby dependencies and lazy execution, and can easily run inNode.js; ES6implements module functionality at the language-standard level, in a fairly simple way, and can completely replace theCommonJSandAMDspecs as a universal module solution for both browsers and servers;UMDis an implementation that satisfies theCommonJS,AMD, andCMDstandards at the same time;
CommonJS
Introduction
- Implemented by the JS runtime — an upper-level remedy made when the underlying module mechanism was missing.
- Modules analyze their dependencies during the execution phase, using depth-first traversal; the execution order is parent -> child -> parent
- Module files are loaded and executed synchronously (no distinction between loading and parsing — everything runs synchronously)
- The position of import/export statements affects the module code's execution result
- A
CommonJSmodule exports a single object containing several variables
Use Cases
- Supported by default in nodejs
- Other scenarios need a build tool to bundle the code and simulate it with a runtime polyfill, e.g. running in browsers
Module Loading Errors
As a runtime solution, loading errors are thrown via throw
Execution Order
CommonJS modules execute in order. When a require is encountered, the corresponding module's code is loaded and executed, and then execution returns to the current module. As shown below, module A depends on modules B and C, and module A is split top-down by 2 require statements into 3 segments, denoted A1, A2, A3.
---
title: CommonJS Module Relationship Diagram
---
flowchart TD
A[Module A]
B[Module B]
C[Module C]
A --> B
A --> C
The execution order is: A1 -> B -> A2 -> C -> A3
That is, the following sequence diagram
---
title: Module Resolution Sequence Diagram
---
sequenceDiagram
participant A
participant B
participant C
Note over A: Execute A1
A ->> B: A require B
Note over B: Execute B
B ->> A: Return control
Note over A: Execute A2
A ->> C: A require C
Note over C: Execute C
C ->> A: Return control
Note over A: Execute A3
Circular Dependencies
Module A depends on module B, and module B depends on module A. Modules A and B are each split top-down by require statements into 2 segments, denoted A1, A2, B1, B2.
The corresponding reference diagram is as follows:
---
title: CommonJS Circular Dependency Module Diagram
---
flowchart TD
A[Module A]
B[Module B]
A --> B
B --> A
That is, the following sequence diagram:
---
title: CommonJS Circular Dependency Resolution Diagram
---
sequenceDiagram
participant A
participant B
Note over A: Execute A1
A ->> B: A require B
Note over B: Execute B1
B ->> A: B require A
Note over A: Get the execution result of A1
A -->> B: Return the context after A1 finished
Note over B: Execute B2
B ->> A: Return control
Note over A: Execute A2
If code from the A2 part is referenced in B1, Runtime-level CommonJS usually cannot detect it, so an undefined value is obtained; if the code has fault tolerance, no exception is thrown.
ESM (ECMAScript Module)
Introduction
- Implemented inside the JS engine — a low-level implementation at the language level
- Modules analyze their dependencies in the preprocessing phase and execute in the execution phase; both phases use depth-first traversal, and the execution order is child -> parent
- Module files are loaded and executed ahead of time (all modules are loaded first, then executed one by one)
- The position of import/export statements does not affect the execution result of module code
- ESM exports individual variables separately. Looking only at the parent module, the parent module does bind the child module's exported variables during the preprocessing phase, but at that point the child's exported variables have not yet been assigned their final values, so it does not count as a real export
Use Cases
- Node 16+ supports ESM by default, but files need the
mjsextension andpackage.jsonneeds to declare"type": "module" - Non-IE browsers and newer mobile browsers support declaring ESM code via the
<script type="module">syntax, and declaring where dependency packages load from viaimportmap - Other scenarios need a build tool to bundle the code and simulate it with a runtime polyfill
Module Loading Errors
As an engine-level solution, loading errors point directly to the specific erroneous statement, with an effect similar to a throw statement
Execution Order
ESM has 5 states — unlinked, linking, linked, evaluating, evaluated — represented by the Status field of the cyclic module record (Module Environment Records). ESM processing consists of two steps: linking and evaluation. Evaluation can only happen after linking succeeds.
Linking is mainly implemented by the function InnerModuleLinking. InnerModuleLinking calls InitializeEnvironment, which initializes the module's Environment Records — mainly creating the module's Execution Contexts, creating bindings for imported module variables and initializing them to the corresponding variables of the child module, creating bindings for var variables initialized to undefined, creating bindings for function declarations initialized to the instantiated function body, and creating bindings for other variables without initializing them.
---
title: ESM Module Relationship Diagram
---
flowchart TD
A[Module A]
B[Module B]
C[Module C]
A --> B
A --> C
The execution order is: preprocess B -> preprocess C -> preprocess A -> execute B -> execute C -> execute A
That is, the following sequence diagram
---
title: ESM Module Resolution Flowchart
---
sequenceDiagram
participant A
participant B
participant C
A ->> B: A import B
Note over B: Preprocess B
B ->> A: Return control
A ->> C: A import C
Note over C: Preprocess C
C ->> A: Return control
Note over A: Preprocess A
A ->> B: A import B
Note over B: Execute B
B ->> A: Return control
A ->> C: A import C
Note over C: Execute C
C ->> A: Return control
Note over A: Execute A
Because of the preprocessing phase, the following code executes normally
// Preprocessing defines the variable a — effectively variable hoisting — so no error is thrown
import { a } from './child.js'
console.log(a)
Circular Dependencies
Module A depends on module B, and module B depends on module A. Modules A and B are each split top-down by require statements into 2 segments, denoted A1, A2, B1, B2.
The corresponding reference diagram is as follows:
---
title: ESM Circular Dependency Module Diagram
---
flowchart TD
A[Module A]
B[Module B]
A --> B
B --> A
That is, the following sequence diagram:
---
title: ESM Circular Dependency Resolution Diagram
---
sequenceDiagram
participant A
participant B
A ->> B: A import B
Note over B: Preprocess B
B ->> A: Return control
Note over A: Preprocess A
A ->> B: A import B
Note over B: Execute B
B ->> A: Return control
Note over A: Execute A
If code from A is referenced in B, since B executes first, the variables imported from A at execution time are only defined but not initialized — using them directly causes a JS error.
Module Resolution Strategies
- When discussing module resolution strategies, the type of file being looked up does not matter.
css,png,html, andwasmfiles can all be treated as modules. - Which tool looks up the module does not matter either.
tsc,nodejs,vite,esbuild,webpack, andrspackall need to handleimport/require, all need to resolve modules, and all need to choose a lookup strategy — and the vast majority use thenodestrategy - The
nodemodule resolution strategy itself keeps evolving. For example, earlynodedid not support theexportsfield ofpackage.json
The classic Strategy
// File: /root/src/folder/index.js
import 'pkg'
It goes through the following steps to find pkg:
/root/src/folder/pkg.js/root/src/pkg.js/root/pkg.js/pkg.js
Simply put, this module resolution strategy keeps recursively looking upward for a file with the same name — if it is not found in the current directory, it looks in the parent directory. However, this strategy is not widely used in the frontend world today.
The node Strategy
The most common strategy today is the node strategy. The other strategies — node16, nodenext, bundler, etc. — resolve in much the same way as node, with only some conditions added on top of file names and module specs; see Summary of module resolution strategies for details.
nodejs (module#all-together), webpack (enhanced-resolve), vite (resolve.exports), and rspack (nodejs_resolver) all use this strategy to resolve modules (rollup has no built-in resolution strategy)
// File: /root/src/folder/index.js
import 'pkg'
It goes through the following steps to find pkg:
- Look for a js file with the same name in the sibling
node_modules:/root/src/node_modules/pkg.js - Look in the sibling
node_modulesfor a folder named pkg containing apackage.json:/root/src/node_modules/pkg/package.json - Look in the sibling
node_modulesfor a folder named pkg containing anindex.js:/root/src/node_modules/pkg/index.js - If still not found, go up one directory level and repeat the previous lookup steps
/root/node_modules/pkg.js/root/node_modules/pkg/package.json/root/node_modules/pkg/index.js
Summary
The classic Strategy
- The directories searched recursively are parent folders, not
node_modules package.jsonis not parsed; the overall resolution strategy is fairly crude- Folder modules are not supported — that is, indexing into
pkg/index.jsviarequire('pkg')
node (Node.js CommonJS Resolution)
- Optional extensions: file extensions may be omitted and are auto-completed with
.js,.json,.node,.ts,.d.ts,.tsx - Folder modules: folder modules are supported — a folder's module can be indexed via an
indexfile orpackage.json node_modules-based lookup: searchesnode_modulesdirectories level by level, with a fixed directory name- Complex strategies configured via
package.json:package.jsonhas many fields that can declare a module's entry, affecting the resolution strategy. Declaring multiple fields at once achieves good resolution compatibility, but also introduces complexity such as priority issues.
node16 / nodenext (Node.js ESM Strict Mode)
- ESM support: supports
import/exportsyntax andtop await - Mandatory extensions: in ESM, the extension must be specified explicitly, otherwise an error is thrown
- Module type distinguished by module type:
CommonJSandESMare distinguished by.mjs/.cjsor thetypefield ofpackage.json(which declares what format.jsfiles use), and different module types use different resolution capabilities - Subpath exports: configured via the
exportsfield; see TheexportsField for details
bundler (Lenient Mode Aimed at Build Tools)
- Optional extensions: omitting extensions is supported, and they are not auto-completed — left to the build tool
- Module type: module types are not distinguished — left to the build tool
- Partial
exportssupport: ignores thenodeandbrowserfields, preferring theimportandmodulefields - No recursive
node_modules: left to the build tool, which flattensnode_modules
Module Main Entry
The classic strategy does not consider package.json, so this chapter mainly concerns strategies other than classic.
package.json is unavoidable in frontend development — many frontend tools support writing configuration in package.json. Under node_modules, a folder containing a package.json can be treated as a module, and we can use package.json to define the resolution rules for this module when it is imported by another module.
The main Field
Using the main field to define how a module is exported is currently the most common practice. The most minimal package.json declaration is as follows:
{
"name": "lodash",
"version": "1.1.1",
"main": "lodash.js"
}
When no other fields are present, the node strategy resolves a module without a subpath to the file pointed to by the main field.
Paths in main are all computed relative to the directory containing the package.json; the node resolution strategy, file name completion, folder modules, and so on no longer apply.
So libraries like lodash need to publish their code flattened next to package.json
The module Field
To solve the problem of libraries wanting to ship both cjs and esm versions of their JS code, we can use the module field to specify the entry of the esm version. For example redux, with a simplified package.json:
{
"name": "redux",
"version": "1.1.1",
"main": "lib/redux.js",
"module": "es/redux.js"
}
The exports Field
The Node.js official documentation on package.json is mostly about the exports field — it can be called the ultimate solution, adapting to all current scenarios.
Main Entry Export
Like the main and module fields, we can use the following syntax to configure how a module is exported when no subpath is written — also called the main entry:
All paths in exports must start with . — you can simply think of . as the module name
{
"name": "xxx",
"exports": {
".": "./index.js"
}
}
For the example above, the object can be simplified to a single path:
{
"name": "xxx",
"exports": "./index.js"
}
Subpath Exports
When matching rules in the exports field, subpaths must be declared in exports to be usable — otherwise an error is thrown
When declaring subpaths, a subpath must start with ./, and the subpath suffix is strictly distinguished: if ./bar.js is declared, the import must be pkg/bar.js — importing pkg/bar throws an error.
{
"name": "pkg",
"exports": {
// Must be imported as `pkg/bar.js`
"./bar.js": "./src/bar.js",
// Must be imported as `pkg/foo`
"./foo": "./src/bar.js"
}
}
Wildcard Exports
To adapt to multi-subdirectory scenarios, the exports field supports wildcard exports, which also perform strict string replacement to generate the imported file path. Example:
{
"name": "lodash",
"exports": {
// For any subpath, look up the `.js` file with the corresponding name in the ./lib directory
// Even importing `lodash/get.js` looks up `./lib/get.js.js`
// `*` supports arbitrarily deep paths — e.g. lodash/a/b also matches, looking up `./lib/a/b.js`
"./*": "./lib/*.js"
}
}
The specific lookup rules are as follows:
- Given a module id
lodash/add - Replace the
.in the left-hand pattern./*with the module namelodash, givinglodash/* - Pattern-match
lodash/*against the module idlodash/add; the value of * is add - Replace the
*in the target pattern./lib/*.jswith the value obtained in step 3, giving./lib/add.js— the path relative to thelodashpackage - Replace the
.in the relative path with the absolute path of thelodashpackage to get the absolute path of module idlodash/add:/xxx/node_modules/lodash/lib/add.js
Forbidden Exports
Setting a module's target pattern to null forbids a subpath from being imported by other modules:
{
"name": "xxx",
"exports": {
"./forbidden": null
}
}
Priority of Multiple matches
Given the following package.json definition:
{
"name": "xxx",
"exports": {
"./*": "./*",
"./a/*": "./a/*.js",
"./a/b": "./a/b.js",
"./*.js": "./*.js"
}
}
When importing the package with import 'pkg/a/b', the ./* rule matches — this is the short-circuit matching strategy
Short-circuit matching: matching proceeds from front to back; once a key pattern matches, matching ends regardless of whether the file for the target pattern can be found
However, the matching may differ between specific module loading libraries — there is currently no standard definition of matching strategy priority
Conditional Exports
When the exports field defines entries / subpaths, different conditions can be defined for matching, e.g.:
{
"exports": {
"./foo": {
// Keys like types, node-addons, node, and import represent conditions
"types": "./types.d.ts",
"node-addons": "./c-plus-native.node",
"node": "./can-be-esm-or-cjs.js",
"import": "./index-module.mjs",
"require": "./index-require.cjs",
"default": "./fallback-to-this-pattern.js"
}
}
}
- In the
nodejs+esmcase,"import": "./index-module.mjs"is used - In the
CommonJScase,"require": "./index-require.cjs"is used - When none of the conditions are met,
"default": "./fallback-to-this-pattern.js"is used
The priority of the conditions in a conditional export depends on the order in which they are declared — the earlier, the higher.
In other words, matching short-circuits from front to back. Therefore, importing the following module with commonjs in node throws an error:
{
"name": "xxx",
"exports": {
".": {
"default": null,
"require": "./dist/hello.js"
}
}
}
This means that when using conditional exports, we must write them in priority order — put the more specific conditions first.
Other Important package.json Fields
files
Declares which files or directories the current package exports; only the declared content is published to npm
types / typings
Declares the directory of type definitions (.d.ts) corresponding to the modules exported by the current dependency package, so TS projects can obtain the package's TS types
typesVersions
Solves TS type syntax compatibility issues across versions — different .d.ts type definition files are used for specific TS version ranges; besides versioning, it also supports loading types per submodule.
{
"name": "pkg",
"version": "1.1.1",
"types": "dist/index.d.ts",
"typesVersions": {
"<=4.8": {
// Declares the type declaration file for the `pkg/subpath` module; wildcards are supported
// If using `node16` / `nodenext`, `ts4.8/*.d.ts` is needed
"subpath": ["ts4.8/*"]
},
// Fallback strategy; may be omitted. By default types are imported from `.`,
// e.g. `pkg/foo` imports types from the `./pkg/foo.d.ts` file
"*": {
// Declares the type declaration file for the `pkg` module; wildcards are supported
// If using `node16` / `nodenext`, `dist/*.d.ts` is needed
"*": ["dist/*"]
}
}
}
When both types / typings and typeVersions are configured, types / typings takes higher priority — typeVersions is only used when types / typings cannot be resolved
unpkg / jsdelivr / cdn / browser
Provided for cdn vendors — usually an entry to a full JS file containing everything; see the discussion thread on cdn entries in package.json
Taking jsdelivr as an example, the priority is:
jsdelivrbrowsermain
How Vite Resolves npm Packages
vite uses esbuild to convert ts files into js files. During conversion, esbuild directly discards ts types without doing type checking, so it does not care how types are resolved and does not need to handle fields like typings.
When both main and module entries exist, build tools — especially ESM-based ones like rollup and vite — prefer the module field.
Vite bundling comes in several flavors:
- pre bundling: pre-bundling with
esbuild - esm dev server: the built-in
vite:resolveplugin handles moduleidresolution - prod build: production build — essentially
rollup+ thevite:resolveplugin + the@rollup/plugin-commonjsplugin
By default, vite pre-bundles your third-party dependencies regardless of whether they support esm. The main consideration is that dependencies with an especially large number of modules, like lodash-es, would cause too many http requests if not pre-bundled.
If you don't want pre-bundling, you have to manually add the dependency to the pre-bundling exclude list. Once a dependency is in the exclude list, vite will not perform commonjs -> esm conversion on it — even if its main field points to a commonjs module, vite respects the user's configuration and treats it as an esm module.
Both vite and rollup extend their capabilities through plugin systems: they first determine a module's final file path through a resolve plugin, then use the @rollup/plugin-commonjs plugin to convert it to esm when conversion is needed. If both an esm entry and a generic entry exist, the esm entry is preferred.
The main entry can also serve esm — it is a generic entry. A similar one is the default field in exports.
{
"type": "module",
"exports": {
".": {
"import": {
// Use the source code under src in development, so our source changes hot-reload too
"development": "./src",
// In production — i.e. when the app runs vite build — use the bundled, compiled dist
"default": "./dist/es/index.mjs"
}
}
},
"publishConfig": {
// When publishing, we don't need to keep the development condition
// If kept, users of this library would also resolve to src
// Not all fields can be overridden in publishConfig — for example npm does not support overriding typesVersion, but pnpm does
"exports": {
".": {
"import": "./dist/es/index.mjs"
}
}
}
}