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 6, 2024
1 parent 9769c29 commit 099e7bf
Show file tree
Hide file tree
Showing 15 changed files with 156 additions and 32 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
55 changes: 53 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,44 @@ import {
fileExists,
checkFilesExist,
runE2ETests,
updateFile,
} from 'e2e/utils';

describe('@nx/react-native', () => {
let proj: string;
let appName: string;
let libName: string;
let componentName: 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} --buildable --no-interactive --unitTestRunner=jest --linter=eslint`
);
componentName = uniq('Component');
runCLI(
`generate @nx/react-native:component ${libName}/src/lib/${componentName}/${componentName} --export --no-interactive`
);
updateFile(`${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 +127,38 @@ 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 build publishable library', async () => {
expect(() => {
runCLI(`build ${libName}`);
checkFilesExist(
`dist/${libName}/index.esm.js`,
`dist/${libName}/src/index.d.ts`
);
}).not.toThrow();
});

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/${componentName}/${componentName}.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 +186,7 @@ describe('@nx/react-native', () => {
`apps/${appName2}/.storybook/main.ts`,
`apps/${appName2}/src/app/App.stories.tsx`
);
runCLI(`build-storybook ${appName2}`);
checkFilesExist(`apps/${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`).
5 changes: 4 additions & 1 deletion packages/react-native/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ export const createNodesV2: CreateNodesV2<ReactNativePluginOptions> = [
'**/app.{json,config.js,config.ts}',
async (configFiles, options, context) => {
const optionsHash = hashObject(options);
const cachePath = join(workspaceDataDirectory, `expo-${optionsHash}.hash`);
const cachePath = join(
workspaceDataDirectory,
`react-native-${optionsHash}.hash`
);
const targetsCache = readTargetsCache(cachePath);

try {
Expand Down
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 @@ -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
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ import {
createProjectStorybookDir,
createStorybookTsconfigFile,
editTsconfigBaseJson,
findMetroConfig,
findNextConfig,
findViteConfig,
isUsingReactNative,
projectIsRootProjectInStandaloneWorkspace,
updateLintConfig,
} from './lib/util-functions';
Expand Down Expand Up @@ -82,7 +82,6 @@ export async function configurationGeneratorInternal(
const viteConfigFilePath = viteConfig?.fullConfigPath;
const viteConfigFileName = viteConfig?.viteConfigFileName;
const nextConfigFilePath = findNextConfig(tree, root);
const metroConfigFilePath = findMetroConfig(tree, root);

if (viteConfigFilePath) {
if (schema.uiFramework === '@storybook/react-webpack5') {
Expand Down Expand Up @@ -133,7 +132,7 @@ export async function configurationGeneratorInternal(

const usesVite =
!!viteConfigFilePath || schema.uiFramework?.endsWith('-vite');
const useReactNative = !!metroConfigFilePath;
const usesReactNative = isUsingReactNative(schema.project);

createProjectStorybookDir(
tree,
Expand All @@ -152,7 +151,7 @@ export async function configurationGeneratorInternal(
viteConfigFilePath,
hasPlugin,
viteConfigFileName,
useReactNative
usesReactNative
);

if (schema.uiFramework !== '@storybook/angular') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
joinPathFragments,
logger,
offsetFromRoot,
readCachedProjectGraph,
readJson,
readNxJson,
readProjectConfiguration,
Expand Down Expand Up @@ -577,7 +578,7 @@ export function createProjectStorybookDir(
viteConfigFilePath?: string,
hasPlugin?: boolean,
viteConfigFileName?: string,
useReactNative?: boolean
usesReactNative?: boolean
) {
let projectDirectory =
projectType === 'application'
Expand Down Expand Up @@ -622,7 +623,7 @@ export function createProjectStorybookDir(
viteConfigFilePath,
hasPlugin,
viteConfigFileName,
useReactNative,
usesReactNative,
});

if (js) {
Expand Down Expand Up @@ -739,14 +740,11 @@ export function findNextConfig(
}
}

export function findMetroConfig(
tree: Tree,
projectRoot: string
): string | undefined {
const nextConfigPath = joinPathFragments(projectRoot, `metro.config.js`);
if (tree.exists(nextConfigPath)) {
return nextConfigPath;
}
export function isUsingReactNative(projectName: string): boolean {
const projectGraph = readCachedProjectGraph();
return projectGraph.dependencies[projectName]?.some(
(dep) => dep.target === 'npm:react-native'
);
}

export function renameAndMoveOldTsConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const config: StorybookConfig = {
<% } %>
},
},
<% if (useReactNative && uiFramework === '@storybook/react-webpack5') { %>webpackFinal: async (config) => {
<% if (usesReactNative && uiFramework === '@storybook/react-webpack5') { %>webpackFinal: async (config) => {
if (config.resolve) {
config.resolve.alias = {
...config.resolve.alias,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const config = {
<% } %>
},
},
<% if (useReactNative && uiFramework === '@storybook/react-webpack5') { %>webpackFinal: async (config) => {
<% if (usesReactNative && uiFramework === '@storybook/react-webpack5') { %>webpackFinal: async (config) => {
if (config.resolve) {
config.resolve.alias = {
...config.resolve.alias,
Expand All @@ -43,6 +43,21 @@ const config = {
},<% } %><% if (usesVite && !viteConfigFilePath) { %>
viteFinal: async (config) =>
mergeConfig(config, {
<% if (usesReactNative) { %>define: {
global: 'window',
},
resolve: {
extensions: [
'.web.tsx',
'.web.ts',
'.web.jsx',
'.web.js',
...(config.resolve.extensions ?? []),
],
alias: {
'react-native': 'react-native-web',
},
},<% } %>
plugins: [<% if(uiFramework === '@storybook/vue3-vite') { %>vue(), <% } %>nxViteTsPaths()],
}),
<% } %>
Expand Down
7 changes: 1 addition & 6 deletions packages/storybook/src/utils/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
TargetConfiguration,
Tree,
readNxJson,
updateNxJson,
} from '@nx/devkit';
import { TargetConfiguration, Tree } from '@nx/devkit';
import { CompilerOptions } from 'typescript';
import { statSync } from 'fs';
import { findNodes } from '@nx/js';
Expand Down

0 comments on commit 099e7bf

Please sign in to comment.