fix(storybook): cleanup v17 tasks (#19560)
This commit is contained in:
parent
5a30beff77
commit
1c08c8b2f9
@ -19,7 +19,7 @@ Storybook helps you test your UIs. You can read more about testing with Storyboo
|
||||
|
||||
### Documentation
|
||||
|
||||
Storybook helps you document your UI elements, or your design system, effectively and in an interactive way. You can read more in the [How-to document components](https://storybook.js.org/docs/react/writing-docs/introduction) documentation page. Essentially, you can use Storybook to publish a catalog of your components. A catalog that you can share with the design team, the developer team, the product team, anyone else in the product development process, or even the client. The components are isolated, interactive, and can be represented in all possible forms that they can take (e.g. for a button: enabled, disabled, active, etc). You can read more about publishing your Storybook in the [Publish Storybook](https://storybook.js.org/docs/react/sharing/publish-storybook) documentation page.
|
||||
Storybook helps you document your UI elements, or your design system, effectively and in an interactive way. You can read more in the [How to document components](https://storybook.js.org/docs/react/writing-docs/introduction) documentation page. Essentially, you can use Storybook to publish a catalog of your components. A catalog that you can share with the design team, the developer team, the product team, anyone else in the product development process, or even the client. The components are isolated, interactive, and can be represented in all possible forms that they can take (e.g. for a button: enabled, disabled, active, etc). You can read more about publishing your Storybook in the [Publish Storybook](https://storybook.js.org/docs/react/sharing/publish-storybook) documentation page.
|
||||
|
||||
## Nx and Storybook
|
||||
|
||||
@ -42,38 +42,34 @@ If your project is not configured yet, check out one of these guides:
|
||||
- [Set up Storybook for React Projects](/recipes/storybook/overview-react)
|
||||
|
||||
- [Set up Storybook for Angular Projects](/recipes/storybook/overview-angular)
|
||||
-
|
||||
- [Set up Storybook for Vue Projects](/recipes/storybook/overview-vue)
|
||||
|
||||
If your project is [already configured](/nx-api/storybook), you can use the `stories` generator:
|
||||
|
||||
- [React stories generator](/nx-api/react/generators/stories)
|
||||
- [React (and Next.js) stories generator](/nx-api/react/generators/stories)
|
||||
|
||||
- [React Native stories generator](/nx-api/react-native/generators/stories)
|
||||
|
||||
- [Vue stories generator](/nx-api/vue/generators/stories)
|
||||
|
||||
- [Angular stories generator](/nx-api/angular/generators/stories)
|
||||
|
||||
The stories generator will read your inputs (if you’re using Angular), or your props (if you're using React), and will generate stories with the corresponding arguments/controls already prefilled.
|
||||
|
||||
#### Cypress tests generation
|
||||
#### Storybook interaction tests
|
||||
|
||||
Nx also generates Cypress tests for your components, that point to the corresponding component’s story. You can read more about how the Cypress tests are generated and how they look like in the [storybook-configuration generator documentation](/recipes/storybook/overview-react#cypress-tests-for-stories).
|
||||
[Storybook interaction tests](https://storybook.js.org/docs/react/writing-tests/interaction-testing) allow you to test user interactions within your Storybook stories. It enhances your [Storybook](https://storybook.js.org/) setup, ensuring that not only do your components look right, but they also work correctly when interacted with.
|
||||
|
||||
Take a look at the generated code of the Cypress test file, specifically at the URL which Cypress visits:
|
||||
|
||||
```javascript
|
||||
cy.visit(
|
||||
'/iframe.html?id=buttoncomponent--primary&args=text:Click+me!;padding;style:default'
|
||||
);
|
||||
```
|
||||
|
||||
Cypress visits the URL that hosts the story of the component we are testing, adding values to its controls (eg. `args=text:Click+me!`). Then, the test attempts to validate that the values are correctly applied.
|
||||
Nx will generate interaction tests for your stories. You can read more in our [Setting up Storybook Interaction Tests with Nx guide](/recipes/storybook/storybook-interaction-tests).
|
||||
|
||||
### CI/CD tools
|
||||
|
||||
Now let’s see how Nx helps in the CI/CD journey, as well.
|
||||
|
||||
#### Cypress testing
|
||||
#### Interaction tests in your CI
|
||||
|
||||
When you are running the Cypress tests for a project, Cypress will start the Storybook server of that project. The Storybook server will fire up a Storybook instance, hosting all the components's stories for that project. The e2e tests will then run, which actually visit the stories and perform the tests there. Cypress will be configured to start and stop the Storybook server. The results will be cached, and they will go through the Nx graph, meaning that Nx will know if the tests need to be run again or not, depending on the affected status of your project.
|
||||
You can set up your interaction tests to run as part of your CI. You can read more in the [Storybook docs](https://storybook.js.org/docs/react/writing-tests/test-runner#set-up-ci-to-run-**tests**).
|
||||
|
||||
#### Serve
|
||||
|
||||
@ -97,7 +93,7 @@ Setting up Storybook on Nx reflects - and takes advantage of - the [mental model
|
||||
|
||||
##### Development and debugging
|
||||
|
||||
In the process of setting up Storybook in your Nx workspace that we described above, you end up with one Storybook instance per project. That way, you can use your project’s Storybook targets to serve and build Storybook:
|
||||
In the process of setting up Storybook in your Nx workspace that we described above, you end up with one Storybook instance per project. That way, you can use your project’s Storybook targets to serve, test and build Storybook:
|
||||
|
||||
```shell
|
||||
nx storybook my-project
|
||||
@ -109,11 +105,13 @@ and
|
||||
nx build-storybook my-project
|
||||
```
|
||||
|
||||
This feature is extremely useful when developing locally. The containerized stories in your Storybook are the only ones that are built/served when you want to debug just one component, or just one library. You don’t have to wait for a huge Storybook containing all your stories in your repository to fire up. You just need to wait for the Storybook of a single project to start. This speeds up the process.
|
||||
and
|
||||
|
||||
##### E2e tests with Cypress
|
||||
```shell
|
||||
nx test-storybook my-project
|
||||
```
|
||||
|
||||
If you’re using Cypress, and you’re taking advantage of the generated Cypress tests that our Storybook generators generate, then your e2e tests are also going to be much faster. When you run your e2e tests for a particular project, Cypress is only going to start the specific Storybook instance, and it’s going to take much less time than having to start an all-including universal Storybook.
|
||||
This feature is extremely useful when developing locally. The containerized stories in your Storybook are the only ones that are built/served/tested when you want to debug just one component, or just one library. You don’t have to wait for a huge Storybook containing all your stories in your repository to fire up. You just need to wait for the Storybook of a single project to start. This speeds up the process.
|
||||
|
||||
##### Caching, affected, dependency management
|
||||
|
||||
|
||||
@ -7,10 +7,6 @@ description: This is an overview page for the Storybook plugin in Nx. It explain
|
||||
|
||||
This guide will briefly walk you through using Storybook within an Nx workspace.
|
||||
|
||||
{% callout type="info" title="Storybook 7 by default" %}
|
||||
Starting with Nx 16, Storybook 7 is used by default to configure your projects.
|
||||
{% /callout %}
|
||||
|
||||
## Setting Up Storybook
|
||||
|
||||
### Add the Storybook plugin
|
||||
@ -86,7 +82,7 @@ nx g @nx/react-native:storybook-configuration my-react-native-project
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
These framework-specific generators will also **generate stories** for you.
|
||||
These framework-specific generators will also **generate stories** and interaction tests for you.
|
||||
|
||||
If you are NOT using a framework-specific generator (for [Angular](/nx-api/angular/generators/storybook-configuration), [React](/nx-api/react/generators/storybook-configuration), [React Native](/nx-api/react-native/generators/storybook-configuration), [Vue](/nx-api/vue/generators/storybook-configuration)), in the field `uiFramework` you must choose one of the following Storybook frameworks:
|
||||
|
||||
@ -113,9 +109,7 @@ Choosing one of these frameworks will have the following effects on your workspa
|
||||
|
||||
2. Nx will generate a project-level `.storybook` folder (located under `libs/your-project/.storybook` or `apps/your-project/.storybook`) containing the essential configuration files for Storybook.
|
||||
|
||||
3. Nx will create new `targets` in your project's `project.json`, called `storybook` and `build-storybook`, containing all the necessary configuration to serve and build Storybook.
|
||||
|
||||
4. Nx will generate a new Cypress e2e app for your project (if there isn't one already) to run against the Storybook instance.
|
||||
3. Nx will create new `targets` in your project's `project.json`, called `storybook`, `test-storybook` and `build-storybook`, containing all the necessary configuration to serve, test and build Storybook.
|
||||
|
||||
Make sure to **use the framework-specific generators** if your project is using Angular, React, Next.js or React Native: [`@nx/angular:storybook-configuration`](/nx-api/angular/generators/storybook-configuration), [`@nx/react:storybook-configuration`](/nx-api/react/generators/storybook-configuration), [`@nx/react-native:storybook-configuration`](/nx-api/react-native/generators/storybook-configuration), as shown above.
|
||||
|
||||
@ -147,6 +141,20 @@ or
|
||||
nx build-storybook project-name
|
||||
```
|
||||
|
||||
### Testing Storybook
|
||||
|
||||
With the Storybook server running, you can test Storybook (run all the interaction tests) using this command:
|
||||
|
||||
```shell
|
||||
nx run project-name:test-storybook
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```shell
|
||||
nx test-storybook project-name
|
||||
```
|
||||
|
||||
### Anatomy of the Storybook setup
|
||||
|
||||
When running the Nx Storybook generator, it'll configure the Nx workspace to be able to run Storybook seamlessly. It'll create a project specific Storybook configuration.
|
||||
@ -171,17 +179,21 @@ To register a [Storybook addon](https://storybook.js.org/addons/) for all Storyb
|
||||
|
||||
1. In your project's `.storybook/main.ts` file, in the `addons` array of the `module.exports` object, add the new addon:
|
||||
|
||||
```typescript {% fileName="<project-path>/.storybook/main.js" %}
|
||||
module.exports = {
|
||||
stories: [...],
|
||||
...,
|
||||
addons: [..., '@storybook/addon-essentials'],
|
||||
};
|
||||
```
|
||||
```typescript {% fileName="<project-path>/.storybook/main.ts" %}
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
...
|
||||
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions', ...],
|
||||
...
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
2. If a decorator is required, in each project's `<project-path>/.storybook/preview.ts`, you can export an array called `decorators`.
|
||||
|
||||
```typescript {% fileName="<project-path>/.storybook/preview.js" %}
|
||||
```typescript {% fileName="<project-path>/.storybook/preview.ts" %}
|
||||
import someDecorator from 'some-storybook-addon';
|
||||
export const decorators = [someDecorator];
|
||||
```
|
||||
|
||||
@ -89,19 +89,6 @@
|
||||
"type": "boolean",
|
||||
"description": "Starts Storybook in documentation mode. Learn more about it : https://storybook.js.org/docs/react/writing-docs/build-documentation#preview-storybooks-documentation."
|
||||
},
|
||||
"uiFramework": {
|
||||
"type": "string",
|
||||
"description": "Storybook framework npm package.",
|
||||
"enum": [
|
||||
"@storybook/react",
|
||||
"@storybook/html",
|
||||
"@storybook/web-components",
|
||||
"@storybook/vue",
|
||||
"@storybook/vue3",
|
||||
"@storybook/svelte"
|
||||
],
|
||||
"x-deprecated": "Upgrade to Storybook 7."
|
||||
},
|
||||
"webpackStatsJson": {
|
||||
"type": ["boolean", "string"],
|
||||
"description": "Write Webpack Stats JSON to disk.",
|
||||
@ -147,7 +134,7 @@
|
||||
},
|
||||
"additionalProperties": true,
|
||||
"required": ["configDir"],
|
||||
"examplesFile": "---\ntitle: Storybook builder executor examples\ndescription: This page contains examples for the @nx/storybook:build executor.\n---\n\n`project.json`:\n\n```json\n//...\n\"ui\": {\n \"targets\": {\n //...\n \"build-storybook\": {\n \"executor\": \"@nx/storybook:build\",\n \"outputs\": [\"{options.outputDir}\"],\n \"options\": {\n \"outputDir\": \"dist/storybook/ui\",\n \"configDir\": \"libs/ui/.storybook\"\n },\n \"configurations\": {\n \"ci\": {\n \"quiet\": true\n }\n }\n }\n}\n```\n\n```bash\nnx run ui:build-storybook\n```\n\n## Examples\n\n### For non-Angular projects\n\n{% tabs %}\n{% tab label=\"Working in docsMode\" %}\n\nYou can work in docs mode, building a documentation-only site, by setting the `docsMode` option to `true` and using the `@storybook/addon-docs` addon.\n\nRead more on the [Storybook documentation page for `addon-docs`](https://storybook.js.org/addons/@storybook/addon-docs).\n\n```json\n\"storybook\": {\n \"executor\": \"@nx/storybook:build\",\n \"options\": {\n \"port\": 4400,\n \"configDir\": \"libs/ui/.storybook\",\n \"docsMode\": true\n },\n \"configurations\": {\n \"ci\": {\n \"quiet\": true\n }\n }\n}\n```\n\n{% /tab %}\n\n{% /tabs %}\n\n### For Angular projects\n\n{% tabs %}\n{% tab label=\"Default configuration\" %}\n\nThis is the default configuration for Angular projects using Storybook. You can see that it uses the native `@storybook/angular:build-storybook` executor. You can read more about the configuration options at the relevant [Storybook documentation page](https://storybook.js.org/docs/angular/get-started/install).\n\n```json\n\"build-storybook\": {\n \"executor\": \"@storybook/angular:build-storybook\",\n \"outputs\": [\"{options.outputDir}\"],\n \"options\": {\n \"outputDir\": \"dist/storybook/ngapp\",\n \"configDir\": \"libs/ui/.storybook\",\n \"browserTarget\": \"ui:build\",\n \"compodoc\": false\n },\n \"configurations\": {\n \"ci\": {\n \"quiet\": true\n }\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Changing the browserTarget\" %}\n\nYou can set the [`browserTarget`](/deprecated/storybook/angular-browser-target) to use `build-storybook` as the builder. This is most useful in the cases where your project does not have a `build` target.\n\n```json\n\"build-storybook\": {\n \"executor\": \"@storybook/angular:build-storybook\",\n \"outputs\": [\"{options.outputDir}\"],\n \"options\": {\n \"outputDir\": \"dist/storybook/ngapp\",\n \"configDir\": \"libs/ui/.storybook\",\n \"browserTarget\": \"ui:build-storybook\",\n \"compodoc\": false\n },\n \"configurations\": {\n \"ci\": {\n \"quiet\": true\n }\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"Adding styles\" %}\n\nYou can add paths to stylesheets to be included in the Storybook build by using the `styles` array. You can also add `stylePreprocessorOptions`, much like you would do in the Angular builder. You can read more in our guide about [styles and preprocessor options for Storybook](/recipes/storybook/angular-configuring-styles).\n\n```json\n\"build-storybook\": {\n \"executor\": \"@storybook/angular:build-storybook\",\n \"outputs\": [\"{options.outputDir}\"],\n \"options\": {\n \"outputDir\": \"dist/storybook/ngapp\",\n \"configDir\": \"libs/ui/.storybook\",\n \"browserTarget\": \"ui:build-storybook\",\n \"compodoc\": false,\n \"styles\": [\"some-styles.css\"],\n \"stylePreprocessorOptions\": {\n \"includePaths\": [\"some-style-paths\"]\n }\n },\n \"configurations\": {\n \"ci\": {\n \"quiet\": true\n }\n }\n}\n```\n\n{% /tab %}\n\n{% /tabs %}\n"
|
||||
"examplesFile": "---\ntitle: Storybook builder executor examples\ndescription: This page contains examples for the @nx/storybook:build executor.\n---\n\n`project.json`:\n\n```json\n//...\n\"ui\": {\n \"targets\": {\n //...\n \"build-storybook\": {\n \"executor\": \"@nx/storybook:build\",\n \"outputs\": [\"{options.outputDir}\"],\n \"options\": {\n \"outputDir\": \"dist/storybook/ui\",\n \"configDir\": \"libs/ui/.storybook\"\n },\n \"configurations\": {\n \"ci\": {\n \"quiet\": true\n }\n }\n }\n}\n```\n\n```bash\nnx run ui:build-storybook\n```\n\n## Examples\n\n### For non-Angular projects\n\n{% tabs %}\n{% tab label=\"Working in docsMode\" %}\n\nYou can work in docs mode, building a documentation-only site, by setting the `docsMode` option to `true` and using the `@storybook/addon-docs` addon.\n\nRead more on the [Storybook documentation page for `addon-docs`](https://storybook.js.org/addons/@storybook/addon-docs).\n\n```json\n\"storybook\": {\n \"executor\": \"@nx/storybook:build\",\n \"options\": {\n \"port\": 4400,\n \"configDir\": \"libs/ui/.storybook\",\n \"docsMode\": true\n },\n \"configurations\": {\n \"ci\": {\n \"quiet\": true\n }\n }\n}\n```\n\n{% /tab %}\n\n{% /tabs %}\n\n### For Angular projects\n\n{% tabs %}\n{% tab label=\"Default configuration\" %}\n\nThis is the default configuration for Angular projects using Storybook. You can see that it uses the native `@storybook/angular:build-storybook` executor. You can read more about the configuration options at the relevant [Storybook documentation page](https://storybook.js.org/docs/angular/get-started/install).\n\n```json\n\"build-storybook\": {\n \"executor\": \"@storybook/angular:build-storybook\",\n \"outputs\": [\"{options.outputDir}\"],\n \"options\": {\n \"outputDir\": \"dist/storybook/ngapp\",\n \"configDir\": \"apps/ngapp/.storybook\",\n \"browserTarget\": \"ngapp:build\",\n \"compodoc\": false\n },\n \"configurations\": {\n \"ci\": {\n \"quiet\": true\n }\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Changing the browserTarget\" %}\n\nYou can set the [`browserTarget`](/deprecated/storybook/angular-browser-target) to use `build-storybook` as the builder. This is most useful in the cases where your project does not have a `build` target.\n\n```json\n\"build-storybook\": {\n \"executor\": \"@storybook/angular:build-storybook\",\n \"outputs\": [\"{options.outputDir}\"],\n \"options\": {\n \"outputDir\": \"dist/storybook/ngapp\",\n \"configDir\": \"apps/ngapp/.storybook\",\n \"browserTarget\": \"ngapp:build-storybook\",\n \"compodoc\": false\n },\n \"configurations\": {\n \"ci\": {\n \"quiet\": true\n }\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"Adding styles\" %}\n\nYou can add paths to stylesheets to be included in the Storybook build by using the `styles` array. You can also add `stylePreprocessorOptions`, much like you would do in the Angular builder. You can read more in our guide about [styles and preprocessor options for Storybook](/recipes/storybook/angular-configuring-styles).\n\n```json\n\"build-storybook\": {\n \"executor\": \"@storybook/angular:build-storybook\",\n \"outputs\": [\"{options.outputDir}\"],\n \"options\": {\n \"outputDir\": \"dist/storybook/ngapp\",\n \"configDir\": \"apps/ngapp/.storybook\",\n \"browserTarget\": \"ngapp:build-storybook\",\n \"compodoc\": false,\n \"styles\": [\"some-styles.css\"],\n \"stylePreprocessorOptions\": {\n \"includePaths\": [\"some-style-paths\"]\n }\n },\n \"configurations\": {\n \"ci\": {\n \"quiet\": true\n }\n }\n}\n```\n\n{% /tab %}\n\n{% /tabs %}\n"
|
||||
},
|
||||
"description": "Build Storybook.",
|
||||
"aliases": [],
|
||||
|
||||
@ -104,7 +104,7 @@
|
||||
}
|
||||
},
|
||||
"required": ["name", "uiFramework"],
|
||||
"examplesFile": "---\ntitle: Storybook configuration generator examples\ndescription: This page contains examples for the @nx/storybook:configuration generator.\n---\n\nThis is a framework-agnostic generator for setting up Storybook configuration for a project.\n\n```bash\nnx g @nx/storybook:configuration\n```\n\nStarting Nx 16, Nx does not support Storybook v6 any more. So, Nx will configure your project to use Storybook v7. If you are not on Storybook 7 yet, please migrate. You can read more about how to migrate to Storybook 7 in our [Storybook 7 migration generator](/packages/storybook/generators/migrate-7) guide.\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- The `uiFramework` you want to use. Supported values are:\n - `@storybook/angular`\n - `@storybook/html-webpack5`\n - `@storybook/nextjs`\n - `@storybook/preact-webpack5`\n - `@storybook/react-webpack5`\n - `@storybook/react-vite`\n - `@storybook/server-webpack5`\n - `@storybook/svelte-webpack5`\n - `@storybook/svelte-vite`\n - `@storybook/sveltekit`\n - `@storybook/vue-webpack5`\n - `@storybook/vue-vite`\n - `@storybook/vue3-webpack5`\n - `@storybook/vue3-vite`\n - `@storybook/web-components-webpack5`\n - `@storybook/web-components-vite`\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, 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/angular/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests).\n\nYou must provide a `name` and a `uiFramework` for the generator to work.\n\nYou can read more about how this generator works, in the [Storybook package overview page](/packages/storybook#generating-storybook-configuration).\n\nIf you are using Angular, React, React Native or Next.js in your project, it's best to use the framework specific generator:\n\n- [React Storybook Configuration Generator](/nx-api/react/generators/storybook-configuration) (React and Next.js projects)\n\n- [Angular Storybook Configuration Generator](/nx-api/angular/generators/storybook-configuration)\n\n- [React Native Storybook Configuration Generator](/nx-api/react-native/generators/storybook-configuration)\n\n## Examples\n\n### Generate Storybook configuration using JavaScript\n\n```bash\nnx g @nx/storybook:configuration ui --uiFramework=@storybook/web-components-vite --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",
|
||||
"examplesFile": "---\ntitle: Storybook configuration generator examples\ndescription: This page contains examples for the @nx/storybook:configuration generator.\n---\n\nThis is a framework-agnostic generator for setting up Storybook configuration for a project.\n\n```bash\nnx g @nx/storybook:configuration\n```\n\n{% callout type=\"info\" title=\"Nx uses Storybook 7\" %}\nNx does not support Storybook v6 any more. So, Nx will configure your project to use Storybook v7. If you are not on Storybook 7 yet, please migrate. Please follow our [Storybook 7 migration generator](/packages/storybook/generators/migrate-7) guide.\n{% /callout %}\n\nIf you are using Angular, React, Next.js, Vue or React Native in your project, it's best to use the framework specific Storybook configuration generator:\n\n- [React Storybook Configuration Generator](/nx-api/react/generators/storybook-configuration) (React and Next.js projects)\n\n- [Angular Storybook Configuration Generator](/nx-api/angular/generators/storybook-configuration)\n\n- [Vue Storybook Configuration Generator](/nx-api/vue/generators/storybook-configuration)\n\n<!-- TODO(katerina): uncomment when Nuxt is in - [Nuxt Storybook Configuration Generator](/nx-api/nuxt/generators/storybook-configuration) -->\n\n- [React Native Storybook Configuration Generator](/nx-api/react-native/generators/storybook-configuration)\n\nIf you are not using one of the framework-specific generators mentioned above, when 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- The `uiFramework` you want to use. Supported values are:\n - `@storybook/angular`\n - `@storybook/html-webpack5`\n - `@storybook/nextjs`\n - `@storybook/preact-webpack5`\n - `@storybook/react-webpack5`\n - `@storybook/react-vite`\n - `@storybook/server-webpack5`\n - `@storybook/svelte-webpack5`\n - `@storybook/svelte-vite`\n - `@storybook/sveltekit`\n - `@storybook/vue-webpack5`\n - `@storybook/vue-vite`\n - `@storybook/vue3-webpack5`\n - `@storybook/vue3-vite`\n - `@storybook/web-components-webpack5`\n - `@storybook/web-components-vite`\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, 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/angular/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests).\n\nYou must provide a `name` and a `uiFramework` for the generator to work.\n\nYou can read more about how this generator works, in the [Storybook package overview page](/packages/storybook#generating-storybook-configuration).\n\n## Examples\n\n### Generate Storybook configuration using JavaScript\n\n```bash\nnx g @nx/storybook:configuration ui --uiFramework=@storybook/web-components-vite --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": "Add Storybook configuration to a UI library or an application.",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -19,7 +19,7 @@ Storybook helps you test your UIs. You can read more about testing with Storyboo
|
||||
|
||||
### Documentation
|
||||
|
||||
Storybook helps you document your UI elements, or your design system, effectively and in an interactive way. You can read more in the [How-to document components](https://storybook.js.org/docs/react/writing-docs/introduction) documentation page. Essentially, you can use Storybook to publish a catalog of your components. A catalog that you can share with the design team, the developer team, the product team, anyone else in the product development process, or even the client. The components are isolated, interactive, and can be represented in all possible forms that they can take (e.g. for a button: enabled, disabled, active, etc). You can read more about publishing your Storybook in the [Publish Storybook](https://storybook.js.org/docs/react/sharing/publish-storybook) documentation page.
|
||||
Storybook helps you document your UI elements, or your design system, effectively and in an interactive way. You can read more in the [How to document components](https://storybook.js.org/docs/react/writing-docs/introduction) documentation page. Essentially, you can use Storybook to publish a catalog of your components. A catalog that you can share with the design team, the developer team, the product team, anyone else in the product development process, or even the client. The components are isolated, interactive, and can be represented in all possible forms that they can take (e.g. for a button: enabled, disabled, active, etc). You can read more about publishing your Storybook in the [Publish Storybook](https://storybook.js.org/docs/react/sharing/publish-storybook) documentation page.
|
||||
|
||||
## Nx and Storybook
|
||||
|
||||
@ -42,38 +42,34 @@ If your project is not configured yet, check out one of these guides:
|
||||
- [Set up Storybook for React Projects](/recipes/storybook/overview-react)
|
||||
|
||||
- [Set up Storybook for Angular Projects](/recipes/storybook/overview-angular)
|
||||
-
|
||||
- [Set up Storybook for Vue Projects](/recipes/storybook/overview-vue)
|
||||
|
||||
If your project is [already configured](/nx-api/storybook), you can use the `stories` generator:
|
||||
|
||||
- [React stories generator](/nx-api/react/generators/stories)
|
||||
- [React (and Next.js) stories generator](/nx-api/react/generators/stories)
|
||||
|
||||
- [React Native stories generator](/nx-api/react-native/generators/stories)
|
||||
|
||||
- [Vue stories generator](/nx-api/vue/generators/stories)
|
||||
|
||||
- [Angular stories generator](/nx-api/angular/generators/stories)
|
||||
|
||||
The stories generator will read your inputs (if you’re using Angular), or your props (if you're using React), and will generate stories with the corresponding arguments/controls already prefilled.
|
||||
|
||||
#### Cypress tests generation
|
||||
#### Storybook interaction tests
|
||||
|
||||
Nx also generates Cypress tests for your components, that point to the corresponding component’s story. You can read more about how the Cypress tests are generated and how they look like in the [storybook-configuration generator documentation](/recipes/storybook/overview-react#cypress-tests-for-stories).
|
||||
[Storybook interaction tests](https://storybook.js.org/docs/react/writing-tests/interaction-testing) allow you to test user interactions within your Storybook stories. It enhances your [Storybook](https://storybook.js.org/) setup, ensuring that not only do your components look right, but they also work correctly when interacted with.
|
||||
|
||||
Take a look at the generated code of the Cypress test file, specifically at the URL which Cypress visits:
|
||||
|
||||
```javascript
|
||||
cy.visit(
|
||||
'/iframe.html?id=buttoncomponent--primary&args=text:Click+me!;padding;style:default'
|
||||
);
|
||||
```
|
||||
|
||||
Cypress visits the URL that hosts the story of the component we are testing, adding values to its controls (eg. `args=text:Click+me!`). Then, the test attempts to validate that the values are correctly applied.
|
||||
Nx will generate interaction tests for your stories. You can read more in our [Setting up Storybook Interaction Tests with Nx guide](/recipes/storybook/storybook-interaction-tests).
|
||||
|
||||
### CI/CD tools
|
||||
|
||||
Now let’s see how Nx helps in the CI/CD journey, as well.
|
||||
|
||||
#### Cypress testing
|
||||
#### Interaction tests in your CI
|
||||
|
||||
When you are running the Cypress tests for a project, Cypress will start the Storybook server of that project. The Storybook server will fire up a Storybook instance, hosting all the components's stories for that project. The e2e tests will then run, which actually visit the stories and perform the tests there. Cypress will be configured to start and stop the Storybook server. The results will be cached, and they will go through the Nx graph, meaning that Nx will know if the tests need to be run again or not, depending on the affected status of your project.
|
||||
You can set up your interaction tests to run as part of your CI. You can read more in the [Storybook docs](https://storybook.js.org/docs/react/writing-tests/test-runner#set-up-ci-to-run-**tests**).
|
||||
|
||||
#### Serve
|
||||
|
||||
@ -97,7 +93,7 @@ Setting up Storybook on Nx reflects - and takes advantage of - the [mental model
|
||||
|
||||
##### Development and debugging
|
||||
|
||||
In the process of setting up Storybook in your Nx workspace that we described above, you end up with one Storybook instance per project. That way, you can use your project’s Storybook targets to serve and build Storybook:
|
||||
In the process of setting up Storybook in your Nx workspace that we described above, you end up with one Storybook instance per project. That way, you can use your project’s Storybook targets to serve, test and build Storybook:
|
||||
|
||||
```shell
|
||||
nx storybook my-project
|
||||
@ -109,11 +105,13 @@ and
|
||||
nx build-storybook my-project
|
||||
```
|
||||
|
||||
This feature is extremely useful when developing locally. The containerized stories in your Storybook are the only ones that are built/served when you want to debug just one component, or just one library. You don’t have to wait for a huge Storybook containing all your stories in your repository to fire up. You just need to wait for the Storybook of a single project to start. This speeds up the process.
|
||||
and
|
||||
|
||||
##### E2e tests with Cypress
|
||||
```shell
|
||||
nx test-storybook my-project
|
||||
```
|
||||
|
||||
If you’re using Cypress, and you’re taking advantage of the generated Cypress tests that our Storybook generators generate, then your e2e tests are also going to be much faster. When you run your e2e tests for a particular project, Cypress is only going to start the specific Storybook instance, and it’s going to take much less time than having to start an all-including universal Storybook.
|
||||
This feature is extremely useful when developing locally. The containerized stories in your Storybook are the only ones that are built/served/tested when you want to debug just one component, or just one library. You don’t have to wait for a huge Storybook containing all your stories in your repository to fire up. You just need to wait for the Storybook of a single project to start. This speeds up the process.
|
||||
|
||||
##### Caching, affected, dependency management
|
||||
|
||||
|
||||
@ -7,10 +7,6 @@ description: This is an overview page for the Storybook plugin in Nx. It explain
|
||||
|
||||
This guide will briefly walk you through using Storybook within an Nx workspace.
|
||||
|
||||
{% callout type="info" title="Storybook 7 by default" %}
|
||||
Starting with Nx 16, Storybook 7 is used by default to configure your projects.
|
||||
{% /callout %}
|
||||
|
||||
## Setting Up Storybook
|
||||
|
||||
### Add the Storybook plugin
|
||||
@ -86,7 +82,7 @@ nx g @nx/react-native:storybook-configuration my-react-native-project
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
These framework-specific generators will also **generate stories** for you.
|
||||
These framework-specific generators will also **generate stories** and interaction tests for you.
|
||||
|
||||
If you are NOT using a framework-specific generator (for [Angular](/nx-api/angular/generators/storybook-configuration), [React](/nx-api/react/generators/storybook-configuration), [React Native](/nx-api/react-native/generators/storybook-configuration), [Vue](/nx-api/vue/generators/storybook-configuration)), in the field `uiFramework` you must choose one of the following Storybook frameworks:
|
||||
|
||||
@ -113,9 +109,7 @@ Choosing one of these frameworks will have the following effects on your workspa
|
||||
|
||||
2. Nx will generate a project-level `.storybook` folder (located under `libs/your-project/.storybook` or `apps/your-project/.storybook`) containing the essential configuration files for Storybook.
|
||||
|
||||
3. Nx will create new `targets` in your project's `project.json`, called `storybook` and `build-storybook`, containing all the necessary configuration to serve and build Storybook.
|
||||
|
||||
4. Nx will generate a new Cypress e2e app for your project (if there isn't one already) to run against the Storybook instance.
|
||||
3. Nx will create new `targets` in your project's `project.json`, called `storybook`, `test-storybook` and `build-storybook`, containing all the necessary configuration to serve, test and build Storybook.
|
||||
|
||||
Make sure to **use the framework-specific generators** if your project is using Angular, React, Next.js or React Native: [`@nx/angular:storybook-configuration`](/nx-api/angular/generators/storybook-configuration), [`@nx/react:storybook-configuration`](/nx-api/react/generators/storybook-configuration), [`@nx/react-native:storybook-configuration`](/nx-api/react-native/generators/storybook-configuration), as shown above.
|
||||
|
||||
@ -147,6 +141,20 @@ or
|
||||
nx build-storybook project-name
|
||||
```
|
||||
|
||||
### Testing Storybook
|
||||
|
||||
With the Storybook server running, you can test Storybook (run all the interaction tests) using this command:
|
||||
|
||||
```shell
|
||||
nx run project-name:test-storybook
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```shell
|
||||
nx test-storybook project-name
|
||||
```
|
||||
|
||||
### Anatomy of the Storybook setup
|
||||
|
||||
When running the Nx Storybook generator, it'll configure the Nx workspace to be able to run Storybook seamlessly. It'll create a project specific Storybook configuration.
|
||||
@ -171,17 +179,21 @@ To register a [Storybook addon](https://storybook.js.org/addons/) for all Storyb
|
||||
|
||||
1. In your project's `.storybook/main.ts` file, in the `addons` array of the `module.exports` object, add the new addon:
|
||||
|
||||
```typescript {% fileName="<project-path>/.storybook/main.js" %}
|
||||
module.exports = {
|
||||
stories: [...],
|
||||
...,
|
||||
addons: [..., '@storybook/addon-essentials'],
|
||||
};
|
||||
```
|
||||
```typescript {% fileName="<project-path>/.storybook/main.ts" %}
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
...
|
||||
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions', ...],
|
||||
...
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
2. If a decorator is required, in each project's `<project-path>/.storybook/preview.ts`, you can export an array called `decorators`.
|
||||
|
||||
```typescript {% fileName="<project-path>/.storybook/preview.js" %}
|
||||
```typescript {% fileName="<project-path>/.storybook/preview.ts" %}
|
||||
import someDecorator from 'some-storybook-addon';
|
||||
export const decorators = [someDecorator];
|
||||
```
|
||||
|
||||
@ -138,14 +138,6 @@ Let's see the result for our `web` app `storybook` target, for example (in `apps
|
||||
},
|
||||
```
|
||||
|
||||
{% callout type="warning" title="Check the version!" %}
|
||||
Make sure you are on Nx version `>=14.1.8` and your `storybook` target is using `@storybook/angular:start-storybook` as the `executor` (like the example above).
|
||||
|
||||
If you are using an older version of Nx, you can use [`nx migrate`](/nx-api/nx/documents/migrate) to migrate your codebase to a later version. Using `nx migrate` will also make sure to update your `storybook` and `build-storybook` targets to match the new format.
|
||||
|
||||
If you **are** on Nx `>=14.1.8` and you are still using the old executor (`@nx/storybook:storybook`), you can read our documentation about the [Angular Storybook targets](/deprecated/storybook/angular-storybook-targets) to help you change your `storybook` and `build-storybook` targets across your workspace for your Angular projects using Storybook.
|
||||
{% /callout %}
|
||||
|
||||
### 4. Let Storybook know of the `documentation.json` file
|
||||
|
||||
In your project's `.storybook/preview.js` file (for example for your `web` app the path would be `apps/web/.storybook/preview.js`), add the following:
|
||||
|
||||
@ -17,7 +17,7 @@ If you want to add a global configuration for Webpack or Vite in your workspace,
|
||||
|
||||
The `webpackFinal` field would look like this:
|
||||
|
||||
```ts {% fileName=".storybook/main.js" %}
|
||||
```ts {% fileName=".storybook/main.ts" %}
|
||||
webpackFinal: async (config, { configType }) => {
|
||||
// Make whatever fine-grained changes you need that should apply to all storybook configs
|
||||
|
||||
@ -30,7 +30,7 @@ webpackFinal: async (config, { configType }) => {
|
||||
|
||||
The `viteFinal` field would look like this:
|
||||
|
||||
```ts {% fileName=".storybook/main.js" %}
|
||||
```ts {% fileName=".storybook/main.ts" %}
|
||||
async viteFinal(config, { configType }) {
|
||||
if (configType === 'DEVELOPMENT') {
|
||||
// Your development configuration goes here
|
||||
@ -46,7 +46,7 @@ async viteFinal(config, { configType }) {
|
||||
|
||||
In the `viteFinal` case, you would have to import the `mergeConfig` function from `vite`. So, on the top of your root `.storybook/main.js|ts` file, you would have to add:
|
||||
|
||||
```ts {% fileName=".storybook/main.js" %}
|
||||
```ts {% fileName=".storybook/main.ts" %}
|
||||
import { mergeConfig } from 'vite';
|
||||
```
|
||||
|
||||
@ -56,37 +56,49 @@ import { mergeConfig } from 'vite';
|
||||
|
||||
You can customize the `webpack` configuration for a specific project by adding a `webpackFinal` field in your project-specific `.storybok/main.js|ts` file, like this:
|
||||
|
||||
```ts {% fileName="apps/my-react-webpack-app/.storybook/main.js" %}
|
||||
export default {
|
||||
...
|
||||
```ts {% fileName="apps/my-react-webpack-app/.storybook/main.ts" %}
|
||||
import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ...,
|
||||
addons: ...,
|
||||
framework: {
|
||||
name: '@storybook/react-webpack5',
|
||||
options: {},
|
||||
},
|
||||
webpackFinal: async (config, { configType }) => {
|
||||
|
||||
// add your own webpack tweaks if needed
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
If you are using a global, root-level, `webpack` configuration in your project, you can customize or extend that for a specific project like this:
|
||||
|
||||
```ts {% fileName="apps/my-react-webpack-app/.storybook/main.js" %}
|
||||
```ts {% fileName="apps/my-react-webpack-app/.storybook/main.ts" %}
|
||||
import rootMain from '../../../.storybook/main';
|
||||
|
||||
export default {
|
||||
const config: StorybookConfig = {
|
||||
...rootMain,
|
||||
...
|
||||
stories: ...,
|
||||
addons: ...,
|
||||
framework: {
|
||||
name: '@storybook/react-webpack5',
|
||||
options: {},
|
||||
},
|
||||
webpackFinal: async (config, { configType }) => {
|
||||
// apply any global webpack configs that might have been specified in .storybook/main.js
|
||||
if (rootMain.webpackFinal) {
|
||||
config = await rootMain.webpackFinal(config, { configType });
|
||||
}
|
||||
|
||||
// add your own webpack tweaks if needed
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
Take note how, in this case, we are first applying the global `webpack` configuration, and then adding our own tweaks. If you don't want to apply any global configuration, you can just return your own configuration, and skip the `rootMain.webpackFinal` check.
|
||||
@ -95,50 +107,56 @@ Take note how, in this case, we are first applying the global `webpack` configur
|
||||
|
||||
You can customize the `vite` configuration for a specific project by adding a `viteFinal` field in your project-specific `.storybok/main.js|ts` file, like this:
|
||||
|
||||
```ts {% fileName="apps/my-react-vite-app/.storybook/main.js" %}
|
||||
```ts {% fileName="apps/my-react-vite-app/.storybook/main.ts" %}
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
import { mergeConfig } from 'vite';
|
||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
export default {
|
||||
...
|
||||
const config: StorybookConfig = {
|
||||
stories: ...,
|
||||
addons: ...,
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {
|
||||
builder: {
|
||||
viteConfigPath: 'apps/web/vite.config.ts',
|
||||
},
|
||||
},
|
||||
},
|
||||
async viteFinal(config, { configType }) {
|
||||
return mergeConfig(config, {
|
||||
... <your config here>
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
If you are using a global, root-level, `vite` configuration in your workspace, you can customize or extend that for a specific project like this:
|
||||
|
||||
```ts {% fileName="apps/my-react-vite-app/.storybook/main.js" %}
|
||||
```ts {% fileName="apps/my-react-vite-app/.storybook/main.ts" %}
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
import { mergeConfig } from 'vite';
|
||||
import rootMain from '../../../.storybook/main';
|
||||
|
||||
export default {
|
||||
...
|
||||
const config: StorybookConfig = {
|
||||
...rootMain,
|
||||
stories: ...,
|
||||
addons: ...,
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {
|
||||
builder: {
|
||||
viteConfigPath: 'apps/web/vite.config.ts',
|
||||
},
|
||||
},
|
||||
},
|
||||
async viteFinal(config, { configType }) {
|
||||
return mergeConfig(config, {
|
||||
...((await rootMain.viteFinal(config, { configType })) ?? {})
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
So, a full project-level `.storybook/main.js|ts` file for a Vite.js project would look like this:
|
||||
|
||||
```ts {% fileName="apps/my-react-vite-app/.storybook/main.js" %}
|
||||
import { mergeConfig } from 'vite';
|
||||
|
||||
export default {
|
||||
stories: ['../src/app/**/*.stories.@(mdx|js|jsx|ts|tsx)'],
|
||||
addons: ['@storybook/addon-essentials'],
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {},
|
||||
},
|
||||
async viteFinal(config, { configType }) {
|
||||
return mergeConfig(config, {});
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
@ -15,6 +15,7 @@ You can read more about Storybook interaction tests in the following sections of
|
||||
|
||||
- [Storybook interaction tests for React](https://storybook.js.org/docs/react/writing-tests/interaction-testing)
|
||||
- [Storybook interaction tests for Angular](https://storybook.js.org/docs/angular/writing-tests/interaction-testing)
|
||||
- [Storybook interaction tests for Vue](https://storybook.js.org/docs/vue/writing-tests/interaction-testing)
|
||||
- [Storybook test runner](https://storybook.js.org/docs/react/writing-tests/test-runner)
|
||||
- [The `play` function](https://storybook.js.org/docs/react/writing-stories/play-function)
|
||||
|
||||
@ -42,6 +43,13 @@ nx g @nx/angular:storybook-configuration project-name --interactionTests=true
|
||||
nx g @nx/react:storybook-configuration project-name --interactionTests=true
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Vue" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/vue:storybook-configuration project-name --interactionTests=true
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="No framework" %}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ description: Dive into a comprehensive guide on how to consolidate all your Stor
|
||||
This guide extends the
|
||||
[Using Storybook in a Nx workspace - Best practices](/nx-api/storybook/documents/best-practices) guide. In that guide, we discussed the best practices of using Storybook in a Nx workspace. We explained the main concepts and the mental model of how to best set up Storybook. In this guide, we are going to see how to put that into practice, by looking at a real-world example. We are going to see how you can publish one single Storybook for your workspace.
|
||||
|
||||
This case would work if all your projects (applications and libraries) containing stories that you want to use are using the same framework (Angular, React, Vue, etc). The reason is that you will be importing the stories in a central host Storybook's `.storybook/main.js`, and we will be using one specific builder to build that Storybook. Storybook does not support mixing frameworks in the same Storybook instance.
|
||||
This case would work if all your projects (applications and libraries) containing stories that you want to use are using the same framework (Angular, React, Vue, etc). The reason is that you will be importing the stories in a central host Storybook's `.storybook/main.ts`, and we will be using one specific builder to build that Storybook. Storybook does not support mixing frameworks in the same Storybook instance.
|
||||
|
||||
Let’s see how we can implement this solution:
|
||||
|
||||
@ -18,14 +18,14 @@ Let’s see how we can implement this solution:
|
||||
|
||||
### Generate a new library that will host our Storybook instance
|
||||
|
||||
According to the framework you are using, use the corresponding generator to generate a new library. Let’s suppose that you are using React and all your stories are using `@storybook/react`:
|
||||
According to the framework you are using, use the corresponding generator to generate a new library. Let’s suppose that you are using React and all your stories are using the `@storybook/react-vite` framework:
|
||||
|
||||
{% callout type="note" title="Directory Flag Behavior Changes" %}
|
||||
The command below uses the `as-provided` directory flag behavior, which is the default in Nx 16.8.0. If you're on an earlier version of Nx or using the `derived` option, omit the `--directory` flag. See the [workspace layout documentation](/reference/nx-json#workspace-layout) for more details.
|
||||
{% /callout %}
|
||||
|
||||
```shell
|
||||
nx g @nx/react:library storybook-host --directory=libs/storybook-host
|
||||
nx g @nx/react:library storybook-host --directory=libs/storybook-host --bundler=none --unitTestRunner=none --projectNameAndRootFormat=as-provided
|
||||
```
|
||||
|
||||
Now, you have a new library, which will act as a shell/host for all your stories.
|
||||
@ -35,28 +35,40 @@ Now, you have a new library, which will act as a shell/host for all your stories
|
||||
Now let’s configure our new library to use Storybook, using the [`@nx/storybook:configuration` generator](/nx-api/storybook/generators/configuration). Run:
|
||||
|
||||
```shell
|
||||
nx g @nx/storybook:configuration storybook-host
|
||||
nx g @nx/storybook:configuration storybook-host --interactionTests=true --uiFramework=@storybook/react-vite
|
||||
```
|
||||
|
||||
and choose the framework you want to use (in our case, choose `@storybook/react`).
|
||||
This generator will only generate the `storybook`, `build-storybook` and `test-storybook` targets in our new library's `project.json` (`libs/storybook-host/project.json`), and also the `libs/storybook-host/.storybook` folder. This is all we care about. We don’t need any stories in this project, since we will be importing the stories from other projects in our workspace. So, if you want, you can delete the contents of the `src/lib` folder. You may also delete the `lint` and `test` targets in `libs/storybook-host/project.json`. We will not be needing those.
|
||||
|
||||
This generator will only generate the `storybook` and `build-storybook` targets in our new library's `project.json` (`libs/storybook-host/project.json`), and also the `libs/storybook-host/.storybook` folder. This is all we care about. We don’t need any stories in this project, since we will be importing the stories from other projects in our workspace. So, if you want, you can delete the contents of the `src/lib` folder. You may also delete the `lint` and `test` targets in `libs/storybook-host/project.json`. We will not be needing those.
|
||||
### Import the stories in our library's main.ts
|
||||
|
||||
### Import the stories in our library's main.js
|
||||
Now it’s time to import the stories of our other projects in our new library's `./storybook/main.ts`.
|
||||
|
||||
Now it’s time to import the stories of our other projects in our new library's `./storybook/main.js`.
|
||||
Here is a sample `libs/storybook-host/.storybook/main.ts` file:
|
||||
|
||||
Here is a sample `libs/storybook-host/.storybook/main.js` file:
|
||||
```javascript {% fileName="libs/storybook-host/.storybook/main.ts" %}
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { mergeConfig } from 'vite';
|
||||
|
||||
```javascript {% fileName="libs/storybook-host/.storybook/main.js" %}
|
||||
module.exports = {
|
||||
core: { builder: 'webpack5' },
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../../**/ui/**/src/lib/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||
addons: ['@storybook/addon-essentials', '@nx/react/plugins/storybook'],
|
||||
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {},
|
||||
},
|
||||
|
||||
viteFinal: async (config) =>
|
||||
mergeConfig(config, {
|
||||
plugins: [nxViteTsPaths()],
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
Notice how we only link the stories matching that pattern. According to your workspace set-up, you can adjust the pattern, or add more patterns, so that you can match all the stories in all the projects you want.
|
||||
Notice how we only link the stories matching a specific pattern. According to your workspace set-up, you can adjust the pattern, or add more patterns, so that you can match all the stories in all the projects you want.
|
||||
|
||||
For example:
|
||||
|
||||
@ -68,28 +80,38 @@ stories: [
|
||||
];
|
||||
```
|
||||
|
||||
### Import the stories in Storybook’s tsconfig.json
|
||||
### Import the stories in tsconfig.storybook.json
|
||||
|
||||
If you are using Angular, do not forget to import the stories in the TypeScript configuration of Storybook.
|
||||
Here is a sample `libs/storybook-host/tsconfig.storybook.json` file:
|
||||
|
||||
Here is a sample `libs/storybook-host-angular/.storybook/tsconfig.json` file:
|
||||
|
||||
```json {% fileName="libs/storybook-host-angular/.storybook/tsconfig.json" %}
|
||||
```json {% fileName="libs/storybook-host/tsconfig.storybook.json" %}
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"exclude": ["../**/*.spec.ts"],
|
||||
"include": ["../../**/ui/**/src/lib/**/*.stories.ts", "*.js"]
|
||||
...
|
||||
"include": [
|
||||
"../../**/ui/**/src/**/*.stories.ts",
|
||||
"../../**/ui/**/src/**/*.stories.js",
|
||||
"../../**/ui/**/src/**/*.stories.jsx",
|
||||
"../../**/ui/**/src/**/*.stories.tsx",
|
||||
"../../**/ui/**/src/**/*.stories.mdx",
|
||||
".storybook/*.js",
|
||||
".storybook/*.ts"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Notice how in the `include` array we are specifying the paths to our stories, using the same pattern we used in our `.storybook/main.js`.
|
||||
Notice how in the `include` array we are specifying the paths to our stories, using the same pattern we used in our `.storybook/main.ts`.
|
||||
|
||||
### Serve or build your Storybook!
|
||||
{% callout type="note" title="Angular Storybook tsconfig.json" %}
|
||||
For Angular, that file is in your project's `.storybook` directory, so in this case it would be under `libs/storybook-host/.storybook/tsconfig.json`.
|
||||
{% /callout %}
|
||||
|
||||
Now you can serve or build your Storybook as you would, normally. And then you can publish the bundled app!
|
||||
### Serve or build your Storybook
|
||||
|
||||
Now you can serve, test or build your Storybook as you would, normally. And then you can publish the bundled app!
|
||||
|
||||
```shell
|
||||
nx storybook storybook-host
|
||||
@ -101,6 +123,12 @@ or
|
||||
nx build-storybook storybook-host
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```shell
|
||||
nx test-storybook storybook-host
|
||||
```
|
||||
|
||||
## Use cases that apply to this solution
|
||||
|
||||
Can be used for:
|
||||
@ -113,7 +141,7 @@ Ideal for:
|
||||
|
||||
## Extras - Dependencies
|
||||
|
||||
Your new Storybook host, essentially, depends on all the projects from which it is importing stories. This means whenever one of these projects updates a component, or updates a story, our Storybook host would have to rebuild, to reflect these changes. It cannot rely on the cached result. However, Nx does not understand the imports in `libs/storybook-host/.storybook/main.js`, and the result is that Nx does not know which projects the Storybook host depends on, based solely on the `main.js` imports. The good thing is that there is a solution to this. You can manually add the projects your Storybook host depends on as implicit dependencies in your project’s `project.json`, in the implicit dependencies array.
|
||||
Your new Storybook host, essentially, depends on all the projects from which it is importing stories. This means whenever one of these projects updates a component, or updates a story, our Storybook host would have to rebuild, to reflect these changes. It cannot rely on the cached result. However, Nx does not understand the imports in `libs/storybook-host/.storybook/main.ts`, and the result is that Nx does not know which projects the Storybook host depends on, based solely on the `main.ts` imports. The good thing is that there is a solution to this. You can manually add the projects your Storybook host depends on as implicit dependencies in your project’s `project.json`, in the implicit dependencies array.
|
||||
|
||||
For example, `libs/storybook-host/project.json`:
|
||||
|
||||
|
||||
@ -22,12 +22,7 @@ Say, for example, that you have a client app, an admin app, and a number of UI l
|
||||
happynrwl/
|
||||
├── apps/
|
||||
│ ├── client/
|
||||
│ ├── client-e2e/
|
||||
│ ├── admin/
|
||||
│ ├── admin-e2e/
|
||||
│ ├── client-ui-header-e2e/
|
||||
│ ├── admin-ui-dashboard-e2e/
|
||||
│ └── shared-ui-cta-e2e/
|
||||
├── libs/
|
||||
│ ├── client/
|
||||
│ │ ├── feature/
|
||||
@ -75,7 +70,7 @@ happynrwl/
|
||||
|
||||
In this case you can see that we have two deployable applications, `client` and `admin`, and we have a number of UI libraries, each associated with a specific app. For example, `client-ui-header` is a UI library associated with the `client` app, and `admin-ui-dashboard` is a UI library associated with the `admin` app. We also have one more library, the `shared-ui-cta` library, which is shared between the two apps. The way we have structured our folders is such that any new library that is related to the `client` app will go in the `libs/client` folder, and in that folder we have a sub-folder to determine if the new library is related to `ui` or anything else. The same applies to the `admin` app. Any library shared between the two apps will live under a subfolder of the `libs/shared` folder.
|
||||
|
||||
Notice how we have already generated Storybook configuration and stories for all of our `ui` libraries. We have also generated a `e2e` application for each of these `ui` libraries, which is going to use the corresponding Storybook instance to run end-to-end tests.
|
||||
Notice how we have already generated Storybook configuration and stories for all of our `ui` libraries.
|
||||
|
||||
## Setting up the thematic Storybook instances
|
||||
|
||||
@ -96,19 +91,31 @@ The commands below uses the `as-provided` directory flag behavior, which is the
|
||||
{% /callout %}
|
||||
|
||||
```shell
|
||||
nx g @nx/angular:lib storybook-host-client --directory=libs/storybook-host-client
|
||||
nx g @nx/angular:lib storybook-host-admin --directory=libs/storybook-host-admin
|
||||
nx g @nx/angular:lib storybook-host-shared --directory=libs/storybook-host-shared
|
||||
nx g @nx/angular:lib storybook-host-client --directory=libs/storybook-host-client --projectNameAndRootFormat=as-provided
|
||||
```
|
||||
|
||||
```shell
|
||||
nx g @nx/angular:lib storybook-host-admin --directory=libs/storybook-host-admin --projectNameAndRootFormat=as-provided
|
||||
```
|
||||
|
||||
```shell
|
||||
nx g @nx/angular:lib storybook-host-shared --directory=libs/storybook-host-shared --projectNameAndRootFormat=as-provided
|
||||
```
|
||||
|
||||
### Generate the Storybook configuration for the libraries
|
||||
|
||||
Now, we need to generate Storybook configuration for all these new libraries. We don't want to generate `stories` or a new Cypress project for these libraries, so we can run the following commands:
|
||||
Now, we need to generate Storybook configuration for all these new libraries. We don't want to generate `stories` for these libraries, so we can run the following commands:
|
||||
|
||||
```shell
|
||||
nx g @nx/storybook:configuration storybook-host-client --uiFramework=@storybook/angular
|
||||
nx g @nx/storybook:configuration storybook-host-admin --uiFramework=@storybook/angular
|
||||
nx g @nx/storybook:configuration storybook-host-shared --uiFramework=@storybook/angular
|
||||
nx g @nx/storybook:configuration storybook-host-client --uiFramework=@storybook/angular --interactionTests=true
|
||||
```
|
||||
|
||||
```shell
|
||||
nx g @nx/storybook:configuration storybook-host-admin --uiFramework=@storybook/angular --interactionTests=true
|
||||
```
|
||||
|
||||
```shell
|
||||
nx g @nx/storybook:configuration storybook-host-shared --uiFramework=@storybook/angular --interactionTests=true
|
||||
```
|
||||
|
||||
### Import the stories
|
||||
@ -117,14 +124,21 @@ Now that our Storybook configuration is ready for our new libraries, we can go a
|
||||
|
||||
Thanks to our folder structure, we can easily configure Storybook to import all the stories under a specific folder, for example, which are associated with a specific scope.
|
||||
|
||||
For example, `libs/storybook-host-admin/.storybook/main.js`:
|
||||
For example, `libs/storybook-host-admin/.storybook/main.ts`:
|
||||
|
||||
```javascript {% fileName="libs/storybook-host-admin/.storybook/main.js" %}
|
||||
module.exports = {
|
||||
core: { builder: 'webpack5' },
|
||||
```javascript {% fileName="libs/storybook-host-admin/.storybook/main.ts" %}
|
||||
import type { StorybookConfig } from '@storybook/angular';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../../admin/ui/**/src/lib/**/*.stories.ts'],
|
||||
addons: ['@storybook/addon-essentials'],
|
||||
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||
framework: {
|
||||
name: '@storybook/angular',
|
||||
options: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
And don't forget the `libs/storybook-host-admin/.storybook/tsconfig.json`:
|
||||
@ -136,7 +150,7 @@ And don't forget the `libs/storybook-host-admin/.storybook/tsconfig.json`:
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"exclude": ["../**/*.spec.ts"],
|
||||
"include": ["../../admin/ui/**/src/lib/**/*.stories.ts", "*.js"]
|
||||
"include": ["../../admin/ui/**/src/lib/**/*.stories.ts", "*.ts"]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ The command below uses the `as-provided` directory flag behavior, which is the d
|
||||
So, let’s use React for the Storybook Composition host library:
|
||||
|
||||
```shell
|
||||
nx g @nx/react:lib storybook-host --directory=libs/storybook-host
|
||||
nx g @nx/react:lib storybook-host --directory=libs/storybook-host --bundler=none --unitTestRunner=none --projectNameAndRootFormat=as-provided
|
||||
```
|
||||
|
||||
Now that your library is generated, you can write your intro in the generated component (you can also do this later, it does not matter).
|
||||
@ -45,22 +45,29 @@ Now that your library is generated, you can write your intro in the generated co
|
||||
Since you do need a story for your host Storybook, you should use the React storybook configuration generator, and actually choose to generate stories (not an e2e project though):
|
||||
|
||||
```shell
|
||||
nx g @nx/react:storybook-configuration –-name=storybook-host
|
||||
nx g @nx/react:storybook-configuration storybook-host --interactionTests=true --generateStories=true
|
||||
```
|
||||
|
||||
And choose `yes` to generate stories, and `no` to generate a Cypress app.
|
||||
|
||||
### Change the Storybook port in the hosted apps
|
||||
|
||||
Now it’s important to change the Storybook ports in the `storybook-host-angular` and `storybook-host-react`. Go to the `project.json` of each of these libraries (`libs/storybook-host-angular/project.json` and `libs/storybook-host-react/project.json`), find the `storybook` target, and set the port to `4401` and `4402` accordingly. This is because the Storybook Composition host is going to be looking at these ports to find which Storybooks to host, and which Storybook goes where.
|
||||
|
||||
### Add the `refs` to the main.js of the host library
|
||||
### Add the `refs` to the main.ts of the host library
|
||||
|
||||
Create the composition in ``:
|
||||
|
||||
```javascript {% fileName="libs/storybook-host/.storybook/main.js" %}
|
||||
module.exports = {
|
||||
core: { builder: 'webpack5' },
|
||||
```javascript {% fileName="libs/storybook-host/.storybook/main.ts" %}
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { mergeConfig } from 'vite';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/lib/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {},
|
||||
},
|
||||
refs: {
|
||||
'angular-stories': {
|
||||
title: 'Angular Stories',
|
||||
@ -71,9 +78,13 @@ module.exports = {
|
||||
url: 'http://localhost:4402',
|
||||
},
|
||||
},
|
||||
stories: ['../src/lib/**/*.stories.tsx'],
|
||||
addons: ['@storybook/addon-essentials', '@nx/react/plugins/storybook'],
|
||||
viteFinal: async (config) =>
|
||||
mergeConfig(config, {
|
||||
plugins: [nxViteTsPaths()],
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
### Serve the Storybook instances
|
||||
@ -96,7 +107,7 @@ To deploy the composed Storybooks you need to do the following:
|
||||
|
||||
1. Deploy the `storybook-host-angular` Storybook
|
||||
2. Deploy the `storybook-host-react` Storybook
|
||||
3. Change the `refs` in `libs/storybook-host/.storybook/main.js` to point to the URLs of the deployed Storybooks mentioned above
|
||||
3. Change the `refs` in `libs/storybook-host/.storybook/main.ts` to point to the URLs of the deployed Storybooks mentioned above
|
||||
4. Deploy the `storybook-host` Storybook
|
||||
|
||||
## Use cases that apply to this solution
|
||||
|
||||
@ -73,74 +73,74 @@ and the result would be the following:
|
||||
|
||||
{% /callout %}
|
||||
|
||||
## Cypress tests for Stories
|
||||
|
||||
The [`@nx/angular:storybook-configuration` generator](/nx-api/angular/generators/storybook-configuration) gives the option to set up an e2e Cypress app that is configured to run against the project's Storybook instance.
|
||||
|
||||
To launch Storybook and run the Cypress tests against the iframe inside of Storybook:
|
||||
|
||||
```shell
|
||||
nx run project-name-e2e:e2e
|
||||
```
|
||||
|
||||
The url that Cypress points to should look like this:
|
||||
|
||||
`'/iframe.html?id=buttoncomponent--primary&args=text:Click+me!;padding;style:default'`
|
||||
|
||||
- `buttoncomponent` is a lowercase version of the `Title` in the `*.stories.ts` file.
|
||||
- `primary` is the name of an individual story.
|
||||
- `style=default` sets the `style` arg to a value of `default`.
|
||||
|
||||
Changing args in the url query parameters allows your Cypress tests to test different configurations of your component. You can [read the documentation](https://storybook.js.org/docs/angular/writing-stories/args#setting-args-through-the-url) for more information.
|
||||
|
||||
## Example Files
|
||||
|
||||
Let's take for a example a library in your workspace, under `libs/feature/ui`, called `feature-ui` with a component, called `my-button`.
|
||||
Let's take for example a library in your workspace, under `libs/feature/ui`, called `feature-ui` with a component, called `my-button`.
|
||||
|
||||
Let's say that the template for that component looks like this:
|
||||
|
||||
```html {% fileName="libs/feature/ui/src/lib/my-button/my-button.component.html" %}
|
||||
<button [disabled]="disabled" [ngStyle]="{ 'padding.px': padding }">
|
||||
{{ text }}
|
||||
</button>
|
||||
```
|
||||
|
||||
and the component looks like this:
|
||||
|
||||
```typescript {% fileName="libs/feature/ui/src/lib/my-button/my-button.component.ts" %}
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'feature-ui-my-button',
|
||||
templateUrl: './my-button.component.html',
|
||||
styleUrls: ['./my-button.component.css'],
|
||||
})
|
||||
export class MyButtonComponent {
|
||||
@Input() text = 'Click me!';
|
||||
@Input() padding = 10;
|
||||
@Input() disabled = true;
|
||||
}
|
||||
```
|
||||
|
||||
### Story file
|
||||
|
||||
The [`@nx/angular:storybook-configuration` generator](/nx-api/angular/generators/storybook-configuration) would generate a Story file that looks like this:
|
||||
|
||||
```typescript {% fileName="libs/feature/ui/src/lib/my-button/my-button.component.stories.ts" %}
|
||||
import { Meta } from '@storybook/angular';
|
||||
import type { Meta, StoryObj } from '@storybook/angular';
|
||||
import { MyButtonComponent } from './my-button.component';
|
||||
import { within } from '@storybook/testing-library';
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
export default {
|
||||
title: 'MyButtonComponent',
|
||||
const meta: Meta<MyButtonComponent> = {
|
||||
component: MyButtonComponent,
|
||||
} as Meta<MyButtonComponent>;
|
||||
title: 'MyButtonComponent',
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<MyButtonComponent>;
|
||||
|
||||
export const Primary = {
|
||||
render: (args: MyButtonComponent) => ({
|
||||
props: args,
|
||||
}),
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
text: 'Click me!',
|
||||
padding: 10,
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Heading: Story = {
|
||||
args: {
|
||||
text: 'Click me!',
|
||||
padding: 10,
|
||||
disabled: true,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
expect(canvas.getByText(/my-button works!/gi)).toBeTruthy();
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Cypress test file
|
||||
|
||||
For the library described above, Nx would generate an E2E project called `feature-ui-e2e` with a Cypress test file that looks like this:
|
||||
|
||||
```typescript {% fileName="apps/feature-ui-e2e/src/e2e/my-button/my-button.component.cy.ts" %}
|
||||
describe('feature-ui', () => {
|
||||
beforeEach(() =>
|
||||
cy.visit(
|
||||
'/iframe.html?id=mybuttoncomponent--primary&args=text:Click+me!;padding:10;disabled:true;'
|
||||
)
|
||||
);
|
||||
|
||||
it('should contain the right text', () => {
|
||||
cy.get('button').should('contain', 'Click me!');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Depending on your Cypress version, the file will end with `.spec.ts` or `.cy.ts`.
|
||||
Notice the interaction test on the second story, inside the `play` function. This just tests if the default text that appears on generated components exists in the rendered component. You can edit this test to suit your needs. You can read more about interaction tests [here](https://storybook.js.org/docs/angular/writing-tests/interaction-testing).
|
||||
|
||||
## More Documentation
|
||||
|
||||
|
||||
@ -19,18 +19,6 @@ You can generate Storybook configuration for an individual React project by usin
|
||||
nx g @nx/react:storybook-configuration project-name
|
||||
```
|
||||
|
||||
## Nx React Storybook Preset
|
||||
|
||||
The [`@nx/react`](/nx-api/react) package ships with a Storybook addon to make sure it uses the very same configuration as your Nx React application. When you generate a Storybook configuration for a project, it'll automatically add the addon to your configuration.
|
||||
|
||||
```typescript
|
||||
module.exports = {
|
||||
...
|
||||
addons: ['@storybook/addon-essentials', ..., '@nx/react/plugins/storybook'],
|
||||
...
|
||||
};
|
||||
```
|
||||
|
||||
## Auto-generate Stories
|
||||
|
||||
The [`@nx/react:storybook-configuration` generator](/nx-api/react/generators/storybook-configuration) has the option to automatically generate `*.stories.ts|tsx` files for each component declared in the library. The stories will be generated using [Component Story Format 3 (CSF3)](https://storybook.js.org/blog/storybook-csf3-is-here/).
|
||||
@ -85,72 +73,69 @@ and the result would be the following:
|
||||
|
||||
{% /callout %}
|
||||
|
||||
## Cypress tests for Stories
|
||||
|
||||
The [`@nx/react:storybook-configuration` generator](/nx-api/react/generators/storybook-configuration) gives the option to set up an e2e Cypress app that is configured to run against the project's Storybook instance.
|
||||
|
||||
To launch Storybook and run the Cypress tests against the iframe inside of Storybook:
|
||||
|
||||
```shell
|
||||
nx run project-name-e2e:e2e
|
||||
```
|
||||
|
||||
The url that Cypress points to should look like this:
|
||||
|
||||
`'/iframe.html?id=mybutton--primary&args=text:Click+me!;padding;style:default'`
|
||||
|
||||
- `buttoncomponent` is a lowercase version of the `Title` in the `*.stories.tsx` file.
|
||||
- `primary` is the name of an individual story.
|
||||
- `style=default` sets the `style` arg to a value of `default`.
|
||||
|
||||
Changing args in the url query parameters allows your Cypress tests to test different configurations of your component. You can [read the documentation](https://storybook.js.org/docs/react/writing-stories/args#setting-args-through-the-url) for more information.
|
||||
|
||||
## Example Files
|
||||
|
||||
Let's take for a example a library in your workspace, under `libs/feature/ui`, called `feature-ui` with a component, called `my-button`.
|
||||
|
||||
Let's say that your component code looks like this:
|
||||
|
||||
```typescript {% fileName="libs/feature/ui/src/lib/my-button/my-button.tsx" %}
|
||||
export interface MyButtonProps {
|
||||
text: string;
|
||||
padding: number;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export function MyButton(props: MyButtonProps) {
|
||||
return (
|
||||
<button disabled={props.disabled} style={{ padding: props.padding }}>
|
||||
{props.text}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyButton;
|
||||
```
|
||||
|
||||
### Story file
|
||||
|
||||
The [`@nx/react:storybook-configuration` generator](/nx-api/react/generators/storybook-configuration) would generate a Story file that looks like this:
|
||||
|
||||
```typescript {% fileName="libs/feature/ui/src/lib/my-button/my-button.stories.tsx" %}
|
||||
import type { Meta } from '@storybook/react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { MyButton } from './my-button';
|
||||
import { within } from '@storybook/testing-library';
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
const Story: Meta<typeof MyButton> = {
|
||||
const meta: Meta<typeof MyButton> = {
|
||||
component: MyButton,
|
||||
title: 'MyButton',
|
||||
};
|
||||
export default Story;
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof MyButton>;
|
||||
|
||||
export const Primary = {
|
||||
args: {
|
||||
text: 'Click me!',
|
||||
padding: 10,
|
||||
disabled: true,
|
||||
text: '',
|
||||
padding: 0,
|
||||
disabled: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const Heading: Story = {
|
||||
args: {
|
||||
text: '',
|
||||
padding: 0,
|
||||
disabled: false,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
expect(canvas.getByText(/Welcome to MyButton!/gi)).toBeTruthy();
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Cypress test file
|
||||
|
||||
For the library described above, Nx would generate an E2E project called `feature-ui-e2e` with a Cypress test file that looks like this:
|
||||
|
||||
```typescript {% fileName="apps/feature-ui-e2e/src/e2e/my-button/my-button.cy.ts" %}
|
||||
describe('feature-ui: MyButton component', () => {
|
||||
beforeEach(() =>
|
||||
cy.visit(
|
||||
'/iframe.html?id=mybutton--primary&args=text:Click+me!;padding;style:default'
|
||||
)
|
||||
);
|
||||
|
||||
it('should contain the right text', () => {
|
||||
cy.get('button').should('contain', 'Click me!');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Depending on your Cypress version, the file will end with `.spec.ts` or `.cy.ts`.
|
||||
Notice the interaction test on the second story, inside the `play` function. This just tests if the default text that appears on generated components exists in the rendered component. You can edit this test to suit your needs. You can read more about interaction tests [here](https://storybook.js.org/docs/react/writing-tests/interaction-testing).
|
||||
|
||||
## More Documentation
|
||||
|
||||
|
||||
@ -18,14 +18,17 @@ In essence, you have a Storybook running, which will be the host of the embeded
|
||||
|
||||
## How to use it
|
||||
|
||||
All you need is a URL of a live Storybook, and a "host" Storybook. In the `.storybook/main.js` file of the "host" Storybook, inside `module.exports` you add a new `refs` attribute, which will contain the link(s) for the composed Storybook(s).
|
||||
All you need is a URL of a live Storybook, and a "host" Storybook. In the `.storybook/main.ts` file of the "host" Storybook, inside `module.exports` you add a new `refs` attribute, which will contain the link(s) for the composed Storybook(s).
|
||||
|
||||
In the example below, we have a host Storybook running on local port 4400 (http://localhost:4400) - not displayed here. In it, we want to compose three other Storybooks. The "one-composed" and "two-composed", running on local ports `4401` and `4402` accordingly, as well as the [Storybook website's Storybook](https://next--storybookjs.netlify.app/official-storybook) which is live on the address that you see.
|
||||
|
||||
```javascript
|
||||
// .storybook/main.js of our Host Storybook - assuming it's running on port 4400
|
||||
module.exports = {
|
||||
...,
|
||||
// .storybook/main.ts of our Host Storybook - assuming it's running on port 4400
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
...
|
||||
|
||||
const config: StorybookConfig = {
|
||||
...
|
||||
refs: {
|
||||
'one-composed': {
|
||||
title: 'One composed',
|
||||
@ -40,14 +43,17 @@ module.exports = {
|
||||
url: 'https://next--storybookjs.netlify.app/official-storybook/',
|
||||
},
|
||||
},
|
||||
...
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
You can always read more in the [official Storybook docs](https://storybook.js.org/docs/angular/workflows/storybook-composition#compose-published-storybooks).
|
||||
You can always read more in the [official Storybook docs](https://storybook.js.org/docs/react/workflows/storybook-composition#compose-published-storybooks).
|
||||
|
||||
## How to use it in Nx
|
||||
|
||||
It's quite easy to use this feature, in Nx and in general, since you do not need to make any code changes, you just need to have the "composed" Storybook instances (the ones you need to "compose") running, choose a "host" Storybook, and just add the composed Storybooks in it's `.storybook/main.js` file.
|
||||
It's quite easy to use this feature, in Nx and in general, since you do not need to make any code changes, you just need to have the "composed" Storybook instances (the ones you need to "compose") running, choose a "host" Storybook, and just add the composed Storybooks in it's `.storybook/main.ts` file.
|
||||
|
||||
Nx provides the [`run-many`](/nx-api/nx/documents/run-many) command, which will allow you to easily run multiple Storybooks at the same time. You need to run the `run-many` command with the parallel flag (eg. `--parallel=3`), because you want to run all your Storybooks in parallel. You can change the value of the `parallel` flag to be of as many Storybooks you want to run in parallel as you need. However, be **very carefull** with putting large numbers in this
|
||||
flag, since it can cause big delays or get stuck. You can play around and adjust that number to one your machine runs comfortably with. Keep in mind that you can add in this feature however many live/public Storybooks as you need (Storybooks that you do not run locally).
|
||||
@ -102,13 +108,16 @@ Since we are using the `--parallel` flag, and the commands are executed in paral
|
||||
If we don't change the port numbers, and there are projects that want to use the same port for their Storybooks, the `run-many` command will change that port, and the result will be that we will not know for sure which
|
||||
of our projects runs on which port. The problem that this creates is that we will not be able to create the proper configuration for Storybook Composition, since we will not be able to tell which URLs our composed Storybooks run on.
|
||||
|
||||
### Add the refs in our host project's `.storybook/main.js` file
|
||||
### Add the refs in our host project's `.storybook/main.ts` file
|
||||
|
||||
Now, we need to add to our host project's `main.js` file (the path of which would be `apps/main-host/.storybook/main.js`) a `refs` object, to configure our composition. An example of such a configuration looks like this:
|
||||
Now, we need to add to our host project's `main.ts` file (the path of which would be `apps/main-host/.storybook/main.ts`) a `refs` object, to configure our composition. An example of such a configuration looks like this:
|
||||
|
||||
```javascript {% fileName="apps/main-host/.storybook/main.js" %}
|
||||
module.exports = {
|
||||
...,
|
||||
```javascript {% fileName="apps/main-host/.storybook/main.ts" %}
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
...
|
||||
|
||||
const config: StorybookConfig = {
|
||||
...
|
||||
refs: {
|
||||
one-composed: {
|
||||
title: 'One composed',
|
||||
@ -123,7 +132,10 @@ module.exports = {
|
||||
url: 'http://localhost:4403',
|
||||
},
|
||||
},
|
||||
...
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
### Optional: use `run-commands` and create a `storybook-composition` target
|
||||
|
||||
@ -11,7 +11,7 @@ import { stripIndents } from '@nx/devkit';
|
||||
// which is v9 while we are testing for the new v10 version
|
||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
||||
|
||||
// TODO(v18): remove Cypress
|
||||
// TODO(katerina): Nx 18 -> remove Cypress
|
||||
|
||||
describe('angularStories generator: applications', () => {
|
||||
let tree: Tree;
|
||||
|
||||
@ -14,7 +14,7 @@ import { angularStoriesGenerator } from './stories';
|
||||
// need to mock cypress otherwise it'll use the nx installed version from package.json
|
||||
// which is v9 while we are testing for the new v10 version
|
||||
jest.mock('@nx/cypress/src/utils/cypress-version');
|
||||
// TODO(v18): remove Cypress
|
||||
// TODO(katerina): Nx 18 -> remove Cypress
|
||||
|
||||
describe('angularStories generator: libraries', () => {
|
||||
const libName = 'test-ui-lib';
|
||||
|
||||
@ -5,7 +5,7 @@ import { generateStorybookConfiguration } from './lib/generate-storybook-configu
|
||||
import { validateOptions } from './lib/validate-options';
|
||||
import type { StorybookConfigurationOptions } from './schema';
|
||||
|
||||
// TODO(v18): remove Cypress
|
||||
// TODO(katerina): Nx 18 -> remove Cypress
|
||||
export async function storybookConfigurationGenerator(
|
||||
tree: Tree,
|
||||
options: StorybookConfigurationOptions
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// TODO(v18): remove Cypress
|
||||
// TODO(katerina): Nx 18 -> remove Cypress
|
||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
||||
import { logger, Tree } from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
import { nxVersion } from '../../utils/versions';
|
||||
|
||||
async function generateStories(host: Tree, schema: StorybookConfigureSchema) {
|
||||
// TODO(v18): remove Cypress
|
||||
// TODO(katerina): Nx 18 -> remove Cypress
|
||||
ensurePackage('@nx/cypress', nxVersion);
|
||||
const { getE2eProjectName } = await import(
|
||||
'@nx/cypress/src/utils/project-name'
|
||||
@ -62,7 +62,7 @@ export async function storybookConfigurationGenerator(
|
||||
tsConfiguration: schema.tsConfiguration ?? true, // default is true
|
||||
interactionTests: schema.interactionTests ?? true, // default is true
|
||||
configureStaticServe: schema.configureStaticServe,
|
||||
uiFramework: uiFramework as any, // cannot import UiFramework7 type dynamically
|
||||
uiFramework: uiFramework as any, // cannot import UiFramework type dynamically
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
|
||||
@ -74,8 +74,8 @@ This is the default configuration for Angular projects using Storybook. You can
|
||||
"outputs": ["{options.outputDir}"],
|
||||
"options": {
|
||||
"outputDir": "dist/storybook/ngapp",
|
||||
"configDir": "libs/ui/.storybook",
|
||||
"browserTarget": "ui:build",
|
||||
"configDir": "apps/ngapp/.storybook",
|
||||
"browserTarget": "ngapp:build",
|
||||
"compodoc": false
|
||||
},
|
||||
"configurations": {
|
||||
@ -97,8 +97,8 @@ You can set the [`browserTarget`](/deprecated/storybook/angular-browser-target)
|
||||
"outputs": ["{options.outputDir}"],
|
||||
"options": {
|
||||
"outputDir": "dist/storybook/ngapp",
|
||||
"configDir": "libs/ui/.storybook",
|
||||
"browserTarget": "ui:build-storybook",
|
||||
"configDir": "apps/ngapp/.storybook",
|
||||
"browserTarget": "ngapp:build-storybook",
|
||||
"compodoc": false
|
||||
},
|
||||
"configurations": {
|
||||
@ -121,8 +121,8 @@ You can add paths to stylesheets to be included in the Storybook build by using
|
||||
"outputs": ["{options.outputDir}"],
|
||||
"options": {
|
||||
"outputDir": "dist/storybook/ngapp",
|
||||
"configDir": "libs/ui/.storybook",
|
||||
"browserTarget": "ui:build-storybook",
|
||||
"configDir": "apps/ngapp/.storybook",
|
||||
"browserTarget": "ngapp:build-storybook",
|
||||
"compodoc": false,
|
||||
"styles": ["some-styles.css"],
|
||||
"stylePreprocessorOptions": {
|
||||
|
||||
@ -9,9 +9,23 @@ This is a framework-agnostic generator for setting up Storybook configuration fo
|
||||
nx g @nx/storybook:configuration
|
||||
```
|
||||
|
||||
Starting Nx 16, Nx does not support Storybook v6 any more. So, Nx will configure your project to use Storybook v7. If you are not on Storybook 7 yet, please migrate. You can read more about how to migrate to Storybook 7 in our [Storybook 7 migration generator](/packages/storybook/generators/migrate-7) guide.
|
||||
{% callout type="info" title="Nx uses Storybook 7" %}
|
||||
Nx does not support Storybook v6 any more. So, Nx will configure your project to use Storybook v7. If you are not on Storybook 7 yet, please migrate. Please follow our [Storybook 7 migration generator](/packages/storybook/generators/migrate-7) guide.
|
||||
{% /callout %}
|
||||
|
||||
When running this generator, you will be prompted to provide the following:
|
||||
If you are using Angular, React, Next.js, Vue or React Native in your project, it's best to use the framework specific Storybook configuration generator:
|
||||
|
||||
- [React Storybook Configuration Generator](/nx-api/react/generators/storybook-configuration) (React and Next.js projects)
|
||||
|
||||
- [Angular Storybook Configuration Generator](/nx-api/angular/generators/storybook-configuration)
|
||||
|
||||
- [Vue Storybook Configuration Generator](/nx-api/vue/generators/storybook-configuration)
|
||||
|
||||
<!-- TODO(katerina): uncomment when Nuxt is in - [Nuxt Storybook Configuration Generator](/nx-api/nuxt/generators/storybook-configuration) -->
|
||||
|
||||
- [React Native Storybook Configuration Generator](/nx-api/react-native/generators/storybook-configuration)
|
||||
|
||||
If you are not using one of the framework-specific generators mentioned above, when running this generator you will be prompted to provide the following:
|
||||
|
||||
- The `name` of the project you want to generate the configuration for.
|
||||
- The `uiFramework` you want to use. Supported values are:
|
||||
@ -37,14 +51,6 @@ You must provide a `name` and a `uiFramework` for the generator to work.
|
||||
|
||||
You can read more about how this generator works, in the [Storybook package overview page](/packages/storybook#generating-storybook-configuration).
|
||||
|
||||
If you are using Angular, React, React Native or Next.js in your project, it's best to use the framework specific generator:
|
||||
|
||||
- [React Storybook Configuration Generator](/nx-api/react/generators/storybook-configuration) (React and Next.js projects)
|
||||
|
||||
- [Angular Storybook Configuration Generator](/nx-api/angular/generators/storybook-configuration)
|
||||
|
||||
- [React Native Storybook Configuration Generator](/nx-api/react-native/generators/storybook-configuration)
|
||||
|
||||
## Examples
|
||||
|
||||
### Generate Storybook configuration using JavaScript
|
||||
|
||||
@ -3,10 +3,6 @@ title: Storybook 7 Migration Generator Examples
|
||||
description: This page contains examples for the @nx/storybook:migrate-7 generator.
|
||||
---
|
||||
|
||||
{% callout type="info" title="Available on Nx v15.9" %}
|
||||
This is a new feature available on Nx v15.9.0. If you are using an older version of Nx, please [upgrade](/packages/nx/documents/migrate).
|
||||
{% /callout %}
|
||||
|
||||
{% callout type="info" title="Setting up Storybook 7 in a new workspace" %}
|
||||
For setting up Storybook version 7 in a new Nx workspace, or a workspace that does NOT already have Storybook configured already, please refer to our [Storybook 7 setup guide](/packages/storybook/documents/storybook-7-setup).
|
||||
{% /callout %}
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
import { ExecutorContext, logger } from '@nx/devkit';
|
||||
import { join } from 'path';
|
||||
import storybookBuilder from './build-storybook.impl';
|
||||
import * as executorContext from '../../utils/test-configs/executor-context.json';
|
||||
jest.mock('@storybook/core-server', () => {
|
||||
// TODO(katerina): Fix when Nx17
|
||||
const buildStaticStandalone = jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve());
|
||||
const build = jest.fn().mockImplementation(() => Promise.resolve());
|
||||
return {
|
||||
buildStaticStandalone,
|
||||
build,
|
||||
};
|
||||
});
|
||||
import * as build from '@storybook/core-server';
|
||||
import { CLIOptions } from '@storybook/types';
|
||||
import { CommonNxStorybookConfig } from '../../utils/models';
|
||||
|
||||
describe('Build storybook', () => {
|
||||
let context: ExecutorContext;
|
||||
let options: CLIOptions & CommonNxStorybookConfig;
|
||||
|
||||
beforeEach(async () => {
|
||||
options = {
|
||||
configDir: join(__dirname, `/../../utils/test-configs/.storybook`),
|
||||
outputDir: `/root/dist/storybook`,
|
||||
};
|
||||
|
||||
context = executorContext as ExecutorContext;
|
||||
});
|
||||
|
||||
it('should call the storybook build', async () => {
|
||||
const loggerSpy = jest.spyOn(logger, 'info');
|
||||
|
||||
const buildSpy = jest
|
||||
.spyOn(build, 'build')
|
||||
.mockImplementation(() => Promise.resolve());
|
||||
|
||||
const result = await storybookBuilder(options, context);
|
||||
|
||||
expect(buildSpy).toHaveBeenCalled();
|
||||
expect(loggerSpy).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'NX Storybook builder starting ...'
|
||||
);
|
||||
expect(loggerSpy).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'NX Storybook builder finished ...'
|
||||
);
|
||||
expect(loggerSpy).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'NX Storybook files available in /root/dist/storybook'
|
||||
);
|
||||
|
||||
expect(result.success).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -6,73 +6,33 @@ import {
|
||||
storybookConfigExistsCheck,
|
||||
storybookMajorVersion,
|
||||
} from '../../utils/utilities';
|
||||
import { CommonNxStorybookConfig } from '../../utils/models';
|
||||
import { getStorybookFrameworkPath, runStorybookSetupCheck } from '../utils';
|
||||
|
||||
export default async function buildStorybookExecutor(
|
||||
options: CLIOptions & CommonNxStorybookConfig,
|
||||
options: CLIOptions,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
storybookConfigExistsCheck(options.configDir, context.projectName);
|
||||
const storybook7 = storybookMajorVersion() === 7;
|
||||
if (storybook7) {
|
||||
const buildOptions: CLIOptions = options;
|
||||
logger.info(`NX Storybook builder starting ...`);
|
||||
await runInstance(buildOptions, storybook7);
|
||||
logger.info(`NX Storybook builder finished ...`);
|
||||
logger.info(`NX Storybook files available in ${buildOptions.outputDir}`);
|
||||
return { success: true };
|
||||
} else {
|
||||
// TODO(katerina): Remove Nx17
|
||||
// print warnings
|
||||
runStorybookSetupCheck(options);
|
||||
logger.error(pleaseUpgrade());
|
||||
|
||||
const frameworkPath = getStorybookFrameworkPath(options.uiFramework);
|
||||
const { default: frameworkOptions } = await import(frameworkPath);
|
||||
|
||||
const buildOptions: CLIOptions = {
|
||||
...options,
|
||||
...frameworkOptions,
|
||||
frameworkPresets: [...(frameworkOptions.frameworkPresets || [])],
|
||||
};
|
||||
|
||||
logger.info(`NX Storybook builder starting ...`);
|
||||
await runInstance(buildOptions, storybook7);
|
||||
logger.info(`NX Storybook builder finished ...`);
|
||||
logger.info(`NX Storybook files available in ${buildOptions.outputDir}`);
|
||||
return { success: true };
|
||||
if (!storybook7) {
|
||||
throw pleaseUpgrade();
|
||||
}
|
||||
const buildOptions: CLIOptions = options;
|
||||
logger.info(`NX Storybook builder starting ...`);
|
||||
await runInstance(buildOptions);
|
||||
logger.info(`NX Storybook builder finished ...`);
|
||||
logger.info(`NX Storybook files available in ${buildOptions.outputDir}`);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
function runInstance(
|
||||
options: CLIOptions,
|
||||
storybook7: boolean
|
||||
): Promise<void | {
|
||||
function runInstance(options: CLIOptions): Promise<void | {
|
||||
port: number;
|
||||
address: string;
|
||||
networkAddress: string;
|
||||
}> {
|
||||
const env = process.env.NODE_ENV ?? 'production';
|
||||
process.env.NODE_ENV = env;
|
||||
|
||||
if (storybook7) {
|
||||
return build.build({
|
||||
...options,
|
||||
mode: 'static',
|
||||
});
|
||||
} else {
|
||||
// TODO(katerina): Remove Nx17
|
||||
const nodeVersion = process.version.slice(1).split('.');
|
||||
if (+nodeVersion[0] === 18) {
|
||||
logger.warn(`
|
||||
If you are using the @storybook/builder-vite you may experience issues with Node 18.
|
||||
Please use Node 16 if you are using @storybook/builder-vite.
|
||||
`);
|
||||
}
|
||||
return build.buildStaticStandalone({
|
||||
...options,
|
||||
ci: true,
|
||||
} as any);
|
||||
}
|
||||
return build.build({
|
||||
...options,
|
||||
mode: 'static',
|
||||
});
|
||||
}
|
||||
|
||||
@ -69,19 +69,6 @@
|
||||
"type": "boolean",
|
||||
"description": "Starts Storybook in documentation mode. Learn more about it : https://storybook.js.org/docs/react/writing-docs/build-documentation#preview-storybooks-documentation."
|
||||
},
|
||||
"uiFramework": {
|
||||
"type": "string",
|
||||
"description": "Storybook framework npm package.",
|
||||
"enum": [
|
||||
"@storybook/react",
|
||||
"@storybook/html",
|
||||
"@storybook/web-components",
|
||||
"@storybook/vue",
|
||||
"@storybook/vue3",
|
||||
"@storybook/svelte"
|
||||
],
|
||||
"x-deprecated": "Upgrade to Storybook 7."
|
||||
},
|
||||
"webpackStatsJson": {
|
||||
"type": ["boolean", "string"],
|
||||
"description": "Write Webpack Stats JSON to disk.",
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
import { ExecutorContext } from '@nx/devkit';
|
||||
|
||||
jest.mock('@storybook/core-server', () => ({
|
||||
// TODO(katerina): Fix when Nx17
|
||||
buildDev: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
build: jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
port: 4400,
|
||||
})
|
||||
),
|
||||
}));
|
||||
import { build } from '@storybook/core-server';
|
||||
|
||||
import storybookExecutor from './storybook.impl';
|
||||
import { join } from 'path';
|
||||
import { CLIOptions } from '@storybook/types';
|
||||
import { CommonNxStorybookConfig } from '../../utils/models';
|
||||
|
||||
describe('@nx/storybook:storybook', () => {
|
||||
let context: ExecutorContext;
|
||||
let options: CLIOptions & CommonNxStorybookConfig;
|
||||
beforeEach(() => {
|
||||
const rootPath = join(__dirname, `../../../../../`);
|
||||
|
||||
options = {
|
||||
port: 4400,
|
||||
configDir: join(__dirname, `/../../utils/test-configs/.storybook`),
|
||||
};
|
||||
|
||||
context = {
|
||||
root: rootPath,
|
||||
cwd: rootPath,
|
||||
projectName: 'proj',
|
||||
targetName: 'storybook',
|
||||
projectsConfigurations: {
|
||||
version: 2,
|
||||
projects: {
|
||||
proj: {
|
||||
root: '',
|
||||
sourceRoot: 'src',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@nx/web:webpack',
|
||||
options: {},
|
||||
},
|
||||
storybook: {
|
||||
executor: '@nx/storybook:storybook',
|
||||
options,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nxJsonConfiguration: {},
|
||||
isVerbose: false,
|
||||
};
|
||||
});
|
||||
|
||||
it('should provide options to storybook', async () => {
|
||||
const iterator = storybookExecutor(options, context);
|
||||
const { value } = await iterator.next();
|
||||
expect(value).toEqual({
|
||||
success: true,
|
||||
info: {
|
||||
baseUrl: 'http://localhost:4400',
|
||||
port: 4400,
|
||||
},
|
||||
});
|
||||
expect(build).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@ -1,77 +1,47 @@
|
||||
import { ExecutorContext, logger } from '@nx/devkit';
|
||||
import { ExecutorContext } from '@nx/devkit';
|
||||
import * as build from '@storybook/core-server';
|
||||
import {
|
||||
pleaseUpgrade,
|
||||
storybookConfigExistsCheck,
|
||||
storybookMajorVersion,
|
||||
} from '../../utils/utilities';
|
||||
import { getStorybookFrameworkPath, runStorybookSetupCheck } from '../utils';
|
||||
import { CLIOptions } from '@storybook/types'; // TODO(katerina): Remove Nx17
|
||||
import { CommonNxStorybookConfig } from '../../utils/models';
|
||||
import { CLIOptions } from '@storybook/types';
|
||||
|
||||
export default async function* storybookExecutor(
|
||||
options: CLIOptions & CommonNxStorybookConfig,
|
||||
options: CLIOptions,
|
||||
context: ExecutorContext
|
||||
): AsyncGenerator<{
|
||||
success: boolean;
|
||||
info?: { port: number; baseUrl?: string };
|
||||
}> {
|
||||
const storybook7 = storybookMajorVersion() === 7;
|
||||
storybookConfigExistsCheck(options.configDir, context.projectName);
|
||||
if (storybook7) {
|
||||
const buildOptions: CLIOptions = options;
|
||||
const result = await runInstance(buildOptions, storybook7);
|
||||
yield {
|
||||
success: true,
|
||||
info: {
|
||||
port: result?.['port'],
|
||||
baseUrl: `${options.https ? 'https' : 'http'}://${
|
||||
options.host ?? 'localhost'
|
||||
}:${result?.['port']}`,
|
||||
},
|
||||
};
|
||||
await new Promise<{ success: boolean }>(() => {});
|
||||
} else {
|
||||
// TODO(katerina): Remove Nx17
|
||||
// print warnings
|
||||
runStorybookSetupCheck(options);
|
||||
logger.error(pleaseUpgrade());
|
||||
|
||||
let frameworkPath = getStorybookFrameworkPath(options.uiFramework);
|
||||
const frameworkOptions = (await import(frameworkPath)).default;
|
||||
const buildOptions: CLIOptions = {
|
||||
...options,
|
||||
...frameworkOptions,
|
||||
frameworkPresets: [...(frameworkOptions.frameworkPresets || [])],
|
||||
};
|
||||
|
||||
await runInstance(buildOptions, storybook7);
|
||||
yield { success: true };
|
||||
await new Promise<{ success: boolean }>(() => {});
|
||||
if (!storybook7) {
|
||||
throw pleaseUpgrade();
|
||||
}
|
||||
storybookConfigExistsCheck(options.configDir, context.projectName);
|
||||
const buildOptions: CLIOptions = options;
|
||||
const result = await runInstance(buildOptions);
|
||||
yield {
|
||||
success: true,
|
||||
info: {
|
||||
port: result?.['port'],
|
||||
baseUrl: `${options.https ? 'https' : 'http'}://${
|
||||
options.host ?? 'localhost'
|
||||
}:${result?.['port']}`,
|
||||
},
|
||||
};
|
||||
await new Promise<{ success: boolean }>(() => {});
|
||||
}
|
||||
|
||||
function runInstance(
|
||||
options: CLIOptions,
|
||||
storybook7: boolean
|
||||
): Promise<void | {
|
||||
function runInstance(options: CLIOptions): Promise<void | {
|
||||
port: number;
|
||||
address: string;
|
||||
networkAddress: string;
|
||||
}> {
|
||||
const env = process.env.NODE_ENV ?? 'development';
|
||||
process.env.NODE_ENV = env;
|
||||
if (storybook7) {
|
||||
return build.build({
|
||||
...options,
|
||||
mode: 'dev',
|
||||
});
|
||||
} else {
|
||||
// TODO(katerina): Remove Nx17
|
||||
return build['buildDev']({
|
||||
...options,
|
||||
configType: env.toUpperCase(),
|
||||
mode: 'dev',
|
||||
} as any);
|
||||
}
|
||||
return build.build({
|
||||
...options,
|
||||
mode: 'dev',
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,169 +0,0 @@
|
||||
// TODO(katerina): Remove Nx17 - DELETE whole file
|
||||
|
||||
import { joinPathFragments, logger } from '@nx/devkit';
|
||||
import { findNodes } from '@nx/js';
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { gte } from 'semver';
|
||||
import ts = require('typescript');
|
||||
import { CommonNxStorybookConfig } from '../utils/models';
|
||||
import { CLIOptions } from '@storybook/types';
|
||||
|
||||
export function getStorybookFrameworkPath(uiFramework) {
|
||||
const serverOptionsPaths = {
|
||||
'@storybook/react': '@storybook/react/dist/cjs/server/options',
|
||||
'@storybook/html': '@storybook/html/dist/cjs/server/options',
|
||||
'@storybook/vue': '@storybook/vue/dist/cjs/server/options',
|
||||
'@storybook/vue3': '@storybook/vue3/dist/cjs/server/options',
|
||||
'@storybook/web-components':
|
||||
'@storybook/web-components/dist/cjs/server/options',
|
||||
'@storybook/svelte': '@storybook/svelte/dist/cjs/server/options',
|
||||
};
|
||||
|
||||
if (isStorybookV62onwards(uiFramework)) {
|
||||
return serverOptionsPaths[uiFramework];
|
||||
} else {
|
||||
return `${uiFramework}/dist/server/options`;
|
||||
}
|
||||
}
|
||||
|
||||
function isStorybookV62onwards(uiFramework: string) {
|
||||
try {
|
||||
const storybookPackageVersion = require(join(
|
||||
uiFramework,
|
||||
'package.json'
|
||||
)).version;
|
||||
return gte(storybookPackageVersion, '6.2.0-rc.4');
|
||||
} catch (e) {
|
||||
try {
|
||||
const storybookPackageVersion = require(join(
|
||||
'@storybook/core-server',
|
||||
'package.json'
|
||||
)).version;
|
||||
return gte(storybookPackageVersion, '6.2.0-rc.4');
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Error: ${e}
|
||||
|
||||
It looks like you don\'t have Storybook installed.
|
||||
Please run the @nx/storybook:configuration generator,
|
||||
or run "npm/yarn" again to install your dependencies.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function runStorybookSetupCheck(
|
||||
options: CLIOptions & CommonNxStorybookConfig
|
||||
) {
|
||||
webpackFinalPropertyCheck(options);
|
||||
reactWebpack5Check(options);
|
||||
}
|
||||
|
||||
function reactWebpack5Check(options: CLIOptions & CommonNxStorybookConfig) {
|
||||
if (options.uiFramework === '@storybook/react') {
|
||||
const source = mainJsTsFileContent(options.configDir);
|
||||
const rootSource = mainJsTsFileContent('.storybook');
|
||||
// check whether the current Storybook configuration has the webpack 5 builder enabled
|
||||
if (
|
||||
builderIsWebpackButNotWebpack5(source) &&
|
||||
builderIsWebpackButNotWebpack5(rootSource)
|
||||
) {
|
||||
logger.warn(`
|
||||
It looks like you use Webpack 5 but your Storybook setup is not configured to leverage that
|
||||
and thus falls back to Webpack 4.
|
||||
Make sure you upgrade your Storybook config to use Webpack 5.
|
||||
|
||||
- https://gist.github.com/shilman/8856ea1786dcd247139b47b270912324#upgrade
|
||||
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mainJsTsFileContent(configFolder: string): ts.SourceFile {
|
||||
let storybookConfigFilePath = joinPathFragments(configFolder, 'main.js');
|
||||
|
||||
if (!existsSync(storybookConfigFilePath)) {
|
||||
storybookConfigFilePath = joinPathFragments(configFolder, 'main.ts');
|
||||
}
|
||||
|
||||
if (!existsSync(storybookConfigFilePath)) {
|
||||
// looks like there's no main config file, so skip
|
||||
return;
|
||||
}
|
||||
|
||||
const storybookConfig = readFileSync(storybookConfigFilePath, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
|
||||
return ts.createSourceFile(
|
||||
storybookConfigFilePath,
|
||||
storybookConfig,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
function webpackFinalPropertyCheck(
|
||||
options: CLIOptions & CommonNxStorybookConfig
|
||||
) {
|
||||
let placesToCheck = [
|
||||
{
|
||||
path: joinPathFragments('.storybook', 'webpack.config.js'),
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
path: joinPathFragments(options.configDir, 'webpack.config.js'),
|
||||
result: false,
|
||||
},
|
||||
];
|
||||
|
||||
placesToCheck = placesToCheck
|
||||
.map((entry) => {
|
||||
return {
|
||||
...entry,
|
||||
result: existsSync(entry.path),
|
||||
};
|
||||
})
|
||||
.filter((x) => x.result === true);
|
||||
|
||||
if (placesToCheck.length > 0) {
|
||||
logger.warn(
|
||||
`
|
||||
You have a webpack.config.js files in your Storybook configuration:
|
||||
${placesToCheck.map((x) => `- "${x.path}"`).join('\n ')}
|
||||
|
||||
Consider switching to the "webpackFinal" property declared in "main.js" (or "main.ts") instead.
|
||||
${
|
||||
options.uiFramework === '@storybook/react'
|
||||
? 'https://nx.dev/storybook/migrate-webpack-final-react'
|
||||
: 'https://nx.dev/storybook/migrate-webpack-final-angular'
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function builderIsWebpackButNotWebpack5(
|
||||
storybookConfig: ts.SourceFile
|
||||
): boolean {
|
||||
const importArray = findNodes(storybookConfig, [
|
||||
ts.SyntaxKind.PropertyAssignment,
|
||||
]);
|
||||
let builderIsWebpackNot5 = false;
|
||||
importArray.forEach((parent) => {
|
||||
const identifier = findNodes(parent, ts.SyntaxKind.Identifier);
|
||||
const sbBuilder = findNodes(parent, ts.SyntaxKind.StringLiteral);
|
||||
const builderText = sbBuilder?.[0]?.getText() ?? '';
|
||||
if (
|
||||
identifier?.[0]?.getText() === 'builder' &&
|
||||
builderText.includes('webpack') &&
|
||||
!builderText.includes('webpack5')
|
||||
) {
|
||||
builderIsWebpackNot5 = true;
|
||||
}
|
||||
});
|
||||
|
||||
return builderIsWebpackNot5;
|
||||
}
|
||||
@ -146,7 +146,7 @@ export async function configurationGenerator(
|
||||
addStaticTarget(tree, schema);
|
||||
}
|
||||
|
||||
// TODO(v18): remove Cypress
|
||||
// TODO(katerina): Nx 18 -> remove Cypress
|
||||
if (schema.configureCypress) {
|
||||
const e2eProject = await getE2EProjectName(tree, schema.name);
|
||||
if (!e2eProject) {
|
||||
|
||||
@ -26,7 +26,7 @@ import {
|
||||
TsConfig,
|
||||
} from '../../../utils/utilities';
|
||||
import { StorybookConfigureSchema } from '../schema';
|
||||
import { UiFramework7 } from '../../../utils/models';
|
||||
import { UiFramework } from '../../../utils/models';
|
||||
import { nxVersion } from '../../../utils/versions';
|
||||
import { findEslintFile } from '@nx/linter/src/generators/utils/eslint-file';
|
||||
import { useFlatConfig } from '@nx/linter/src/utils/flat-config';
|
||||
@ -36,7 +36,7 @@ const DEFAULT_PORT = 4400;
|
||||
export function addStorybookTask(
|
||||
tree: Tree,
|
||||
projectName: string,
|
||||
uiFramework: string,
|
||||
uiFramework: UiFramework,
|
||||
interactionTests: boolean
|
||||
) {
|
||||
if (uiFramework === '@storybook/react-native') {
|
||||
@ -160,7 +160,7 @@ export function addStaticTarget(tree: Tree, opts: StorybookConfigureSchema) {
|
||||
export function createStorybookTsconfigFile(
|
||||
tree: Tree,
|
||||
projectRoot: string,
|
||||
uiFramework: UiFramework7,
|
||||
uiFramework: UiFramework,
|
||||
isRootProject: boolean,
|
||||
mainDir: 'components' | 'src'
|
||||
) {
|
||||
@ -525,7 +525,7 @@ export function addStorybookToNamedInputs(tree: Tree) {
|
||||
export function createProjectStorybookDir(
|
||||
tree: Tree,
|
||||
projectName: string,
|
||||
uiFramework: UiFramework7,
|
||||
uiFramework: UiFramework,
|
||||
js: boolean,
|
||||
tsConfiguration: boolean,
|
||||
root: string,
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Linter } from '@nx/linter';
|
||||
import { UiFramework7, UiFramework } from '../../utils/models';
|
||||
import { UiFramework } from '../../utils/models';
|
||||
|
||||
export interface StorybookConfigureSchema {
|
||||
name: string;
|
||||
uiFramework?: UiFramework7;
|
||||
uiFramework?: UiFramework;
|
||||
linter?: Linter;
|
||||
js?: boolean;
|
||||
interactionTests?: boolean;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { UiFramework7 } from '../../utils/models';
|
||||
import { UiFramework } from '../../utils/models';
|
||||
|
||||
export interface Schema {
|
||||
uiFramework: UiFramework7;
|
||||
uiFramework: UiFramework;
|
||||
js?: boolean;
|
||||
}
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { UiFramework, UiFramework7 } from '../../utils/models';
|
||||
|
||||
export interface Schema {
|
||||
autoAcceptAllPrompts?: boolean;
|
||||
onlyShowListOfCommands?: boolean;
|
||||
|
||||
@ -1,15 +1,4 @@
|
||||
export interface CommonNxStorybookConfig {
|
||||
uiFramework?:
|
||||
| '@storybook/angular'
|
||||
| '@storybook/react'
|
||||
| '@storybook/html'
|
||||
| '@storybook/web-components'
|
||||
| '@storybook/vue'
|
||||
| '@storybook/vue3'
|
||||
| '@storybook/svelte'; // TODO(katerina): Remove when Storybook 7
|
||||
}
|
||||
|
||||
export type UiFramework7 =
|
||||
export type UiFramework =
|
||||
| '@storybook/angular'
|
||||
| '@storybook/html-webpack5'
|
||||
| '@storybook/nextjs'
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
module.exports = {
|
||||
core: { builder: 'webpack5' },
|
||||
stories: [
|
||||
'../src/app/**/*.stories.mdx',
|
||||
'../src/app/**/*.stories.@(js|jsx|ts|tsx)',
|
||||
],
|
||||
addons: ['@storybook/addon-essentials', '@nx/react/plugins/storybook'],
|
||||
};
|
||||
@ -1,19 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": ""
|
||||
},
|
||||
"exclude": [
|
||||
"../**/*.spec.ts",
|
||||
"../**/*.spec.js",
|
||||
"../**/*.spec.tsx",
|
||||
"../**/*.spec.jsx"
|
||||
],
|
||||
"include": [
|
||||
"../src/**/*.stories.ts",
|
||||
"../src/**/*.stories.js",
|
||||
"../src/**/*.stories.jsx",
|
||||
"../src/**/*.stories.tsx",
|
||||
"../src/**/*.stories.mdx",
|
||||
"*.js"
|
||||
]
|
||||
}
|
||||
@ -262,9 +262,10 @@ export function getTsSourceFile(host: Tree, path: string): ts.SourceFile {
|
||||
|
||||
export function pleaseUpgrade(): string {
|
||||
return `
|
||||
Storybook 6 is no longer maintained. Please upgrade to Storybook 7.
|
||||
Storybook 6 is no longer maintained, and not supported in Nx.
|
||||
Please upgrade to Storybook 7.
|
||||
|
||||
Here is a guide on how to upgrade:
|
||||
https://nx.dev/packages/storybook/generators/migrate-7
|
||||
https://nx.dev/nx-api/storybook/generators/migrate-7
|
||||
`;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user