Skip to content

Commit c9049b5

Browse files
committed
feat: add ESM support for generated project
1 parent a971d5f commit c9049b5

File tree

9 files changed

+182
-99
lines changed

9 files changed

+182
-99
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import prompts, { type PromptObject } from './utils/prompts';
1111
import generateExampleApp from './utils/generateExampleApp';
1212
import { spawn } from './utils/spawn';
1313

14-
const FALLBACK_BOB_VERSION = '0.20.0';
14+
const FALLBACK_BOB_VERSION = '0.24.0';
1515

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

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

+10-4
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22
"name": "<%- project.slug -%>",
33
"version": "0.1.0",
44
"description": "<%- project.description %>",
5-
"main": "lib/commonjs/index",
6-
"module": "lib/module/index",
7-
"types": "lib/typescript/src/index.d.ts",
8-
"react-native": "src/index",
95
"source": "src/index",
6+
"main": "lib/commonjs/index.cjs",
7+
"module": "lib/module/index.mjs",
8+
"types": "lib/typescript/src/index.d.ts",
9+
"exports": {
10+
".": {
11+
"types": "./lib/typescript/src/index.d.ts",
12+
"import": "./lib/module/index.mjs",
13+
"require": "./lib/commonjs/index.cjs"
14+
}
15+
},
1016
"files": [
1117
"src",
1218
"lib",

packages/create-react-native-library/templates/common/tsconfig.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
"esModuleInterop": true,
1010
"forceConsistentCasingInFileNames": true,
1111
"jsx": "react",
12-
"lib": ["esnext"],
13-
"module": "esnext",
14-
"moduleResolution": "node",
12+
"lib": ["ESNext"],
13+
"module": "ESNext",
14+
"moduleResolution": "Bundler",
1515
"noFallthroughCasesInSwitch": true,
1616
"noImplicitReturns": true,
1717
"noImplicitUseStrict": false,
@@ -22,7 +22,7 @@
2222
"resolveJsonModule": true,
2323
"skipLibCheck": true,
2424
"strict": true,
25-
"target": "esnext",
25+
"target": "ESNext",
2626
"verbatimModuleSyntax": true
2727
}
2828
}

packages/react-native-builder-bob/babel-preset.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
const browserslist = require('browserslist');
44

55
module.exports = function (api, options, cwd) {
6+
const cjs = options.modules === 'commonjs';
7+
68
return {
79
presets: [
810
[
@@ -24,12 +26,20 @@ module.exports = function (api, options, cwd) {
2426
node: '18',
2527
},
2628
useBuiltIns: false,
27-
modules: options.modules || false,
29+
modules: cjs ? 'commonjs' : false,
2830
},
2931
],
3032
require.resolve('@babel/preset-react'),
3133
require.resolve('@babel/preset-typescript'),
3234
require.resolve('@babel/preset-flow'),
3335
],
36+
plugins: [
37+
[
38+
require.resolve('./lib/babel'),
39+
{
40+
extension: cjs ? 'cjs' : 'mjs',
41+
},
42+
],
43+
],
3444
};
3545
};

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

+47-11
Original file line numberDiff line numberDiff line change
@@ -147,16 +147,17 @@ yargs
147147
? targets[0]
148148
: undefined;
149149

150-
const entries: { [key: string]: string } = {
151-
'main': target
152-
? path.join(output, target, 'index.js')
150+
const entries: {
151+
[key in 'source' | 'main' | 'module' | 'types']?: string;
152+
} = {
153+
source: path.join(source, entryFile),
154+
main: target
155+
? path.join(output, target, 'index.cjs')
153156
: path.join(source, entryFile),
154-
'react-native': path.join(source, entryFile),
155-
'source': path.join(source, entryFile),
156157
};
157158

158159
if (targets.includes('module')) {
159-
entries.module = path.join(output, 'module', 'index.js');
160+
entries.module = path.join(output, 'module', 'index.mjs');
160161
}
161162

162163
if (targets.includes('typescript')) {
@@ -181,9 +182,9 @@ yargs
181182
esModuleInterop: true,
182183
forceConsistentCasingInFileNames: true,
183184
jsx: 'react',
184-
lib: ['esnext'],
185-
module: 'esnext',
186-
moduleResolution: 'node',
185+
lib: ['ESNext'],
186+
module: 'ESNext',
187+
moduleResolution: 'Bundler',
187188
noFallthroughCasesInSwitch: true,
188189
noImplicitReturns: true,
189190
noImplicitUseStrict: false,
@@ -194,7 +195,7 @@ yargs
194195
resolveJsonModule: true,
195196
skipLibCheck: true,
196197
strict: true,
197-
target: 'esnext',
198+
target: 'ESNext',
198199
verbatimModuleSyntax: true,
199200
},
200201
},
@@ -214,7 +215,7 @@ yargs
214215
];
215216

216217
for (const key in entries) {
217-
const entry = entries[key];
218+
const entry = entries[key as keyof typeof entries];
218219

219220
if (pkg[key] && pkg[key] !== entry) {
220221
const { replace } = await prompts({
@@ -232,6 +233,41 @@ yargs
232233
}
233234
}
234235

236+
if (Object.values(entries).some((entry) => entry.endsWith('.ms'))) {
237+
let replace = false;
238+
239+
if (pkg.exports) {
240+
replace = (
241+
await prompts({
242+
type: 'confirm',
243+
name: 'replace',
244+
message: `Your package.json has 'exports' field set. Do you want to replace it?`,
245+
initial: true,
246+
})
247+
).replace;
248+
} else {
249+
replace = true;
250+
}
251+
252+
if (replace) {
253+
pkg.exports = {
254+
'.': {},
255+
};
256+
257+
if (entries.types) {
258+
pkg.exports['.'].types = entries.types;
259+
}
260+
261+
if (entries.module) {
262+
pkg.exports['.'].import = entries.module;
263+
}
264+
265+
if (entries.main) {
266+
pkg.exports['.'].require = entries.main;
267+
}
268+
}
269+
}
270+
235271
if (pkg.scripts?.prepare && pkg.scripts.prepare !== prepare) {
236272
const { replace } = await prompts({
237273
type: 'confirm',

packages/react-native-builder-bob/src/targets/commonjs.ts

-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,5 @@ export default async function build({
3636
exclude,
3737
modules: 'commonjs',
3838
report,
39-
field: 'main',
4039
});
4140
}

packages/react-native-builder-bob/src/targets/module.ts

-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,5 @@ export default async function build({
3636
exclude,
3737
modules: false,
3838
report,
39-
field: 'module',
4039
});
4140
}

packages/react-native-builder-bob/src/targets/typescript.ts

+45-32
Original file line numberDiff line numberDiff line change
@@ -217,43 +217,56 @@ export default async function build({
217217
return null;
218218
};
219219

220-
if ('types' in pkg) {
221-
const typesPath = path.join(root, pkg.types);
222-
223-
if (!(await fs.pathExists(typesPath))) {
224-
const generatedTypesPath = await getGeneratedTypesPath();
225-
226-
if (!generatedTypesPath) {
227-
report.warn(
228-
`Failed to detect the entry point for the generated types. Make sure you have a valid ${kleur.blue(
229-
'source'
230-
)} field in your ${kleur.blue('package.json')}.`
231-
);
232-
}
233-
234-
report.error(
235-
`The ${kleur.blue('types')} field in ${kleur.blue(
236-
'package.json'
237-
)} points to a non-existent file: ${kleur.blue(
238-
pkg.types
239-
)}.\nVerify the path points to the correct file under ${kleur.blue(
240-
path.relative(root, output)
241-
)}${
242-
generatedTypesPath
243-
? ` (found ${kleur.blue(generatedTypesPath)}).`
244-
: '.'
245-
}`
246-
);
220+
const fields = [
221+
{ name: 'types', value: pkg.types },
222+
{ name: "exports['.'].types", value: pkg.exports?.['.']?.types },
223+
];
224+
225+
if (fields.some((field) => field.value)) {
226+
await Promise.all(
227+
fields.map(async ({ name, value }) => {
228+
if (!value) {
229+
return;
230+
}
247231

248-
throw new Error("Found incorrect path in 'types' field.");
249-
}
232+
const typesPath = path.join(root, value);
233+
234+
if (!(await fs.pathExists(typesPath))) {
235+
const generatedTypesPath = await getGeneratedTypesPath();
236+
237+
if (!generatedTypesPath) {
238+
report.warn(
239+
`Failed to detect the entry point for the generated types. Make sure you have a valid ${kleur.blue(
240+
'source'
241+
)} field in your ${kleur.blue('package.json')}.`
242+
);
243+
}
244+
245+
report.error(
246+
`The ${kleur.blue(name)} field in ${kleur.blue(
247+
'package.json'
248+
)} points to a non-existent file: ${kleur.blue(
249+
value
250+
)}.\nVerify the path points to the correct file under ${kleur.blue(
251+
path.relative(root, output)
252+
)}${
253+
generatedTypesPath
254+
? ` (found ${kleur.blue(generatedTypesPath)}).`
255+
: '.'
256+
}`
257+
);
258+
259+
throw new Error(`Found incorrect path in '${name}' field.`);
260+
}
261+
})
262+
);
250263
} else {
251264
const generatedTypesPath = await getGeneratedTypesPath();
252265

253266
report.warn(
254-
`No ${kleur.blue('types')} field found in ${kleur.blue(
255-
'package.json'
256-
)}.\nConsider ${
267+
`No ${kleur.blue(
268+
fields.map((field) => field.name).join(' or ')
269+
)} field found in ${kleur.blue('package.json')}.\nConsider ${
257270
generatedTypesPath
258271
? `pointing it to ${kleur.blue(generatedTypesPath)}`
259272
: 'adding it'

0 commit comments

Comments
 (0)