From 8c6e733ec4640d13bbe9bfd0e2dc7339e29eaee0 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Tue, 2 Jul 2024 16:14:59 +0200 Subject: [PATCH 1/3] feat: add ESM support for generated project --- docs/pages/build.md | 23 ++-- .../create-react-native-library/src/index.ts | 2 +- .../templates/common/$package.json | 16 ++- .../templates/common/tsconfig.json | 8 +- .../react-native-builder-bob/babel-preset.js | 12 +- .../react-native-builder-bob/src/index.ts | 69 ++++++++--- .../src/targets/commonjs.ts | 1 - .../src/targets/module.ts | 1 - .../src/targets/typescript.ts | 77 +++++++------ .../src/utils/compile.ts | 108 +++++++++++------- 10 files changed, 207 insertions(+), 110 deletions(-) diff --git a/docs/pages/build.md b/docs/pages/build.md index 8e0b334d5..a3c07190e 100644 --- a/docs/pages/build.md +++ b/docs/pages/build.md @@ -73,11 +73,17 @@ yarn add --dev react-native-builder-bob 1. Configure the appropriate entry points: ```json - "main": "lib/commonjs/index.js", - "module": "lib/module/index.js", - "react-native": "src/index.ts", - "types": "lib/typescript/src/index.d.ts", - "source": "src/index.ts", + "source": "./src/index.tsx", + "main": "./lib/commonjs/index.cjs", + "module": "./lib/module/index.mjs", + "types": "./lib/typescript/src/index.d.ts", + "exports": { + ".": { + "types": "./typescript/src/index.d.ts", + "require": "./commonjs/index.cjs", + "import": "./module/index.mjs" + } + }, "files": [ "lib", "src" @@ -88,7 +94,6 @@ yarn add --dev react-native-builder-bob - `main`: The entry point for the commonjs build. This is used by Node - such as tests, SSR etc. - `module`: The entry point for the ES module build. This is used by bundlers such as webpack. - - `react-native`: The entry point for the React Native apps. This is used by Metro. It's common to point to the source code here as it can make debugging easier. - `types`: The entry point for the TypeScript definitions. This is used by TypeScript to type check the code using your library. - `source`: The path to the source code. It is used by `react-native-builder-bob` to detect the correct output files and provide better error messages. - `files`: The files to include in the package when publishing with `npm`. @@ -150,7 +155,7 @@ Various targets to build for. The available targets are: Enable compiling source files with Babel and use commonjs module system. -This is useful for running the code in Node (SSR, tests etc.). The output file should be referenced in the `main` field of `package.json`. +This is useful for running the code in Node (SSR, tests etc.). The output file should be referenced in the `main` field and `exports['.'].require` field of `package.json`. By default, the code is compiled to support last 2 versions of modern browsers. It also strips TypeScript and Flow annotations, and compiles JSX. You can customize the environments to compile for by using a [browserslist config](https://github.com/browserslist/browserslist#config-file). @@ -174,7 +179,7 @@ Example: Enable compiling source files with Babel and use ES module system. This is essentially same as the `commonjs` target and accepts the same options, but leaves the `import`/`export` statements in your code. -This is useful for bundlers which understand ES modules and can tree-shake. The output file should be referenced in the `module` field of `package.json`. +This is useful for bundlers which understand ES modules and can tree-shake. The output file should be referenced in the `module` field and `exports['.'].import` field of `package.json`. Example: @@ -198,6 +203,8 @@ Example: ["typescript", { "project": "tsconfig.build.json" }] ``` +The output file should be referenced in the `types` field or `exports['.'].types` field of `package.json`. + ## Commands The `bob` CLI exposes the following commands: diff --git a/packages/create-react-native-library/src/index.ts b/packages/create-react-native-library/src/index.ts index 77566886d..f4424c392 100644 --- a/packages/create-react-native-library/src/index.ts +++ b/packages/create-react-native-library/src/index.ts @@ -14,7 +14,7 @@ import generateExampleApp, { import { spawn } from './utils/spawn'; import { version } from '../package.json'; -const FALLBACK_BOB_VERSION = '0.20.0'; +const FALLBACK_BOB_VERSION = '0.25.0'; const BINARIES = [ /(gradlew|\.(jar|keystore|png|jpg|gif))$/, diff --git a/packages/create-react-native-library/templates/common/$package.json b/packages/create-react-native-library/templates/common/$package.json index f0388daa4..0ee03784d 100644 --- a/packages/create-react-native-library/templates/common/$package.json +++ b/packages/create-react-native-library/templates/common/$package.json @@ -2,11 +2,17 @@ "name": "<%- project.slug -%>", "version": "0.1.0", "description": "<%- project.description %>", - "main": "lib/commonjs/index", - "module": "lib/module/index", - "types": "lib/typescript/src/index.d.ts", - "react-native": "src/index", - "source": "src/index", + "source": "./src/index.tsx", + "main": "./lib/commonjs/index.cjs", + "module": "./lib/module/index.mjs", + "types": "./lib/typescript/src/index.d.ts", + "exports": { + ".": { + "types": "./lib/typescript/src/index.d.ts", + "import": "./lib/module/index.mjs", + "require": "./lib/commonjs/index.cjs" + } + }, "files": [ "src", "lib", diff --git a/packages/create-react-native-library/templates/common/tsconfig.json b/packages/create-react-native-library/templates/common/tsconfig.json index cd3d623ba..d76cc5f8c 100644 --- a/packages/create-react-native-library/templates/common/tsconfig.json +++ b/packages/create-react-native-library/templates/common/tsconfig.json @@ -9,9 +9,9 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "jsx": "react", - "lib": ["esnext"], - "module": "esnext", - "moduleResolution": "node", + "lib": ["ESNext"], + "module": "ESNext", + "moduleResolution": "Bundler", "noFallthroughCasesInSwitch": true, "noImplicitReturns": true, "noImplicitUseStrict": false, @@ -22,7 +22,7 @@ "resolveJsonModule": true, "skipLibCheck": true, "strict": true, - "target": "esnext", + "target": "ESNext", "verbatimModuleSyntax": true } } diff --git a/packages/react-native-builder-bob/babel-preset.js b/packages/react-native-builder-bob/babel-preset.js index f664c15b1..7f5d51614 100644 --- a/packages/react-native-builder-bob/babel-preset.js +++ b/packages/react-native-builder-bob/babel-preset.js @@ -3,6 +3,8 @@ const browserslist = require('browserslist'); module.exports = function (api, options, cwd) { + const cjs = options.modules === 'commonjs'; + return { presets: [ [ @@ -24,12 +26,20 @@ module.exports = function (api, options, cwd) { node: '18', }, useBuiltIns: false, - modules: options.modules || false, + modules: cjs ? 'commonjs' : false, }, ], require.resolve('@babel/preset-react'), require.resolve('@babel/preset-typescript'), require.resolve('@babel/preset-flow'), ], + plugins: [ + [ + require.resolve('./lib/babel'), + { + extension: cjs ? 'cjs' : 'mjs', + }, + ], + ], }; }; diff --git a/packages/react-native-builder-bob/src/index.ts b/packages/react-native-builder-bob/src/index.ts index b66859f91..649ba0e2a 100644 --- a/packages/react-native-builder-bob/src/index.ts +++ b/packages/react-native-builder-bob/src/index.ts @@ -147,20 +147,28 @@ yargs ? targets[0] : undefined; - const entries: { [key: string]: string } = { - 'main': target - ? path.join(output, target, 'index.js') - : path.join(source, entryFile), - 'react-native': path.join(source, entryFile), - 'source': path.join(source, entryFile), + const entries: { + [key in 'source' | 'main' | 'module' | 'types']?: string; + } = { + source: `./${path.join(source, entryFile)}`, + main: `./${ + target + ? path.join(output, target, 'index.cjs') + : path.join(source, entryFile) + }`, }; if (targets.includes('module')) { - entries.module = path.join(output, 'module', 'index.js'); + entries.module = `./${path.join(output, 'module', 'index.mjs')}`; } if (targets.includes('typescript')) { - entries.types = path.join(output, 'typescript', source, 'index.d.ts'); + entries.types = `./${path.join( + output, + 'typescript', + source, + 'index.d.ts' + )}`; if (!(await fs.pathExists(path.join(root, 'tsconfig.json')))) { const { tsconfig } = await prompts({ @@ -181,9 +189,9 @@ yargs esModuleInterop: true, forceConsistentCasingInFileNames: true, jsx: 'react', - lib: ['esnext'], - module: 'esnext', - moduleResolution: 'node', + lib: ['ESNext'], + module: 'ESNext', + moduleResolution: 'Bundler', noFallthroughCasesInSwitch: true, noImplicitReturns: true, noImplicitUseStrict: false, @@ -194,7 +202,7 @@ yargs resolveJsonModule: true, skipLibCheck: true, strict: true, - target: 'esnext', + target: 'ESNext', verbatimModuleSyntax: true, }, }, @@ -214,7 +222,7 @@ yargs ]; for (const key in entries) { - const entry = entries[key]; + const entry = entries[key as keyof typeof entries]; if (pkg[key] && pkg[key] !== entry) { const { replace } = await prompts({ @@ -232,6 +240,41 @@ yargs } } + if (Object.values(entries).some((entry) => entry.endsWith('.mjs'))) { + let replace = false; + + if (pkg.exports) { + replace = ( + await prompts({ + type: 'confirm', + name: 'replace', + message: `Your package.json has 'exports' field set. Do you want to replace it?`, + initial: true, + }) + ).replace; + } else { + replace = true; + } + + if (replace) { + pkg.exports = { + '.': {}, + }; + + if (entries.types) { + pkg.exports['.'].types = entries.types; + } + + if (entries.module) { + pkg.exports['.'].import = entries.module; + } + + if (entries.main) { + pkg.exports['.'].require = entries.main; + } + } + } + if (pkg.scripts?.prepare && pkg.scripts.prepare !== prepare) { const { replace } = await prompts({ type: 'confirm', diff --git a/packages/react-native-builder-bob/src/targets/commonjs.ts b/packages/react-native-builder-bob/src/targets/commonjs.ts index 2092906cd..35c9de57f 100644 --- a/packages/react-native-builder-bob/src/targets/commonjs.ts +++ b/packages/react-native-builder-bob/src/targets/commonjs.ts @@ -36,6 +36,5 @@ export default async function build({ exclude, modules: 'commonjs', report, - field: 'main', }); } diff --git a/packages/react-native-builder-bob/src/targets/module.ts b/packages/react-native-builder-bob/src/targets/module.ts index e6cee46c7..5ad1534a6 100644 --- a/packages/react-native-builder-bob/src/targets/module.ts +++ b/packages/react-native-builder-bob/src/targets/module.ts @@ -36,6 +36,5 @@ export default async function build({ exclude, modules: false, report, - field: 'module', }); } diff --git a/packages/react-native-builder-bob/src/targets/typescript.ts b/packages/react-native-builder-bob/src/targets/typescript.ts index 77ec303d1..9d2d9e1a7 100644 --- a/packages/react-native-builder-bob/src/targets/typescript.ts +++ b/packages/react-native-builder-bob/src/targets/typescript.ts @@ -217,43 +217,56 @@ export default async function build({ return null; }; - if ('types' in pkg) { - const typesPath = path.join(root, pkg.types); - - if (!(await fs.pathExists(typesPath))) { - const generatedTypesPath = await getGeneratedTypesPath(); - - if (!generatedTypesPath) { - report.warn( - `Failed to detect the entry point for the generated types. Make sure you have a valid ${kleur.blue( - 'source' - )} field in your ${kleur.blue('package.json')}.` - ); - } - - report.error( - `The ${kleur.blue('types')} field in ${kleur.blue( - 'package.json' - )} points to a non-existent file: ${kleur.blue( - pkg.types - )}.\nVerify the path points to the correct file under ${kleur.blue( - path.relative(root, output) - )}${ - generatedTypesPath - ? ` (found ${kleur.blue(generatedTypesPath)}).` - : '.' - }` - ); + const fields = [ + { name: 'types', value: pkg.types }, + { name: "exports['.'].types", value: pkg.exports?.['.']?.types }, + ]; + + if (fields.some((field) => field.value)) { + await Promise.all( + fields.map(async ({ name, value }) => { + if (!value) { + return; + } - throw new Error("Found incorrect path in 'types' field."); - } + const typesPath = path.join(root, value); + + if (!(await fs.pathExists(typesPath))) { + const generatedTypesPath = await getGeneratedTypesPath(); + + if (!generatedTypesPath) { + report.warn( + `Failed to detect the entry point for the generated types. Make sure you have a valid ${kleur.blue( + 'source' + )} field in your ${kleur.blue('package.json')}.` + ); + } + + report.error( + `The ${kleur.blue(name)} field in ${kleur.blue( + 'package.json' + )} points to a non-existent file: ${kleur.blue( + value + )}.\nVerify the path points to the correct file under ${kleur.blue( + path.relative(root, output) + )}${ + generatedTypesPath + ? ` (found ${kleur.blue(generatedTypesPath)}).` + : '.' + }` + ); + + throw new Error(`Found incorrect path in '${name}' field.`); + } + }) + ); } else { const generatedTypesPath = await getGeneratedTypesPath(); report.warn( - `No ${kleur.blue('types')} field found in ${kleur.blue( - 'package.json' - )}.\nConsider ${ + `No ${kleur.blue( + fields.map((field) => field.name).join(' or ') + )} field found in ${kleur.blue('package.json')}.\nConsider ${ generatedTypesPath ? `pointing it to ${kleur.blue(generatedTypesPath)}` : 'adding it' diff --git a/packages/react-native-builder-bob/src/utils/compile.ts b/packages/react-native-builder-bob/src/utils/compile.ts index 3e0cc8e8c..52a2bd9b6 100644 --- a/packages/react-native-builder-bob/src/utils/compile.ts +++ b/packages/react-native-builder-bob/src/utils/compile.ts @@ -11,7 +11,6 @@ type Options = Input & { sourceMaps?: boolean; copyFlow?: boolean; modules: 'commonjs' | false; - field: 'main' | 'module'; exclude: string; }; @@ -26,7 +25,6 @@ export default async function compile({ copyFlow, sourceMaps = true, report, - field, }: Options) { const files = glob.sync('**/*', { cwd: source, @@ -65,11 +63,13 @@ export default async function compile({ } } + const outputExtension = modules === 'commonjs' ? '.cjs' : '.mjs'; + await Promise.all( files.map(async (filepath) => { const outputFilename = path .join(output, path.relative(source, filepath)) - .replace(/\.(jsx?|tsx?)$/, '.js'); + .replace(/\.(jsx?|tsx?)$/, outputExtension); await fs.mkdirp(path.dirname(outputFilename)); @@ -125,7 +125,8 @@ export default async function compile({ const getGeneratedEntryPath = async () => { if (pkg.source) { const indexName = - path.basename(pkg.source).replace(/\.(jsx?|tsx?)$/, '') + '.js'; + path.basename(pkg.source).replace(/\.(jsx?|tsx?)$/, '') + + outputExtension; const potentialPath = path.join( output, @@ -141,52 +142,71 @@ export default async function compile({ return null; }; - if (field in pkg) { - try { - require.resolve(path.join(root, pkg[field])); - } catch (e: unknown) { - if ( - e != null && - typeof e === 'object' && - 'code' in e && - e.code === 'MODULE_NOT_FOUND' - ) { - const generatedEntryPath = await getGeneratedEntryPath(); - - if (!generatedEntryPath) { - report.warn( - `Failed to detect the entry point for the generated files. Make sure you have a valid ${kleur.blue( - 'source' - )} field in your ${kleur.blue('package.json')}.` - ); + const fields = + modules === 'commonjs' + ? [ + { name: 'main', value: pkg.main }, + { name: "exports['.'].require", value: pkg.exports?.['.']?.require }, + ] + : [ + { name: 'module', value: pkg.module }, + { name: "exports['.'].import", value: pkg.exports?.['.']?.import }, + ]; + + if (fields.some((field) => field.value)) { + await Promise.all( + fields.map(async ({ name, value }) => { + if (!value) { + return; } - report.error( - `The ${kleur.blue(field)} field in ${kleur.blue( - 'package.json' - )} points to a non-existent file: ${kleur.blue( - pkg[field] - )}.\nVerify the path points to the correct file under ${kleur.blue( - path.relative(root, output) - )}${ - generatedEntryPath - ? ` (found ${kleur.blue(generatedEntryPath)}).` - : '.' - }` - ); - - throw new Error(`Found incorrect path in '${field}' field.`); - } - - throw e; - } + try { + require.resolve(path.join(root, value)); + } catch (e: unknown) { + if ( + e != null && + typeof e === 'object' && + 'code' in e && + e.code === 'MODULE_NOT_FOUND' + ) { + const generatedEntryPath = await getGeneratedEntryPath(); + + if (!generatedEntryPath) { + report.warn( + `Failed to detect the entry point for the generated files. Make sure you have a valid ${kleur.blue( + 'source' + )} field in your ${kleur.blue('package.json')}.` + ); + } + + report.error( + `The ${kleur.blue(name)} field in ${kleur.blue( + 'package.json' + )} points to a non-existent file: ${kleur.blue( + value + )}.\nVerify the path points to the correct file under ${kleur.blue( + path.relative(root, output) + )}${ + generatedEntryPath + ? ` (found ${kleur.blue(generatedEntryPath)}).` + : '.' + }` + ); + + throw new Error(`Found incorrect path in '${name}' field.`); + } + + throw e; + } + }) + ); } else { const generatedEntryPath = await getGeneratedEntryPath(); report.warn( - `No ${kleur.blue(field)} field found in ${kleur.blue( - 'package.json' - )}. Consider ${ + `No ${kleur.blue( + fields.map((field) => field.name).join(' or ') + )} field found in ${kleur.blue('package.json')}. Consider ${ generatedEntryPath ? `pointing it to ${kleur.blue(generatedEntryPath)}` : 'adding it' From c45812822cd3032ad2380a45e853f67df73ead6e Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Tue, 2 Jul 2024 22:32:22 +0200 Subject: [PATCH 2/3] feat: switch to new jsx runtime --- .github/workflows/build-templates.yml | 2 -- .../templates/common-example/example/src/App.tsx | 9 +++++---- .../templates/common/$package.json | 1 + .../templates/common/tsconfig.json | 2 +- packages/react-native-builder-bob/babel-preset.js | 7 ++++++- packages/react-native-builder-bob/src/index.ts | 2 +- tsconfig.json | 2 +- 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-templates.yml b/.github/workflows/build-templates.yml index 43f185d55..e316a148f 100644 --- a/.github/workflows/build-templates.yml +++ b/.github/workflows/build-templates.yml @@ -200,8 +200,6 @@ jobs: working-directory: ${{ env.work_dir }} run: | yarn typecheck - # FIXME: Remove this once we fix the typecheck errors - continue-on-error: true - name: Test library working-directory: ${{ env.work_dir }} diff --git a/packages/create-react-native-library/templates/common-example/example/src/App.tsx b/packages/create-react-native-library/templates/common-example/example/src/App.tsx index 38db6fe50..acc5c7d17 100644 --- a/packages/create-react-native-library/templates/common-example/example/src/App.tsx +++ b/packages/create-react-native-library/templates/common-example/example/src/App.tsx @@ -1,9 +1,10 @@ -import * as React from 'react'; - <% if (project.view) { -%> import { StyleSheet, View } from 'react-native'; import { <%- project.name -%>View } from '<%- project.slug -%>'; <% } else { -%> +<% if (project.arch !== 'new') { -%> +import { useState, useEffect } from 'react'; +<% } -%> import { StyleSheet, View, Text } from 'react-native'; import { multiply } from '<%- project.slug -%>'; <% } -%> @@ -28,9 +29,9 @@ export default function App() { } <% } else { -%> export default function App() { - const [result, setResult] = React.useState(); + const [result, setResult] = useState(); - React.useEffect(() => { + useEffect(() => { multiply(3, 7).then(setResult); }, []); diff --git a/packages/create-react-native-library/templates/common/$package.json b/packages/create-react-native-library/templates/common/$package.json index 0ee03784d..85f078c19 100644 --- a/packages/create-react-native-library/templates/common/$package.json +++ b/packages/create-react-native-library/templates/common/$package.json @@ -136,6 +136,7 @@ "prettier" ], "rules": { + "react/react-in-jsx-scope": "off", "prettier/prettier": [ "error", { diff --git a/packages/create-react-native-library/templates/common/tsconfig.json b/packages/create-react-native-library/templates/common/tsconfig.json index d76cc5f8c..7472abcad 100644 --- a/packages/create-react-native-library/templates/common/tsconfig.json +++ b/packages/create-react-native-library/templates/common/tsconfig.json @@ -8,7 +8,7 @@ "allowUnusedLabels": false, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "jsx": "react", + "jsx": "react-jsx", "lib": ["ESNext"], "module": "ESNext", "moduleResolution": "Bundler", diff --git a/packages/react-native-builder-bob/babel-preset.js b/packages/react-native-builder-bob/babel-preset.js index 7f5d51614..2b725e98d 100644 --- a/packages/react-native-builder-bob/babel-preset.js +++ b/packages/react-native-builder-bob/babel-preset.js @@ -29,7 +29,12 @@ module.exports = function (api, options, cwd) { modules: cjs ? 'commonjs' : false, }, ], - require.resolve('@babel/preset-react'), + [ + require.resolve('@babel/preset-react'), + { + runtime: 'automatic', + }, + ], require.resolve('@babel/preset-typescript'), require.resolve('@babel/preset-flow'), ], diff --git a/packages/react-native-builder-bob/src/index.ts b/packages/react-native-builder-bob/src/index.ts index 649ba0e2a..940760f1c 100644 --- a/packages/react-native-builder-bob/src/index.ts +++ b/packages/react-native-builder-bob/src/index.ts @@ -188,7 +188,7 @@ yargs allowUnusedLabels: false, esModuleInterop: true, forceConsistentCasingInFileNames: true, - jsx: 'react', + jsx: 'react-jsx', lib: ['ESNext'], module: 'ESNext', moduleResolution: 'Bundler', diff --git a/tsconfig.json b/tsconfig.json index 90faea635..642896b15 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,7 @@ "allowUnusedLabels": false, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "jsx": "react", + "jsx": "react-jsx", "lib": ["esnext", "dom"], "module": "esnext", "moduleResolution": "node", From 0936f1c86a5fbff7f99940cd40f0ec8b91f05d59 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Wed, 3 Jul 2024 16:30:40 +0200 Subject: [PATCH 3/3] fix: prompt to remove problematic react-native field --- .../react-native-builder-bob/src/index.ts | 60 ++++++++++++------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/packages/react-native-builder-bob/src/index.ts b/packages/react-native-builder-bob/src/index.ts index 940760f1c..4baff1af5 100644 --- a/packages/react-native-builder-bob/src/index.ts +++ b/packages/react-native-builder-bob/src/index.ts @@ -28,7 +28,7 @@ yargs const { shouldContinue } = await prompts({ type: 'confirm', name: 'shouldContinue', - message: `The working directory is not clean. You should commit or stash your changes before configuring bob. Continue anyway?`, + message: `The working directory is not clean.\n You should commit or stash your changes before configuring bob.\n Continue anyway?`, initial: false, }); @@ -41,7 +41,7 @@ yargs if (!(await fs.pathExists(pak))) { logger.exit( - `Couldn't find a 'package.json' file in '${root}'. Are you in a project folder?` + `Couldn't find a 'package.json' file in '${root}'.\n Are you in a project folder?` ); } @@ -52,7 +52,7 @@ yargs const { shouldContinue } = await prompts({ type: 'confirm', name: 'shouldContinue', - message: `The project seems to be already configured with bob. Do you want to overwrite the existing configuration?`, + message: `The project seems to be already configured with bob.\n Do you want to overwrite the existing configuration?`, initial: false, }); @@ -81,7 +81,7 @@ yargs if (!entryFile) { logger.exit( - `Couldn't find a 'index.js'. 'index.ts' or 'index.tsx' file under '${source}'. Please re-run the CLI after creating it.` + `Couldn't find a 'index.js'. 'index.ts' or 'index.tsx' file under '${source}'.\n Please re-run the CLI after creating it.` ); return; } @@ -174,7 +174,7 @@ yargs const { tsconfig } = await prompts({ type: 'confirm', name: 'tsconfig', - message: `You have enabled 'typescript' compilation, but we couldn't find a 'tsconfig.json' in project root. Generate one?`, + message: `You have enabled 'typescript' compilation, but we couldn't find a 'tsconfig.json' in project root.\n Generate one?`, initial: true, }); @@ -228,7 +228,7 @@ yargs const { replace } = await prompts({ type: 'confirm', name: 'replace', - message: `Your package.json has the '${key}' field set to '${pkg[key]}'. Do you want to replace it with '${entry}'?`, + message: `Your package.json has the '${key}' field set to '${pkg[key]}'.\n Do you want to replace it with '${entry}'?`, initial: true, }); @@ -243,12 +243,23 @@ yargs if (Object.values(entries).some((entry) => entry.endsWith('.mjs'))) { let replace = false; - if (pkg.exports) { + const exports = { + '.': { + ...(entries.types ? { types: entries.types } : null), + ...(entries.module ? { import: entries.module } : null), + ...(entries.main ? { require: entries.main } : null), + }, + }; + + if ( + pkg.exports && + JSON.stringify(pkg.exports) !== JSON.stringify(exports) + ) { replace = ( await prompts({ type: 'confirm', name: 'replace', - message: `Your package.json has 'exports' field set. Do you want to replace it?`, + message: `Your package.json has 'exports' field set.\n Do you want to replace it?`, initial: true, }) ).replace; @@ -257,21 +268,24 @@ yargs } if (replace) { - pkg.exports = { - '.': {}, - }; - - if (entries.types) { - pkg.exports['.'].types = entries.types; - } + pkg.exports = exports; + } + } - if (entries.module) { - pkg.exports['.'].import = entries.module; - } + if ( + pkg['react-native'] && + (pkg['react-native'].startsWith(source) || + pkg['react-native'].startsWith(`./${source}`)) + ) { + const { remove } = await prompts({ + type: 'confirm', + name: 'remove', + message: `Your package.json has the 'react-native' field pointing to source code.\n This can cause problems when customizing babel configuration.\n Do you want to remove it?`, + initial: true, + }); - if (entries.main) { - pkg.exports['.'].require = entries.main; - } + if (remove) { + delete pkg['react-native']; } } @@ -279,7 +293,7 @@ yargs const { replace } = await prompts({ type: 'confirm', name: 'replace', - message: `Your package.json has the 'scripts.prepare' field set to '${pkg.scripts.prepare}'. Do you want to replace it with '${prepare}'?`, + message: `Your package.json has the 'scripts.prepare' field set to '${pkg.scripts.prepare}'.\n Do you want to replace it with '${prepare}'?`, initial: true, }); @@ -299,7 +313,7 @@ yargs const { update } = await prompts({ type: 'confirm', name: 'update', - message: `Your package.json already has a 'files' field. Do you want to update it?`, + message: `Your package.json already has a 'files' field.\n Do you want to update it?`, initial: true, });