Skip to content

Commit d870f4e

Browse files
authored
feat: add option for interop default in cjs (#947)
1 parent 692c112 commit d870f4e

6 files changed

Lines changed: 67 additions & 7 deletions

File tree

docs/README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,16 +145,12 @@ Provide the following configuration in your `.vscode/settings.json` (or global)
145145
"json.schemas": [
146146
{
147147
"url": "https://cdn.jsdelivr.net/npm/tsup/schema.json",
148-
"fileMatch": [
149-
"package.json",
150-
"tsup.config.json"
151-
]
148+
"fileMatch": ["package.json", "tsup.config.json"]
152149
}
153150
]
154151
}
155152
```
156153

157-
158154
### Multiple entrypoints
159155

160156
Beside using positional arguments `tsup [...files]` to specify multiple entrypoints, you can also use the cli flag `--entry`:
@@ -164,7 +160,7 @@ Beside using positional arguments `tsup [...files]` to specify multiple entrypoi
164160
tsup --entry src/a.ts --entry src/b.ts
165161
```
166162

167-
The associated output file names can be defined as follows:
163+
The associated output file names can be defined as follows:
168164

169165
```bash
170166
# Outputs `dist/foo.js` and `dist/bar.js`.
@@ -350,6 +346,14 @@ tsup src/index.ts --env.NODE_ENV production
350346
351347
When an entry file like `src/cli.ts` contains hashbang like `#!/bin/env node` tsup will automatically make the output file executable, so you don't have to run `chmod +x dist/cli.js`.
352348
349+
### Interop with CommonJS
350+
351+
By default, esbuild will transform `export default x` to `module.exports.default = x` in CommonJS, but you can change this behavior by using the `--cjsInterop` flag: If there are only default exports and no named exports, it will be transformed to `module.exports = x` instead.
352+
353+
```bash
354+
tsup src/index.ts --cjsInterop
355+
```
356+
353357
### Watch mode
354358
355359
```bash

src/cli-main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export async function main(options: Options = {}) {
9595
'--killSignal <signal>',
9696
'Signal to kill child process, "SIGTERM" or "SIGKILL"'
9797
)
98+
.option('--cjsInterop', 'Enable cjs interop')
9899
.action(async (files: string[], flags) => {
99100
const { build } = await import('.')
100101
Object.assign(options, {

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { sizeReporter } from './plugins/size-reporter'
2121
import { treeShakingPlugin } from './plugins/tree-shaking'
2222
import { copyPublicDir, isInPublicDir } from './lib/public-dir'
2323
import { terserPlugin } from './plugins/terser'
24+
import { cjsInterop } from './plugins/cjs-interop'
2425

2526
export type { Format, Options, NormalizedOptions }
2627

@@ -254,6 +255,7 @@ export async function build(_options: Options) {
254255
silent: options.silent,
255256
}),
256257
cjsSplitting(),
258+
cjsInterop(),
257259
es5(),
258260
sizeReporter(),
259261
terserPlugin({

src/options.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,11 @@ export type Options = {
224224
*/
225225
publicDir?: string | boolean
226226
killSignal?: KILL_SIGNAL
227+
/**
228+
* Interop default within `module.exports` in cjs
229+
* @default false
230+
*/
231+
cjsInterop?: boolean
227232
}
228233

229234
export type NormalizedOptions = Omit<

src/plugins/cjs-interop.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Plugin } from '../plugin'
2+
3+
export const cjsInterop = (): Plugin => {
4+
return {
5+
name: 'cjs-interop',
6+
7+
async renderChunk(code, info) {
8+
if (
9+
!this.options.cjsInterop ||
10+
this.format !== 'cjs' ||
11+
info.type !== 'chunk' ||
12+
!/\.(js|cjs)$/.test(info.path) ||
13+
!info.entryPoint ||
14+
info.exports?.length !== 1 ||
15+
info.exports[0] !== 'default'
16+
) {
17+
return
18+
}
19+
20+
return {
21+
code: code + '\nmodule.exports = exports.default',
22+
map: info.map,
23+
}
24+
},
25+
}
26+
}

src/rollup.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,25 @@ const getRollupConfig = async (
132132
},
133133
}
134134

135+
const fixCjsExport: Plugin = {
136+
name: 'tsup:fix-cjs-export',
137+
renderChunk(code, info) {
138+
if (
139+
info.type !== 'chunk' ||
140+
!/\.(ts|cts)$/.test(info.fileName) ||
141+
!info.isEntry ||
142+
info.exports?.length !== 1 ||
143+
info.exports[0] !== 'default'
144+
)
145+
return
146+
147+
return code.replace(
148+
/(?<=(?<=[;}]|^)\s*export\s*){\s*([\w$]+)\s*as\s+default\s*}/,
149+
`= $1`
150+
)
151+
},
152+
}
153+
135154
return {
136155
inputConfig: {
137156
input: dtsOptions.entry,
@@ -179,7 +198,7 @@ const getRollupConfig = async (
179198
...(options.external || []),
180199
],
181200
},
182-
outputConfig: options.format.map((format) => {
201+
outputConfig: options.format.map((format): OutputOptions => {
183202
const outputExtension =
184203
options.outExtension?.({ format, options, pkgType: pkg.type }).dts ||
185204
defaultOutExtension({ format, pkgType: pkg.type }).dts
@@ -190,6 +209,9 @@ const getRollupConfig = async (
190209
banner: dtsOptions.banner,
191210
footer: dtsOptions.footer,
192211
entryFileNames: `[name]${outputExtension}`,
212+
plugins: [
213+
format === 'cjs' && options.cjsInterop && fixCjsExport,
214+
].filter(Boolean),
193215
}
194216
}),
195217
}

0 commit comments

Comments
 (0)