Skip to content

Commit 6710016

Browse files
committed
fix: detect json exports as esm
1 parent 44bc4bc commit 6710016

2 files changed

Lines changed: 46 additions & 4 deletions

File tree

shared/utils/package-analysis.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,11 @@ export function detectModuleFormat(pkg: ExtendedPackageJson): ModuleFormat {
7676
return 'cjs'
7777
}
7878

79-
// exports field exists but doesn't use import/require conditions
79+
// If exports only contain JSON files, treat as ESM since JSON can be natively imported in ESM contexts.
80+
if (exportInfo.hasJsonExport) {
81+
return 'esm'
82+
}
83+
8084
// Fall through to other detection methods
8185
}
8286

@@ -102,6 +106,7 @@ interface ExportsAnalysis {
102106
hasRequire: boolean
103107
hasModule: boolean
104108
hasTypes: boolean
109+
hasJsonExport: boolean
105110
}
106111

107112
/**
@@ -113,6 +118,7 @@ function analyzeExports(exports: PackageExports, depth = 0): ExportsAnalysis {
113118
hasRequire: false,
114119
hasModule: false,
115120
hasTypes: false,
121+
hasJsonExport: false,
116122
}
117123

118124
// Prevent infinite recursion
@@ -124,13 +130,21 @@ function analyzeExports(exports: PackageExports, depth = 0): ExportsAnalysis {
124130

125131
if (typeof exports === 'string') {
126132
// Check file extension for format hints
127-
if (exports.endsWith('.mjs') || exports.endsWith('.mts')) {
133+
const isMjs = exports.endsWith('.mjs') || exports.endsWith('.mts')
134+
const isCjs = exports.endsWith('.cjs') || exports.endsWith('.cts')
135+
const isTypes =
136+
exports.endsWith('.d.ts') || exports.endsWith('.d.mts') || exports.endsWith('.d.cts')
137+
const isJson = exports.endsWith('.json')
138+
139+
if (isMjs) {
128140
result.hasImport = true
129-
} else if (exports.endsWith('.cjs') || exports.endsWith('.cts')) {
141+
} else if (isCjs) {
130142
result.hasRequire = true
131143
}
132-
if (exports.endsWith('.d.ts') || exports.endsWith('.d.mts') || exports.endsWith('.d.cts')) {
144+
if (isTypes) {
133145
result.hasTypes = true
146+
} else if (isJson) {
147+
result.hasJsonExport = true
134148
}
135149
return result
136150
}
@@ -170,6 +184,7 @@ function mergeExportsAnalysis(target: ExportsAnalysis, source: ExportsAnalysis):
170184
target.hasRequire = target.hasRequire || source.hasRequire
171185
target.hasModule = target.hasModule || source.hasModule
172186
target.hasTypes = target.hasTypes || source.hasTypes
187+
target.hasJsonExport = target.hasJsonExport || source.hasJsonExport
173188
}
174189

175190
/** Info about a related package (@types or create-*) */

test/unit/shared/utils/package-analysis.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,33 @@ describe('detectModuleFormat', () => {
8787
).toBe('dual')
8888
})
8989

90+
it('detects ESM when exports contain only JSON files', () => {
91+
expect(
92+
detectModuleFormat({
93+
exports: {
94+
'.': './data.json',
95+
'./config': './config.json',
96+
},
97+
}),
98+
).toBe('esm')
99+
})
100+
101+
// example from package "@tc39/ecma262-biblio"
102+
it('detects ESM for JSON-only exports with package.json re-export', () => {
103+
expect(
104+
detectModuleFormat({
105+
exports: {
106+
'.': './biblio.json',
107+
'./package.json': './package.json',
108+
},
109+
}),
110+
).toBe('esm')
111+
})
112+
113+
it('detects ESM when exports contain only a single JSON file', () => {
114+
expect(detectModuleFormat({ exports: './data.json' })).toBe('esm')
115+
})
116+
90117
it('returns cjs for empty package (npm default)', () => {
91118
// npm treats packages without type field as CommonJS
92119
expect(detectModuleFormat({})).toBe('cjs')

0 commit comments

Comments
 (0)