Skip to content

Commit

Permalink
fix(react-native): fix buildable react native library (#16749)
Browse files Browse the repository at this point in the history
  • Loading branch information
xiongemi authored May 9, 2023
1 parent c9a7cd8 commit 8347e61
Show file tree
Hide file tree
Showing 12 changed files with 165 additions and 122 deletions.
42 changes: 9 additions & 33 deletions docs/shared/guides/react-native.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# React Native with Nx

![React Logo](/shared/react-logo.png)

Nx provides a holistic dev experience powered by an advanced CLI and editor plugins. It provides rich support for common tools like [Detox](/packages/detox), Storybook, Jest, and more.

In this guide we will show you how to develop [React Native](https://reactnative.dev/) applications with Nx.
Expand Down Expand Up @@ -80,6 +78,10 @@ happynrwl/

To run the application in development mode:

```shell
npx nx start mobile
```

On Android simulator/device:

```shell
Expand All @@ -96,7 +98,6 @@ Try out other commands as well.

- `nx lint mobile` to lint the application
- `nx test mobile` to run unit test on the application using Jest
- `nx serve mobile` to serve the application Javascript bundler that communicates with connected devices. This will start the bundler at http://localhost:8081.
- `nx sync-deps mobile` to sync app dependencies to its `package.json`.

### Release build
Expand All @@ -109,7 +110,9 @@ npx nx build-android mobile

**iOS:** (Mac only)

No CLI support yet. Run in the Xcode project. See: https://reactnative.dev/docs/running-on-device
```shell
npx nx build-ios mobile
```

### E2E

Expand All @@ -125,7 +128,7 @@ npx nx test-android mobile-e2e
npx nx test-ios mobile-e2e
```

When using React Native in Nx, you get the out-of-the-box support for TypeScript, Detox, and Jest. No need to configure anything: watch mode, source maps, and typings just work.
When using React Native in Nx, you get the out-of-the-box support for TypeScript, Detox, and Jest.

### Adding React Native to an Existing Workspace

Expand Down Expand Up @@ -258,40 +261,13 @@ dist/libs/shared-ui-layout/
├── lib/
│ └── layout/
│ └── layout.d.ts
├── package.json
├── shared-ui-layout.esm.css
├── shared-ui-layout.esm.js
├── shared-ui-layout.umd.css
└── shared-ui-layout.umd.js
└── package.json
```

This dist folder is ready to be published to a registry.

## Environment Variables

The workspace should install[react-native-config](https://github.com/luggit/react-native-config) by default. To use environment variable, create a new `.env` file in the `happynrwl/apps/mobile` folder:

```
NX_BUILD_NUMBER=123
```

Then access variables defined there from your app:

```javascript
import Config from 'react-native-config';

Config.NX_BUILD_NUMBER; // '123'
```

## Code Sharing

Without Nx, creating a new shared library can take from several hours to even weeks: a new repo needs to be provisioned, CI needs to be set up, etc... In an Nx Workspace, it only takes minutes.

You can share React Native components between multiple React Native applications, share business logic code between React Native mobile applications and plain React web applications. You can even share code between the backend and the frontend. All of these can be done without any unnecessary ceremony.

## Resources

Here are other resources that you may find useful to learn more about React Native and Nx.

- **Blog post:** [Introducing React Native Support for Nx](https://blog.nrwl.io/introducing-react-native-support-for-nx-48d335e90c89) by Jack Hsu
- **Blog post:** [Step by Step Guide on Creating a Monorepo for React Native Apps using Nx](https://blog.nrwl.io/step-by-step-guide-on-creating-a-monorepo-for-react-native-apps-using-nx-704753b6c70e) by Eimly Xiong
23 changes: 12 additions & 11 deletions packages/expo/src/generators/application/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ import {
Tree,
} from '@nx/devkit';

import { runSymlink } from '../../utils/symlink-task';
import { addLinting } from '../../utils/add-linting';
import { addJest } from '../../utils/add-jest';
import { runSymlink } from '../../utils/symlink-task';

import { normalizeOptions } from './lib/normalize-options';
import initGenerator from '../init/init';
import { addProject } from './lib/add-project';
import { addDetox } from './lib/add-detox';
import { createApplicationFiles } from './lib/create-application-files';
import { addEasScripts } from './lib/add-eas-scripts';
import { addDetox } from './lib/add-detox';
import { Schema } from './schema';

export async function expoApplicationGenerator(
Expand All @@ -29,20 +29,21 @@ export async function expoApplicationGenerator(
addProject(host, options);

const initTask = await initGenerator(host, { ...options, skipFormat: true });
const lintTask = await addLinting(
host,
options.projectName,
options.appProjectRoot,
[joinPathFragments(options.appProjectRoot, 'tsconfig.app.json')],
options.linter,
options.setParserOptionsProject
);
const lintTask = await addLinting(host, {
...options,
projectRoot: options.appProjectRoot,
tsConfigPaths: [
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
],
});

const jestTask = await addJest(
host,
options.unitTestRunner,
options.projectName,
options.appProjectRoot,
options.js
options.js,
options.skipPackageJson
);
const detoxTask = await addDetox(host, options);
const symlinkTask = runSymlink(host.root, options.appProjectRoot);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "<%= offsetFromRoot %>tsconfig.base.json",
"extends": "<%= rootTsConfigPath %>",
"compilerOptions": {
"jsx": "react-native",
"allowJs": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/expo/src/generators/library/library.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ describe('lib', () => {
executor: '@nx/rollup:rollup',
outputs: ['{options.outputPath}'],
options: {
external: ['react/jsx-runtime', 'react-native'],
external: ['react/jsx-runtime', 'react-native', 'react', 'react-dom'],
entryFile: 'libs/my-lib/src/index.ts',
outputPath: 'dist/libs/my-lib',
project: 'libs/my-lib/package.json',
Expand Down
75 changes: 51 additions & 24 deletions packages/expo/src/generators/library/library.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
addProjectConfiguration,
convertNxGenerator,
ensurePackage,
formatFiles,
generateFiles,
GeneratorCallback,
Expand All @@ -15,12 +16,11 @@ import {
updateJson,
} from '@nx/devkit';

import { addTsConfigPath } from '@nx/js';

import { addTsConfigPath, getRelativePathToRootTsConfig } from '@nx/js';
import init from '../init/init';
import { addLinting } from '../../utils/add-linting';
import { addJest } from '../../utils/add-jest';

import { nxVersion } from '../../utils/versions';
import { NormalizedSchema, normalizeOptions } from './lib/normalize-options';
import { Schema } from './schema';

Expand All @@ -35,23 +35,44 @@ export async function expoLibraryGenerator(
);
}

addProject(host, options);
createFiles(host, options);
const tasks: GeneratorCallback[] = [];

const initTask = await init(host, {
...options,
skipFormat: true,
e2eTestRunner: 'none',
});
tasks.push(initTask);

const addProjectTask = await addProject(host, options);
if (addProjectTask) {
tasks.push(addProjectTask);
}

createFiles(host, options);

const lintTask = await addLinting(host, {
...options,
projectName: options.name,
tsConfigPaths: [
joinPathFragments(options.projectRoot, 'tsconfig.lib.json'),
],
});
tasks.push(lintTask);

const lintTask = await addLinting(
const jestTask = await addJest(
host,
options.unitTestRunner,
options.name,
options.projectRoot,
[joinPathFragments(options.projectRoot, 'tsconfig.lib.json')],
options.linter,
options.setParserOptionsProject
options.js,
options.skipPackageJson
);
tasks.push(jestTask);

if (options.publishable || options.buildable) {
updateLibPackageNpmScope(host, options);
}

if (!options.skipTsConfig) {
addTsConfigPath(host, options.importPath, [
Expand All @@ -63,31 +84,30 @@ export async function expoLibraryGenerator(
]);
}

const jestTask = await addJest(
host,
options.unitTestRunner,
options.name,
options.projectRoot,
options.js
);

if (options.publishable || options.buildable) {
updateLibPackageNpmScope(host, options);
}

if (!options.skipFormat) {
await formatFiles(host);
}

return runTasksInSerial(initTask, lintTask, jestTask);
return runTasksInSerial(...tasks);
}

function addProject(host: Tree, options: NormalizedSchema) {
async function addProject(host: Tree, options: NormalizedSchema) {
const targets: { [key: string]: TargetConfiguration } = {};

let task: GeneratorCallback;
if (options.publishable || options.buildable) {
const { rollupInitGenerator } = ensurePackage<typeof import('@nx/rollup')>(
'@nx/rollup',
nxVersion
);

const { libsDir } = getWorkspaceLayout(host);
const external = ['react/jsx-runtime', 'react-native'];
const external = [
'react/jsx-runtime',
'react-native',
'react',
'react-dom',
];

targets.build = {
executor: '@nx/rollup:rollup',
Expand All @@ -108,6 +128,7 @@ function addProject(host: Tree, options: NormalizedSchema) {
],
},
};
task = await rollupInitGenerator(host, { ...options, skipFormat: true });
}

addProjectConfiguration(host, options.name, {
Expand All @@ -117,6 +138,8 @@ function addProject(host: Tree, options: NormalizedSchema) {
tags: options.parsedTags,
targets,
});

return task;
}

function updateTsConfig(tree: Tree, options: NormalizedSchema) {
Expand Down Expand Up @@ -149,6 +172,10 @@ function createFiles(host: Tree, options: NormalizedSchema) {
...names(options.name),
tmpl: '',
offsetFromRoot: offsetFromRoot(options.projectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(
host,
options.projectRoot
),
}
);

Expand Down
7 changes: 5 additions & 2 deletions packages/expo/src/utils/add-jest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ export async function addJest(
unitTestRunner: 'jest' | 'none',
projectName: string,
appProjectRoot: string,
js: boolean
js: boolean,
skipPackageJson: boolean
) {
if (unitTestRunner !== 'jest') {
return () => {};
}

const jestTask = await jestProjectGenerator(host, {
js,
project: projectName,
supportTsx: true,
skipSerializers: true,
setupFile: 'none',
babelJest: true,
compiler: 'babel',
skipPackageJson,
skipFormat: true,
});

Expand Down
28 changes: 13 additions & 15 deletions packages/expo/src/utils/add-linting.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,26 @@ describe('Add Linting', () => {
});
});

it('should add update `project configuration` file properly when eslint is passed', () => {
addLinting(
tree,
'my-lib',
'libs/my-lib',
['libs/my-lib/tsconfig.lib.json'],
Linter.EsLint
);
it('should add update configuration when eslint is passed', () => {
addLinting(tree, {
projectName: 'my-lib',
linter: Linter.EsLint,
tsConfigPaths: ['libs/my-lib/tsconfig.lib.json'],
projectRoot: 'libs/my-lib',
});
const project = readProjectConfiguration(tree, 'my-lib');

expect(project.targets.lint).toBeDefined();
expect(project.targets.lint.executor).toEqual('@nx/linter:eslint');
});

it('should not add lint target when "none" is passed', async () => {
addLinting(
tree,
'my-lib',
'libs/my-lib',
['libs/my-lib/tsconfig.lib.json'],
Linter.None
);
addLinting(tree, {
projectName: 'my-lib',
linter: Linter.None,
tsConfigPaths: ['libs/my-lib/tsconfig.lib.json'],
projectRoot: 'libs/my-lib',
});
const project = readProjectConfiguration(tree, 'my-lib');

expect(project.targets.lint).toBeUndefined();
Expand Down
Loading

1 comment on commit 8347e61

@vercel
Copy link

@vercel vercel bot commented on 8347e61 May 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-dev-git-master-nrwl.vercel.app
nx.dev
nx-five.vercel.app
nx-dev-nrwl.vercel.app

Please sign in to comment.