Skip to content

Commit aef0f97

Browse files
committed
feat: add shims for cjs & esm
1 parent ab2c5ed commit aef0f97

File tree

8 files changed

+143
-13
lines changed

8 files changed

+143
-13
lines changed

assets/esm-shims.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Shim globals in esm bundle
2+
import path from 'node:path'
3+
import { fileURLToPath } from 'node:url'
4+
5+
const getFilename = () => fileURLToPath(import.meta.url)
6+
const getDirname = () => path.dirname(getFilename())
7+
8+
export const __dirname = /* @__PURE__ */ getDirname()
9+
export const __filename = /* @__PURE__ */ getFilename()

src/cli.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export async function runCLI(): Promise<void> {
2525
.option('-d, --out-dir <dir>', 'Output directory', { default: 'dist' })
2626
.option('--treeshake', 'Tree-shake bundle', { default: true })
2727
.option('--sourcemap', 'Generate source map', { default: false })
28+
.option('--shims', 'Enable cjs and esm shims ', { default: false })
2829
.option('--platform <platform>', 'Target platform', {
2930
default: 'node',
3031
})

src/features/output.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
import { getPackageType } from '../utils/package'
2-
import type { ModuleFormat } from 'rolldown'
2+
import type { NormalizedFormat } from '../options'
33

44
export function resolveOutputExtension(
55
pkg: any,
6-
format: ModuleFormat,
6+
format: NormalizedFormat,
77
): 'mjs' | 'cjs' | 'js' {
88
const moduleType = getPackageType(pkg)
99
switch (format) {
1010
case 'es':
11-
case 'esm':
12-
case 'module': {
1311
return moduleType === 'module' ? 'js' : 'mjs'
14-
}
1512
case 'cjs':
16-
case 'commonjs': {
1713
return moduleType === 'module' ? 'cjs' : 'js'
18-
}
1914
default:
2015
return 'js'
2116
}

src/index.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import path from 'node:path'
12
import process from 'node:process'
3+
import { fileURLToPath } from 'node:url'
24
import {
35
build as rolldownBuild,
46
type InputOptions,
@@ -20,6 +22,7 @@ import {
2022
} from './options'
2123
import { debug, logger } from './utils/logger'
2224
import { readPackageJson } from './utils/package'
25+
import { getShimsDefine, getShimsInject } from './utils/shims'
2326

2427
/**
2528
* Build with tsdown.
@@ -53,6 +56,9 @@ export async function build(
5356
}
5457
}
5558

59+
const dirname = path.dirname(fileURLToPath(import.meta.url))
60+
export const assetsDir: string = path.resolve(dirname, '../assets')
61+
5662
/**
5763
* Build a single configuration, without watch and shortcuts features.
5864
*
@@ -78,6 +84,7 @@ export async function buildSingle(
7884
unused,
7985
target,
8086
define,
87+
shims,
8188
onSuccess,
8289
} = resolved
8390

@@ -112,9 +119,16 @@ export async function buildSingle(
112119
resolve: { alias },
113120
treeshake,
114121
platform,
115-
define,
122+
define: {
123+
...getShimsDefine(format),
124+
...define,
125+
},
116126
plugins,
117127
...resolved.inputOptions,
128+
inject: {
129+
...(shims && getShimsInject(format, platform)),
130+
...resolved.inputOptions?.inject,
131+
},
118132
}
119133

120134
const extension = resolveOutputExtension(pkg, format)

src/options.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ import type {
1414
Overwrite,
1515
} from './utils/types'
1616
import type { Stats } from 'node:fs'
17-
import type { InputOptions, ModuleFormat, OutputOptions } from 'rolldown'
17+
import type {
18+
InputOptions,
19+
InternalModuleFormat,
20+
ModuleFormat,
21+
OutputOptions,
22+
} from 'rolldown'
1823
import type { Options as IsolatedDeclOptions } from 'unplugin-isolated-decl'
1924
import type { Options as UnusedOptions } from 'unplugin-unused'
2025

@@ -43,6 +48,7 @@ export interface Options {
4348
define?: Record<string, string>
4449
/** @default 'node' */
4550
platform?: 'node' | 'neutral' | 'browser'
51+
shims?: boolean
4652
/**
4753
* Enable dts generation with `isolatedDeclarations` (experimental)
4854
*/
@@ -73,6 +79,10 @@ export interface Options {
7379
export type Config = Arrayable<Omit<Options, 'config'>>
7480
export type ResolvedConfig = Extract<Config, any[]>
7581

82+
export type NormalizedFormat =
83+
| Exclude<InternalModuleFormat, 'app'>
84+
| 'experimental-app'
85+
7686
export type ResolvedOptions = Omit<
7787
Overwrite<
7888
MarkPartial<
@@ -87,7 +97,7 @@ export type ResolvedOptions = Omit<
8797
| 'external'
8898
| 'onSuccess'
8999
>,
90-
{ format: ModuleFormat[]; clean: string[] | false }
100+
{ format: NormalizedFormat[]; clean: string[] | false }
91101
>,
92102
'config'
93103
>
@@ -118,18 +128,18 @@ export async function resolveOptions(
118128
dts = false,
119129
unused = false,
120130
watch = false,
131+
shims = false,
121132
skipNodeModulesBundle = false,
122133
} = subOptions
123134

124135
entry = await resolveEntry(entry)
125-
format = toArray(format, 'es')
126136
if (clean === true) clean = []
127137

128138
return {
129139
...subOptions,
130140
entry,
131141
plugins,
132-
format,
142+
format: normalizeFormat(format),
133143
outDir: path.resolve(outDir),
134144
clean,
135145
silent,
@@ -139,6 +149,7 @@ export async function resolveOptions(
139149
dts,
140150
unused,
141151
watch,
152+
shims,
142153
skipNodeModulesBundle,
143154
}
144155
}),
@@ -147,6 +158,24 @@ export async function resolveOptions(
147158
]
148159
}
149160

161+
export function normalizeFormat(
162+
format: ModuleFormat | ModuleFormat[],
163+
): NormalizedFormat[] {
164+
return toArray<ModuleFormat>(format, 'es').map((format): NormalizedFormat => {
165+
switch (format) {
166+
case 'es':
167+
case 'esm':
168+
case 'module':
169+
return 'es'
170+
case 'cjs':
171+
case 'commonjs':
172+
return 'cjs'
173+
default:
174+
return format
175+
}
176+
})
177+
}
178+
150179
async function loadConfigFile(
151180
options: Options,
152181
): Promise<[config: ResolvedConfig, file?: string]> {

src/utils/shims.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import path from 'node:path'
2+
import { assetsDir } from '../index'
3+
import type { NormalizedFormat, ResolvedOptions } from '../options'
4+
5+
export function getShimsInject(
6+
format: NormalizedFormat,
7+
platform: ResolvedOptions['platform'],
8+
): Record<string, [string, string]> | undefined {
9+
if (format === 'es' && platform === 'node') {
10+
const shimFile = path.resolve(assetsDir, 'esm-shims.js')
11+
return {
12+
__dirname: [shimFile, '__dirname'],
13+
__filename: [shimFile, '__filename'],
14+
}
15+
}
16+
}
17+
18+
export function getShimsDefine(
19+
format: NormalizedFormat,
20+
): Record<string, string> | undefined {
21+
if (format === 'cjs') {
22+
return {
23+
'import.meta.filename': '__filename',
24+
'import.meta.dirname': '__dirname',
25+
}
26+
}
27+
}

tests/__snapshots__/index.test.ts.snap

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@ const foo = 1;
1717
exports.foo = foo"
1818
`;
1919

20+
exports[`cjs shims 1`] = `
21+
""use strict";
22+
23+
//#region index.ts
24+
var cjs_shims_default = [
25+
require("url").pathToFileURL(__filename).href,
26+
__filename,
27+
__dirname
28+
];
29+
30+
//#endregion
31+
module.exports = cjs_shims_default;"
32+
`;
33+
2034
exports[`esm import 1`] = `
2135
"//#region foo.ts
2236
const foo = 1;
@@ -25,6 +39,24 @@ const foo = 1;
2539
export { foo };"
2640
`;
2741

42+
exports[`esm shims 1`] = `
43+
"import path from "node:path";
44+
import { fileURLToPath } from "node:url";
45+
46+
//#region ../../../assets/esm-shims.js
47+
const getFilename = () => fileURLToPath(import.meta.url);
48+
const getDirname = () => path.dirname(getFilename());
49+
const __dirname = /* @__PURE__ */ getDirname();
50+
const __filename = /* @__PURE__ */ getFilename();
51+
52+
//#endregion
53+
//#region index.ts
54+
var esm_shims_default = [__dirname, __filename];
55+
56+
//#endregion
57+
export { esm_shims_default as default };"
58+
`;
59+
2860
exports[`syntax lowering 1`] = `
2961
"//#region index.ts
3062
var _a, _a$b;

tests/index.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ test('basic', async () => {
2929
})
3030

3131
test('cjs import', async () => {
32-
const { getContent } = await testBuild(files, { args: ['--format', 'cjs'] })
32+
const { getContent } = await testBuild(files, {
33+
args: ['--format', 'cjs'],
34+
})
3335
const out = await getContent('index.js')
3436
expect(out).matchSnapshot()
3537
})
@@ -43,3 +45,24 @@ test('syntax lowering', async () => {
4345
const out = await outputContent()
4446
expect(out).matchSnapshot()
4547
})
48+
49+
test('esm shims', async () => {
50+
const { outputContent } = await testBuild(
51+
{ 'index.ts': 'export default [__dirname, __filename]' },
52+
{ args: ['--shims'] },
53+
)
54+
const out = await outputContent()
55+
expect(out).matchSnapshot()
56+
})
57+
58+
test('cjs shims', async () => {
59+
const { getContent } = await testBuild(
60+
{
61+
'index.ts':
62+
'export default [import.meta.url, import.meta.filename, import.meta.dirname]',
63+
},
64+
{ args: ['--shims', '--format', 'cjs'] },
65+
)
66+
const out = await getContent('index.js')
67+
expect(out).matchSnapshot()
68+
})

0 commit comments

Comments
 (0)