Skip to content

Commit

Permalink
fix(react-native): fix react native storybook for lib
Browse files Browse the repository at this point in the history
  • Loading branch information
xiongemi committed Dec 5, 2024
1 parent 4773e35 commit 9070a82
Show file tree
Hide file tree
Showing 25 changed files with 208 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
"type": "string",
"enum": ["vite", "webpack"],
"x-prompt": "Which bundler do you want to use to build the application?",
"default": "webpack",
"default": "vite",
"x-priority": "important"
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
}
},
"required": ["project"],
"examplesFile": "This generator will set up Storybook for your **React Native** project.\n\n```bash\nnx g @nx/react-native:storybook-configuration project-name\n```\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/react/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/react/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/recipes/storybook/storybook-interaction-tests#setup-storybook-interaction-tests)..\n- Whether you want to `generateStories` for the components in your project. If you choose `yes`, a `.stories.ts` file will be generated next to each of your components in your project.\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/react/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Generate Storybook configuration\n\n```bash\nnx g @nx/react-native:storybook-configuration ui\n```\n\nThis will generate Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory, eg. `.storybook/main.ts`).\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/react-native:storybook-configuration ui --generateStories=true --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*,apps/my-app/**/*.something.ts\n```\n\nThis will generate a Storybook configuration for the `ui` project and generate stories for all components in the `libs/ui/src/lib` directory, except for the ones in the `libs/ui/src/not-stories` directory, and the ones in the `apps/my-app` directory that end with `.something.ts`, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n\n### Generate stories using JavaScript instead of TypeScript\n\n```bash\nnx g @nx/react-native:storybook-configuration ui --generateStories=true --js=true\n```\n\nThis will generate stories for all the components in the `ui` project using JavaScript instead of TypeScript. So, you will have `.stories.js` files next to your components.\n\n### Generate Storybook configuration using JavaScript\n\n```bash\nnx g @nx/react-native:storybook-configuration ui --tsConfiguration=false\n```\n\nBy default, our generator generates TypeScript Storybook configuration files. You can choose to use JavaScript for the Storybook configuration files of your project (the files inside the `.storybook` directory, eg. `.storybook/main.js`).\n",
"presets": []
},
"description": "Set up Storybook for a React Native application or library.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"type": "string",
"enum": ["vite", "webpack"],
"x-prompt": "Which bundler do you want to use to build the application?",
"default": "webpack",
"default": "vite",
"x-priority": "important"
}
},
Expand Down
44 changes: 42 additions & 2 deletions e2e/react-native/src/react-native.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,43 @@ import {
fileExists,
checkFilesExist,
runE2ETests,
updateFile,
} from 'e2e/utils';

describe('@nx/react-native', () => {
let proj: string;
let appName: string;
let libName: string;

beforeAll(() => {
newProject();
proj = newProject();
appName = uniq('app');
runCLI(
`generate @nx/react-native:app ${appName} --install=false --no-interactive --unitTestRunner=jest --linter=eslint`
);
libName = uniq('lib');
runCLI(
`generate @nx/react-native:lib ${libName} --no-interactive --unitTestRunner=jest --linter=eslint`
);
const componentName = uniq('Component');
runCLI(
`generate @nx/react-native:component libs/${libName}/src/lib/${componentName}/${componentName} --export --no-interactive`
);
updateFile(`apps/${appName}/src/app/App.tsx`, (content) => {
let updated = `// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport {${componentName}} from '${proj}/${libName}';\n${content}`;
return updated;
});
});

afterAll(() => cleanupProject());

it('should test and lint', async () => {
expect(() => runCLI(`test ${appName}`)).not.toThrow();
expect(() => runCLI(`test ${libName}`)).not.toThrow();
expect(() => runCLI(`lint ${appName}`)).not.toThrow();
expect(() => runCLI(`lint ${libName}`)).not.toThrow();
});

it('should bundle the app', async () => {
expect(() =>
runCLI(
Expand Down Expand Up @@ -104,12 +126,28 @@ describe('@nx/react-native', () => {

it('should create storybook with application', async () => {
runCLI(
`generate @nx/react:storybook-configuration ${appName} --generateStories --no-interactive`
`generate @nx/react-native:storybook-configuration ${appName} --generateStories --no-interactive`
);
checkFilesExist(
`${appName}/.storybook/main.ts`,
`${appName}/src/app/App.stories.tsx`
);

runCLI(`build-storybook ${appName}`);
checkFilesExist(`${appName}/storybook-static/index.html`);
});

it('should create storybook with library', async () => {
runCLI(
`generate @nx/react-native:storybook-configuration ${libName} --generateStories --no-interactive`
);
checkFilesExist(
`${libName}/.storybook/main.ts`,
`${libName}/src/app/App.stories.tsx`
);

runCLI(`build-storybook ${libName}`);
checkFilesExist(`${libName}/storybook-static/index.html`);
});

it('should run build with vite bundler and e2e with playwright', async () => {
Expand Down Expand Up @@ -137,5 +175,7 @@ describe('@nx/react-native', () => {
`apps/${appName2}/.storybook/main.ts`,
`apps/${appName2}/src/app/App.stories.tsx`
);
runCLI(`build-storybook ${appName2}`);
checkFilesExist(`${appName2}/storybook-static/index.html`);
});
});
61 changes: 61 additions & 0 deletions packages/react-native/docs/storybook-configuration-examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
This generator will set up Storybook for your **React Native** project.

```bash
nx g @nx/react-native:storybook-configuration project-name
```

When running this generator, you will be prompted to provide the following:

- The `name` of the project you want to generate the configuration for.
- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/react/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/react/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/recipes/storybook/storybook-interaction-tests#setup-storybook-interaction-tests)..
- Whether you want to `generateStories` for the components in your project. If you choose `yes`, a `.stories.ts` file will be generated next to each of your components in your project.

You must provide a `name` for the generator to work.

By default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/react/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.

There are a number of other options available. Let's take a look at some examples.

## Examples

### Generate Storybook configuration

```bash
nx g @nx/react-native:storybook-configuration ui
```

This will generate Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory, eg. `.storybook/main.ts`).

### Ignore certain paths when generating stories

```bash
nx g @nx/react-native:storybook-configuration ui --generateStories=true --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*,apps/my-app/**/*.something.ts
```

This will generate a Storybook configuration for the `ui` project and generate stories for all components in the `libs/ui/src/lib` directory, except for the ones in the `libs/ui/src/not-stories` directory, and the ones in the `apps/my-app` directory that end with `.something.ts`, and also for components that their file name is of the pattern `*.other.*`.

This is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.

By default, Nx will ignore the following paths:

```text
*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx
```

but you can change this behaviour easily, as explained above.

### Generate stories using JavaScript instead of TypeScript

```bash
nx g @nx/react-native:storybook-configuration ui --generateStories=true --js=true
```

This will generate stories for all the components in the `ui` project using JavaScript instead of TypeScript. So, you will have `.stories.js` files next to your components.

### Generate Storybook configuration using JavaScript

```bash
nx g @nx/react-native:storybook-configuration ui --tsConfiguration=false
```

By default, our generator generates TypeScript Storybook configuration files. You can choose to use JavaScript for the Storybook configuration files of your project (the files inside the `.storybook` directory, eg. `.storybook/main.js`).
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
"type": "string",
"enum": ["vite", "webpack"],
"x-prompt": "Which bundler do you want to use to build the application?",
"default": "webpack",
"default": "vite",
"x-priority": "important"
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { Tree, logger, readNxJson } from '@nx/devkit';
import {
GeneratorCallback,
Tree,
joinPathFragments,
readNxJson,
readProjectConfiguration,
runTasksInSerial,
} from '@nx/devkit';
import { storybookConfigurationGenerator as reactStorybookConfigurationGenerator } from '@nx/react';
import { StorybookConfigureSchema } from './schema';
import webConfigurationGenerator from '../web-configuration/web-configuration';

export function storybookConfigurationGenerator(
tree: Tree,
Expand All @@ -12,24 +20,59 @@ export function storybookConfigurationGenerator(
});
}

/**
* This would be a direct pass through to @nx/react:storybook-configuration generator.
* @TODO (@xiongemi): remove this generator for v19
*/
export async function storybookConfigurationGeneratorInternal(
host: Tree,
schema: StorybookConfigureSchema
) {
logger.warn(
`Please run 'nx run @nx/react:storybook-configuration ${schema.project}' instead.`
);
const nxJson = readNxJson(host);
const addPluginDefault =
process.env.NX_ADD_PLUGINS !== 'false' &&
nxJson.useInferencePlugins !== false;
schema.addPlugin ??= addPluginDefault;

return reactStorybookConfigurationGenerator(host, schema);
const projectConfig = readProjectConfiguration(host, schema.project);
const projectType =
schema.projectType ?? projectConfig.projectType ?? 'library';
const foundViteConfig = findConfig(host, 'vite.config', projectConfig.root);
const foundWebpackConfig = findConfig(
host,
'webpack.config',
projectConfig.root
);

const tasks: GeneratorCallback[] = [];
if (!foundViteConfig && !foundWebpackConfig) {
const webTask = await webConfigurationGenerator(host, {
...schema,
bundler: schema.bundler ?? 'vite',
projectType,
skipFormat: true,
});
tasks.push(webTask);
}

tasks.push(await reactStorybookConfigurationGenerator(host, schema));

return runTasksInSerial(...tasks);
}

export function findConfig(
tree: Tree,
configFileName: string,
projectRoot: string
): boolean {
const allowsExt = ['js', 'mjs', 'ts', 'cjs', 'mts', 'cts'];

for (const ext of allowsExt) {
const configPath = joinPathFragments(
projectRoot,
`${configFileName}.${ext}`
);
if (tree.exists(configPath)) {
return true;
}
}
return false;
}

export default storybookConfigurationGenerator;
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ export interface StorybookConfigureSchema {
ignorePaths?: string[];
configureStaticServe?: boolean;
addPlugin?: boolean;
bundler?: 'webpack' | 'vite';
projectType?: 'application' | 'library';
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,6 @@
]
}
},
"required": ["project"]
"required": ["project"],
"examplesFile": "../../../docs/storybook-configuration-examples.md"
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const rollupPlugin = (matchers: RegExp[]) => ({

export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/<%= fileName %>',
cacheDir: '<%= offsetFromRoot %>/node_modules/.vite/<%= projectRoot %>',
define: {
global: 'window',
},
Expand All @@ -43,7 +43,7 @@ export default defineConfig({
build: {
reportCompressedSize: true,
commonjsOptions: { transformMixedEsModules: true },
outDir: '../../dist/apps/<%= fileName %>/web',
outDir: '<%= offsetFromRoot %>/dist/<%= projectRoot %>/web',
rollupOptions: {
plugins: [rollupPlugin([/react-native-vector-icons/])],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { Tree, getProjects, names, offsetFromRoot } from '@nx/devkit';
import {
ProjectConfiguration,
Tree,
getProjects,
names,
offsetFromRoot,
} from '@nx/devkit';
import { WebConfigurationGeneratorSchema } from '../schema';

export interface NormalizedSchema extends WebConfigurationGeneratorSchema {
Expand All @@ -8,16 +14,18 @@ export interface NormalizedSchema extends WebConfigurationGeneratorSchema {
}

export function normalizeSchema(
tree: Tree,
schema: WebConfigurationGeneratorSchema
schema: WebConfigurationGeneratorSchema,
projectConfig: ProjectConfiguration
) {
const project = getProjects(tree).get(schema.project);
const { fileName, className } = names(schema.project);
const projectType =
schema.projectType ?? projectConfig.projectType ?? 'library';
return {
...schema,
projectRoot: project.root,
offsetFromRoot: offsetFromRoot(project.root),
projectRoot: projectConfig.root,
offsetFromRoot: offsetFromRoot(projectConfig.root),
fileName,
className,
projectType,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ export interface WebConfigurationGeneratorSchema {
bundler: 'vite' | 'webpack';
skipFormat?: boolean;
skipPackageJson?: boolean; //default is false
projectType?: 'application' | 'library';
addPlugin?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"type": "string",
"enum": ["vite", "webpack"],
"x-prompt": "Which bundler do you want to use to build the application?",
"default": "webpack",
"default": "vite",
"x-priority": "important"
}
},
Expand Down
Loading

0 comments on commit 9070a82

Please sign in to comment.