Skip to content

Commit fd43167

Browse files
authored
feat: add esm build option for typescript (#603)
This wraps up the ESM support to add support for TypeScript as well. For TypeScript to work correctly, it's necessary to have 2 builds (for ESM and CommonJS). With this, we generate one build and then copy the folder, then add a `package.json` with `type` property for the module system.
1 parent f0a7a2f commit fd43167

File tree

7 files changed

+258
-91
lines changed

7 files changed

+258
-91
lines changed

docs/pages/build.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ yarn add --dev react-native-builder-bob
4444
"targets": [
4545
["commonjs", { "esm": true }],
4646
["module", { "esm": true }],
47-
"typescript",
47+
["typescript", { "esm": true }]
4848
]
4949
}
5050
```
@@ -76,12 +76,17 @@ yarn add --dev react-native-builder-bob
7676
"source": "./src/index.tsx",
7777
"main": "./lib/commonjs/index.js",
7878
"module": "./lib/module/index.js",
79-
"types": "./lib/typescript/src/index.d.ts",
79+
"types": "./lib/typescript/commonjs/src/index.d.ts",
8080
"exports": {
8181
".": {
82-
"types": "./typescript/src/index.d.ts",
83-
"import": "./module/index.js",
84-
"require": "./commonjs/index.js"
82+
"import": {
83+
"types": "./lib/typescript/module/src/index.d.ts",
84+
"default": "./lib/module/index.js"
85+
},
86+
"require": {
87+
"types": "./lib/typescript/commonjs/src/index.d.ts",
88+
"default": "./lib/commonjs/index.js"
89+
}
8590
}
8691
},
8792
"files": [
@@ -224,6 +229,12 @@ Example:
224229

225230
The output file should be referenced in the `types` field or `exports['.'].types` field of `package.json`.
226231

232+
##### `esm`
233+
234+
Setting this option to `true` will output 2 sets of type definitions: one for the CommonJS build and one for the ES module build.
235+
236+
See the [ESM support](./esm.md) guide for more details.
237+
227238
## Commands
228239

229240
The `bob` CLI exposes the following commands:

docs/pages/esm.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ You can verify whether ESM support is enabled by checking the configuration for
1111
"targets": [
1212
["commonjs", { "esm": true }],
1313
["module", { "esm": true }],
14-
"typescript",
14+
["typescript", { "esm": true }]
1515
]
1616
}
1717
```
1818

19-
The `"esm": true` option enables ESM-compatible output by adding the `.js` extension to the import statements in the generated files.
19+
The `"esm": true` option enables ESM-compatible output by adding the `.js` extension to the import statements in the generated files. For TypeScript, it also generates 2 sets of type definitions: one for the CommonJS build and one for the ES module build.
2020

2121
It's recommended to specify `"moduleResolution": "Bundler"` in your `tsconfig.json` file as well:
2222

@@ -43,10 +43,16 @@ There are still a few things to keep in mind if you want your library to be ESM-
4343
```json
4444
"exports": {
4545
".": {
46-
"types": "./lib/typescript/src/index.d.ts",
47-
"react-native": "./lib/modules/index.native.js",
48-
"import": "./lib/modules/index.js",
49-
"require": "./lib/commonjs/index.js"
46+
"import": {
47+
"types": "./lib/typescript/module/src/index.d.ts",
48+
"react-native": "./lib/modules/index.native.js",
49+
"default": "./lib/module/index.js"
50+
},
51+
"require": {
52+
"types": "./lib/typescript/commonjs/src/index.d.ts",
53+
"react-native": "./lib/commonjs/index.native.js",
54+
"default": "./lib/commonjs/index.js"
55+
}
5056
}
5157
}
5258
```

packages/create-react-native-library/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import generateExampleApp, {
1414
import { spawn } from './utils/spawn';
1515
import { version } from '../package.json';
1616

17-
const FALLBACK_BOB_VERSION = '0.28.0';
17+
const FALLBACK_BOB_VERSION = '0.29.0';
1818

1919
const BINARIES = [
2020
/(gradlew|\.(jar|keystore|png|jpg|gif))$/,

packages/create-react-native-library/templates/common/$package.json

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
"source": "./src/index.tsx",
66
"main": "./lib/commonjs/index.js",
77
"module": "./lib/module/index.js",
8-
"types": "./lib/typescript/src/index.d.ts",
98
"exports": {
109
".": {
11-
"types": "./lib/typescript/src/index.d.ts",
12-
"import": "./lib/module/index.js",
13-
"require": "./lib/commonjs/index.js"
10+
"import": {
11+
"types": "./lib/typescript/module/src/index.d.ts",
12+
"default": "./lib/module/index.js"
13+
},
14+
"require": {
15+
"types": "./lib/typescript/commonjs/src/index.d.ts",
16+
"default": "./lib/commonjs/index.js"
17+
}
1418
}
1519
},
1620
"files": [
@@ -178,7 +182,8 @@
178182
[
179183
"typescript",
180184
{
181-
"project": "tsconfig.build.json"
185+
"project": "tsconfig.build.json",
186+
"esm": true
182187
}
183188
]
184189
]

packages/react-native-builder-bob/src/index.ts

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,29 @@ yargs
175175
entries.main = entries.source;
176176
}
177177

178+
const types: {
179+
[key in 'require' | 'import']?: string;
180+
} = {};
181+
178182
if (targets.includes('typescript')) {
179-
entries.types = `./${path.join(
183+
types.require = `./${path.join(
184+
output,
185+
'typescript',
186+
'commonjs',
187+
source,
188+
'index.d.ts'
189+
)}`;
190+
191+
types.import = `./${path.join(
180192
output,
181193
'typescript',
194+
'module',
182195
source,
183196
'index.d.ts'
184197
)}`;
185198

199+
entries.types = types.require;
200+
186201
if (!(await fs.pathExists(path.join(root, 'tsconfig.json')))) {
187202
const { tsconfig } = await prompts({
188203
type: 'confirm',
@@ -258,9 +273,14 @@ yargs
258273

259274
const exports = {
260275
'.': {
261-
...(entries.types ? { types: entries.types } : null),
262-
...(entries.module ? { import: entries.module } : null),
263-
...(entries.main ? { require: entries.main } : null),
276+
import: {
277+
...(types.import ? { types: types.import } : null),
278+
...(entries.module ? { default: entries.module } : null),
279+
},
280+
require: {
281+
...(types.require ? { types: types.require } : null),
282+
...(entries.main ? { default: entries.main } : null),
283+
},
264284
},
265285
};
266286

@@ -318,25 +338,23 @@ yargs
318338
pkg.scripts.prepare = prepare;
319339
}
320340

321-
if (
322-
pkg.files &&
323-
JSON.stringify(pkg.files.slice().sort()) !==
324-
JSON.stringify(files.slice().sort())
325-
) {
326-
const { update } = await prompts({
327-
type: 'confirm',
328-
name: 'update',
329-
message: `Your package.json already has a 'files' field.\n Do you want to update it?`,
330-
initial: true,
331-
});
341+
if (pkg.files) {
342+
const pkgFiles = pkg.files;
332343

333-
if (update) {
334-
pkg.files = [
335-
...files,
336-
...pkg.files.filter(
337-
(file: string) => !files.includes(file.replace(/\/$/g, ''))
338-
),
339-
];
344+
if (files?.some((file) => !pkgFiles.includes(file))) {
345+
const { update } = await prompts({
346+
type: 'confirm',
347+
name: 'update',
348+
message: `Your package.json already has a 'files' field.\n Do you want to update it?`,
349+
initial: true,
350+
});
351+
352+
if (update) {
353+
pkg.files = [
354+
...files,
355+
...pkg.files.filter((file: string) => !files.includes(file)),
356+
];
357+
}
340358
}
341359
} else {
342360
pkg.files = files;
@@ -350,7 +368,7 @@ yargs
350368
return [t, { copyFlow: true }];
351369
}
352370

353-
if (t === 'commonjs' || t === 'module') {
371+
if (t === 'commonjs' || t === 'module' || t === 'typescript') {
354372
return [t, { esm }];
355373
}
356374

0 commit comments

Comments
 (0)