Skip to content

Commit 03f8fc4

Browse files
committed
feat: add cjsDefault option
1 parent 6d5f0e9 commit 03f8fc4

File tree

6 files changed

+86
-4
lines changed

6 files changed

+86
-4
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ Resolve external types used in `.d.ts` files from `node_modules`.
8686
- If `true`, all external types are resolved.
8787
- If an array, only types matching the provided strings or regular expressions are resolved.
8888

89+
#### `cjsDefault`
90+
91+
Determines how the default export is emitted.
92+
93+
If set to `true`, and you are only exporting a single item using `export default ...`,
94+
the output will use `export = ...` instead of the standard ES module syntax.
95+
This is useful for compatibility with CommonJS.
96+
8997
### `tsc` Options
9098

9199
> [!NOTE]

src/fake-js.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ type NamespaceMap = Map<
4040
export function createFakeJsPlugin({
4141
dtsInput,
4242
sourcemap,
43-
}: Pick<OptionsResolved, 'dtsInput' | 'sourcemap'>): Plugin {
43+
cjsDefault,
44+
}: Pick<OptionsResolved, 'dtsInput' | 'sourcemap' | 'cjsDefault'>): Plugin {
4445
let symbolIdx = 0
4546
const identifierMap: Record<string, number> = Object.create(null)
4647
const symbolMap = new Map<number /* symbol id */, SymbolInfo>()
@@ -264,7 +265,12 @@ export function createFakeJsPlugin({
264265
program.body = program.body
265266
.map((node) => {
266267
if (isHelperImport(node)) return null
267-
if (patchImportExport(node, typeOnlyIds)) return node
268+
269+
const newNode = patchImportExport(node, typeOnlyIds, cjsDefault)
270+
if (newNode) {
271+
return newNode
272+
}
273+
268274
if (node.type !== 'VariableDeclaration') return node
269275

270276
const [decl] = node.declarations
@@ -523,7 +529,11 @@ function isHelperImport(node: t.Node) {
523529
}
524530

525531
// patch `.d.ts` suffix in import source to `.js`
526-
function patchImportExport(node: t.Node, typeOnlyIds: string[]) {
532+
function patchImportExport(
533+
node: t.Node,
534+
typeOnlyIds: string[],
535+
cjsDefault: boolean,
536+
): t.Statement | undefined {
527537
if (
528538
isTypeOf(node, [
529539
'ImportDeclaration',
@@ -546,7 +556,22 @@ function patchImportExport(node: t.Node, typeOnlyIds: string[]) {
546556

547557
if (node.source?.value && RE_DTS.test(node.source.value)) {
548558
node.source.value = filename_dts_to(node.source.value, 'js')
549-
return true
559+
return node
560+
}
561+
562+
if (
563+
cjsDefault &&
564+
node.type === 'ExportNamedDeclaration' &&
565+
!node.source &&
566+
node.specifiers.length === 1 &&
567+
node.specifiers[0].type === 'ExportSpecifier' &&
568+
resolveString(node.specifiers[0].exported) === 'default'
569+
) {
570+
const defaultExport = node.specifiers[0] as t.ExportSpecifier
571+
return {
572+
type: 'TSExportAssignment',
573+
expression: defaultExport.local,
574+
}
550575
}
551576
}
552577
}

src/options.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ export interface GeneralOptions {
6262
* Resolve external types used in `.d.ts` files from `node_modules`.
6363
*/
6464
resolve?: boolean | (string | RegExp)[]
65+
66+
/**
67+
* Determines how the default export is emitted.
68+
*
69+
* If set to `true`, and you are only exporting a single item using `export default ...`,
70+
* the output will use `export = ...` instead of the standard ES module syntax.
71+
* This is useful for compatibility with CommonJS.
72+
*/
73+
cjsDefault?: boolean
6574
}
6675

6776
//#region tsc Options
@@ -194,6 +203,7 @@ export function resolveOptions({
194203
compilerOptions = {},
195204
sourcemap,
196205
resolve = false,
206+
cjsDefault = false,
197207

198208
// tsc
199209
build = false,
@@ -280,6 +290,7 @@ export function resolveOptions({
280290
tsconfigRaw,
281291
sourcemap,
282292
resolve,
293+
cjsDefault,
283294

284295
// tsc
285296
build,

tests/__snapshots__/index.test.ts.snap

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,23 @@ var Cls = class {
3838
export { Cls, Enum, fn, foo };"
3939
`;
4040
41+
exports[`cjs exports 1`] = `
42+
"// cjs-exports.js
43+
44+
//#region tests/fixtures/cjs-exports.ts
45+
var cjs_exports_default = 42;
46+
47+
//#endregion
48+
module.exports = cjs_exports_default;"
49+
`;
50+
51+
exports[`cjs exports 2`] = `
52+
"// cjs-exports.d.ts
53+
//#region tests/fixtures/cjs-exports.d.ts
54+
declare const _default: number;
55+
export = _default;"
56+
`;
57+
4158
exports[`dts input 1`] = `
4259
"// index.d.ts
4360
//#region tests/fixtures/dts-input.d.ts

tests/fixtures/cjs-exports.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 42

tests/index.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,23 @@ test('type-only export', async () => {
144144
)
145145
expect(snapshot).toMatchSnapshot()
146146
})
147+
148+
test.only('cjs exports', async () => {
149+
{
150+
const { snapshot } = await rolldownBuild(
151+
[path.resolve(dirname, 'fixtures/cjs-exports.ts')],
152+
[],
153+
{},
154+
{ format: 'cjs', exports: 'auto' },
155+
)
156+
expect(snapshot).toMatchSnapshot()
157+
}
158+
159+
{
160+
const { snapshot } = await rolldownBuild(
161+
[path.resolve(dirname, 'fixtures/cjs-exports.ts')],
162+
[dts({ emitDtsOnly: true, cjsDefault: true })],
163+
)
164+
expect(snapshot).toMatchSnapshot()
165+
}
166+
})

0 commit comments

Comments
 (0)