feat(vue): storybook configuration generator for vue (#19141)
This commit is contained in:
parent
76bc58d407
commit
295ea3fb93
@ -1711,6 +1711,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Set up Storybook for Vue Projects",
|
||||
"path": "/recipes/storybook/overview-vue",
|
||||
"id": "overview-vue",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Set up Storybook for Angular Projects",
|
||||
"path": "/recipes/storybook/overview-angular",
|
||||
@ -2900,6 +2908,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Set up Storybook for Vue Projects",
|
||||
"path": "/recipes/storybook/overview-vue",
|
||||
"id": "overview-vue",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Set up Storybook for Angular Projects",
|
||||
"path": "/recipes/storybook/overview-angular",
|
||||
@ -2991,6 +3007,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Set up Storybook for Vue Projects",
|
||||
"path": "/recipes/storybook/overview-vue",
|
||||
"id": "overview-vue",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Set up Storybook for Angular Projects",
|
||||
"path": "/recipes/storybook/overview-angular",
|
||||
|
||||
@ -2133,6 +2133,16 @@
|
||||
"path": "/recipes/storybook/overview-react",
|
||||
"tags": ["storybook"]
|
||||
},
|
||||
{
|
||||
"id": "overview-vue",
|
||||
"name": "Set up Storybook for Vue Projects",
|
||||
"description": "This guide explains how to set up Storybook for Vue projects in your Nx workspace.",
|
||||
"file": "shared/recipes/storybook/plugin-vue",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/storybook/overview-vue",
|
||||
"tags": ["storybook"]
|
||||
},
|
||||
{
|
||||
"id": "overview-angular",
|
||||
"name": "Set up Storybook for Angular Projects",
|
||||
@ -3616,6 +3626,16 @@
|
||||
"path": "/recipes/storybook/overview-react",
|
||||
"tags": ["storybook"]
|
||||
},
|
||||
{
|
||||
"id": "overview-vue",
|
||||
"name": "Set up Storybook for Vue Projects",
|
||||
"description": "This guide explains how to set up Storybook for Vue projects in your Nx workspace.",
|
||||
"file": "shared/recipes/storybook/plugin-vue",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/storybook/overview-vue",
|
||||
"tags": ["storybook"]
|
||||
},
|
||||
{
|
||||
"id": "overview-angular",
|
||||
"name": "Set up Storybook for Angular Projects",
|
||||
@ -3731,6 +3751,16 @@
|
||||
"path": "/recipes/storybook/overview-react",
|
||||
"tags": ["storybook"]
|
||||
},
|
||||
"/recipes/storybook/overview-vue": {
|
||||
"id": "overview-vue",
|
||||
"name": "Set up Storybook for Vue Projects",
|
||||
"description": "This guide explains how to set up Storybook for Vue projects in your Nx workspace.",
|
||||
"file": "shared/recipes/storybook/plugin-vue",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/storybook/overview-vue",
|
||||
"tags": ["storybook"]
|
||||
},
|
||||
"/recipes/storybook/overview-angular": {
|
||||
"id": "overview-angular",
|
||||
"name": "Set up Storybook for Angular Projects",
|
||||
|
||||
@ -892,6 +892,13 @@
|
||||
"name": "Set up Storybook for React Projects",
|
||||
"path": "/recipes/storybook/overview-react"
|
||||
},
|
||||
{
|
||||
"description": "This guide explains how to set up Storybook for Vue projects in your Nx workspace.",
|
||||
"file": "shared/recipes/storybook/plugin-vue",
|
||||
"id": "overview-vue",
|
||||
"name": "Set up Storybook for Vue Projects",
|
||||
"path": "/recipes/storybook/overview-vue"
|
||||
},
|
||||
{
|
||||
"description": "This guide explains how to set up Storybook for Angular projects in your Nx workspace.",
|
||||
"file": "shared/recipes/storybook/plugin-angular",
|
||||
|
||||
@ -53,7 +53,42 @@ You can generate Storybook configuration for an individual project with this com
|
||||
nx g @nx/storybook:configuration project-name
|
||||
```
|
||||
|
||||
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)), in the field `uiFramework` you must choose one of the following Storybook frameworks:
|
||||
or
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Angular" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/angular:storybook-configuration my-angular-project
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="React" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/react:storybook-configuration my-react-project
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Vue" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/vue:storybook-configuration my-vue-project
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="React Native" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/react-native:storybook-configuration my-react-native-project
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
These framework-specific generators will also **generate stories** 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:
|
||||
|
||||
- `@storybook/angular`
|
||||
- `@storybook/html-webpack5`
|
||||
@ -82,43 +117,7 @@ Choosing one of these frameworks will have the following effects on your workspa
|
||||
|
||||
4. Nx will generate a new Cypress e2e app for your project (if there isn't one already) to run against the Storybook instance.
|
||||
|
||||
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):
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Angular" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/angular:storybook-configuration my-angular-project
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="React" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/react:storybook-configuration my-react-project
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="React Native" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/react-native:storybook-configuration my-react-native-project
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
These framework-specific generators will also **generate stories** for you.
|
||||
|
||||
### Configure your project using TypeScript
|
||||
|
||||
You can choose to configure your project using TypeScript instead of JavaScript. To do that, just add the `--tsConfiguration=true` flag to the above command, like this:
|
||||
|
||||
```shell
|
||||
nx g @nx/storybook:configuration project-name --tsConfiguration=true
|
||||
```
|
||||
|
||||
[Here is the Storybook documentation](https://storybook.js.org/docs/react/configure/overview#configure-your-project-with-typescript) if you want to learn more about configuring your project with TypeScript.
|
||||
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.
|
||||
|
||||
### Running Storybook
|
||||
|
||||
@ -157,12 +156,12 @@ The project-specific Storybook configuration is pretty much similar to what you
|
||||
```text
|
||||
<project root>/
|
||||
├── .storybook/
|
||||
│ ├── main.js
|
||||
│ ├── preview.js
|
||||
│ ├── tsconfig.json
|
||||
│ ├── main.ts
|
||||
│ └── preview.ts
|
||||
├── src/
|
||||
├── README.md
|
||||
├── tsconfig.json
|
||||
├── tsconfig.storybook.json
|
||||
└── etc...
|
||||
```
|
||||
|
||||
@ -170,7 +169,7 @@ The project-specific Storybook configuration is pretty much similar to what you
|
||||
|
||||
To register a [Storybook addon](https://storybook.js.org/addons/) for all Storybook instances in your workspace:
|
||||
|
||||
1. In your project's `.storybook/main.js` file, in the `addons` array of the `module.exports` object, add the new addon:
|
||||
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 = {
|
||||
@ -180,7 +179,7 @@ To register a [Storybook addon](https://storybook.js.org/addons/) for all Storyb
|
||||
};
|
||||
```
|
||||
|
||||
2. If a decorator is required, in each project's `<project-path>/.storybook/preview.js`, you can export an array called `decorators`.
|
||||
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" %}
|
||||
import someDecorator from 'some-storybook-addon';
|
||||
@ -199,6 +198,7 @@ You can find dedicated information for React and Angular:
|
||||
|
||||
- [Set up Storybook for Angular Projects](/recipes/storybook/overview-angular)
|
||||
- [Set up Storybook for React Projects](/recipes/storybook/overview-react)
|
||||
- [Set up Storybook for Vue Projects](/recipes/storybook/overview-vue)
|
||||
|
||||
You can find all Storybook-related Nx documentation in the [Storybook recipes section](/recipes/storybook).
|
||||
|
||||
|
||||
@ -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](/packages/react/generators/storybook-configuration) (React and Next.js projects)\n\n- [Angular Storybook Configuration Generator](/packages/angular/generators/storybook-configuration)\n\n- [React Native Storybook Configuration Generator](/packages/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\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",
|
||||
"presets": []
|
||||
},
|
||||
"description": "Add Storybook configuration to a UI library or an application.",
|
||||
|
||||
@ -656,6 +656,13 @@
|
||||
"description": "This guide explains how to set up Storybook for React projects in your Nx workspace.",
|
||||
"file": "shared/recipes/storybook/plugin-react"
|
||||
},
|
||||
{
|
||||
"name": "Set up Storybook for Vue Projects",
|
||||
"id": "overview-vue",
|
||||
"tags": ["storybook"],
|
||||
"description": "This guide explains how to set up Storybook for Vue projects in your Nx workspace.",
|
||||
"file": "shared/recipes/storybook/plugin-vue"
|
||||
},
|
||||
{
|
||||
"name": "Set up Storybook for Angular Projects",
|
||||
"id": "overview-angular",
|
||||
|
||||
@ -53,7 +53,42 @@ You can generate Storybook configuration for an individual project with this com
|
||||
nx g @nx/storybook:configuration project-name
|
||||
```
|
||||
|
||||
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)), in the field `uiFramework` you must choose one of the following Storybook frameworks:
|
||||
or
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Angular" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/angular:storybook-configuration my-angular-project
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="React" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/react:storybook-configuration my-react-project
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Vue" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/vue:storybook-configuration my-vue-project
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="React Native" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/react-native:storybook-configuration my-react-native-project
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
These framework-specific generators will also **generate stories** 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:
|
||||
|
||||
- `@storybook/angular`
|
||||
- `@storybook/html-webpack5`
|
||||
@ -82,43 +117,7 @@ Choosing one of these frameworks will have the following effects on your workspa
|
||||
|
||||
4. Nx will generate a new Cypress e2e app for your project (if there isn't one already) to run against the Storybook instance.
|
||||
|
||||
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):
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Angular" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/angular:storybook-configuration my-angular-project
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="React" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/react:storybook-configuration my-react-project
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="React Native" %}
|
||||
|
||||
```shell
|
||||
nx g @nx/react-native:storybook-configuration my-react-native-project
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
These framework-specific generators will also **generate stories** for you.
|
||||
|
||||
### Configure your project using TypeScript
|
||||
|
||||
You can choose to configure your project using TypeScript instead of JavaScript. To do that, just add the `--tsConfiguration=true` flag to the above command, like this:
|
||||
|
||||
```shell
|
||||
nx g @nx/storybook:configuration project-name --tsConfiguration=true
|
||||
```
|
||||
|
||||
[Here is the Storybook documentation](https://storybook.js.org/docs/react/configure/overview#configure-your-project-with-typescript) if you want to learn more about configuring your project with TypeScript.
|
||||
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.
|
||||
|
||||
### Running Storybook
|
||||
|
||||
@ -157,12 +156,12 @@ The project-specific Storybook configuration is pretty much similar to what you
|
||||
```text
|
||||
<project root>/
|
||||
├── .storybook/
|
||||
│ ├── main.js
|
||||
│ ├── preview.js
|
||||
│ ├── tsconfig.json
|
||||
│ ├── main.ts
|
||||
│ └── preview.ts
|
||||
├── src/
|
||||
├── README.md
|
||||
├── tsconfig.json
|
||||
├── tsconfig.storybook.json
|
||||
└── etc...
|
||||
```
|
||||
|
||||
@ -170,7 +169,7 @@ The project-specific Storybook configuration is pretty much similar to what you
|
||||
|
||||
To register a [Storybook addon](https://storybook.js.org/addons/) for all Storybook instances in your workspace:
|
||||
|
||||
1. In your project's `.storybook/main.js` file, in the `addons` array of the `module.exports` object, add the new addon:
|
||||
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 = {
|
||||
@ -180,7 +179,7 @@ To register a [Storybook addon](https://storybook.js.org/addons/) for all Storyb
|
||||
};
|
||||
```
|
||||
|
||||
2. If a decorator is required, in each project's `<project-path>/.storybook/preview.js`, you can export an array called `decorators`.
|
||||
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" %}
|
||||
import someDecorator from 'some-storybook-addon';
|
||||
@ -199,6 +198,7 @@ You can find dedicated information for React and Angular:
|
||||
|
||||
- [Set up Storybook for Angular Projects](/recipes/storybook/overview-angular)
|
||||
- [Set up Storybook for React Projects](/recipes/storybook/overview-react)
|
||||
- [Set up Storybook for Vue Projects](/recipes/storybook/overview-vue)
|
||||
|
||||
You can find all Storybook-related Nx documentation in the [Storybook recipes section](/recipes/storybook).
|
||||
|
||||
|
||||
80
docs/shared/recipes/storybook/plugin-vue.md
Normal file
80
docs/shared/recipes/storybook/plugin-vue.md
Normal file
@ -0,0 +1,80 @@
|
||||
---
|
||||
title: Set up Storybook for Vue Projects
|
||||
description: This guide explains how to set up Storybook for Vue projects in your Nx workspace.
|
||||
---
|
||||
|
||||
# Set up Storybook for Vue Projects
|
||||
|
||||
This guide will walk you through setting up [Storybook](https://storybook.js.org) for Vue projects in your Nx workspace.
|
||||
|
||||
{% callout type="warning" title="Set up Storybook in your workspace" %}
|
||||
You first need to set up Storybook for your Nx workspace, if you haven't already. You can read the [Storybook plugin overview guide](/nx-api/storybook) to get started.
|
||||
{% /callout %}
|
||||
|
||||
## Generate Storybook Configuration for a Vue project
|
||||
|
||||
You can generate Storybook configuration for an individual Vue project by using the [`@nx/vue:storybook-configuration` generator](/nx-api/vue/generators/storybook-configuration), like this:
|
||||
|
||||
```shell
|
||||
nx g @nx/vue:storybook-configuration project-name
|
||||
```
|
||||
|
||||
## Auto-generate Stories
|
||||
|
||||
The [`@nx/vue:storybook-configuration` generator](/nx-api/vue/generators/storybook-configuration) has the option to automatically generate `*.stories.ts` 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/).
|
||||
|
||||
```text
|
||||
<some-folder>/
|
||||
├── MyComponent.vue
|
||||
└── MyComponent.stories.ts
|
||||
```
|
||||
|
||||
If you add more components to your project, and want to generate stories for all your (new) components at any point, you can use the [`@nx/vue:stories` generator](/nx-api/vue/generators/stories):
|
||||
|
||||
```shell
|
||||
nx g @nx/vue:stories --project=<project-name>
|
||||
```
|
||||
|
||||
{% callout type="note" title="Example" %}
|
||||
Let's take for a example a library in your workspace, under `libs/feature/ui`, called `feature-ui`. This library contains a component, called `my-button`.
|
||||
|
||||
The command to generate stories for that library would be:
|
||||
|
||||
```shell
|
||||
nx g @nx/vue:stories --project=feature-ui
|
||||
```
|
||||
|
||||
and the result would be the following:
|
||||
|
||||
```text
|
||||
<workspace name>/
|
||||
├── apps/
|
||||
├── libs/
|
||||
│ ├── feature/
|
||||
│ │ ├── ui/
|
||||
| | | ├── .storybook/
|
||||
| | | ├── src/
|
||||
| | | | ├──lib
|
||||
| | | | | ├──my-button
|
||||
| | | | | | ├── MyButton.vue
|
||||
| | | | | | ├── MyButton.stories.ts
|
||||
| | | | | | └── etc...
|
||||
| | | | | └── etc...
|
||||
| | | ├── README.md
|
||||
| | | ├── tsconfig.json
|
||||
| | | └── etc...
|
||||
| | └── etc...
|
||||
| └── etc...
|
||||
├── nx.json
|
||||
├── package.json
|
||||
├── README.md
|
||||
└── etc...
|
||||
```
|
||||
|
||||
{% /callout %}
|
||||
|
||||
## More Documentation
|
||||
|
||||
You can find all Storybook-related Nx topics [here](/nx-api#storybook).
|
||||
|
||||
For more on using Storybook, see the [official Storybook documentation](https://storybook.js.org/docs/vue/get-started/introduction).
|
||||
@ -112,6 +112,7 @@
|
||||
- [Wait for Tasks to Finish](/recipes/node/wait-for-tasks)
|
||||
- [Storybook](/recipes/storybook)
|
||||
- [Set up Storybook for React Projects](/recipes/storybook/overview-react)
|
||||
- [Set up Storybook for Vue Projects](/recipes/storybook/overview-vue)
|
||||
- [Set up Storybook for Angular Projects](/recipes/storybook/overview-angular)
|
||||
- [Configuring Storybook on Nx](/recipes/storybook/configuring-storybook)
|
||||
- [One main Storybook instance for all projects](/recipes/storybook/one-storybook-for-all)
|
||||
|
||||
36
e2e/vue/src/vue-storybook.test.ts
Normal file
36
e2e/vue/src/vue-storybook.test.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
newProject,
|
||||
runCLI,
|
||||
setMaxWorkers,
|
||||
uniq,
|
||||
} from '@nx/e2e/utils';
|
||||
import { join } from 'path';
|
||||
|
||||
describe('Storybook generators and executors for Vue projects', () => {
|
||||
const vueStorybookApp = uniq('vue-app');
|
||||
let proj;
|
||||
beforeAll(async () => {
|
||||
proj = newProject();
|
||||
runCLI(
|
||||
`generate @nx/vue:app ${vueStorybookApp} --project-name-and-root-format=as-provided --no-interactive`
|
||||
);
|
||||
setMaxWorkers(join(vueStorybookApp, 'project.json'));
|
||||
runCLI(
|
||||
`generate @nx/vue:storybook-configuration ${vueStorybookApp} --generateStories --no-interactive`
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanupProject();
|
||||
});
|
||||
|
||||
describe('build storybook', () => {
|
||||
it('should build a vue based storybook setup', () => {
|
||||
// build
|
||||
runCLI(`run ${vueStorybookApp}:build-storybook --verbose`);
|
||||
checkFilesExist(`dist/storybook/${vueStorybookApp}/index.html`);
|
||||
}, 300_000);
|
||||
});
|
||||
});
|
||||
@ -39,11 +39,11 @@ You can read more about how this generator works, in the [Storybook package over
|
||||
|
||||
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](/packages/react/generators/storybook-configuration) (React and Next.js projects)
|
||||
- [React Storybook Configuration Generator](/nx-api/react/generators/storybook-configuration) (React and Next.js projects)
|
||||
|
||||
- [Angular Storybook Configuration Generator](/packages/angular/generators/storybook-configuration)
|
||||
- [Angular Storybook Configuration Generator](/nx-api/angular/generators/storybook-configuration)
|
||||
|
||||
- [React Native Storybook Configuration Generator](/packages/react-native/generators/storybook-configuration)
|
||||
- [React Native Storybook Configuration Generator](/nx-api/react-native/generators/storybook-configuration)
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@ -35,12 +35,7 @@ import {
|
||||
pleaseUpgrade,
|
||||
storybookMajorVersion,
|
||||
} from '../../utils/utilities';
|
||||
import {
|
||||
coreJsVersion,
|
||||
nxVersion,
|
||||
storybookVersion,
|
||||
tsNodeVersion,
|
||||
} from '../../utils/versions';
|
||||
import { coreJsVersion, nxVersion, tsNodeVersion } from '../../utils/versions';
|
||||
import { interactionTestsDependencies } from './lib/interaction-testing.utils';
|
||||
|
||||
export async function configurationGenerator(
|
||||
|
||||
@ -549,13 +549,17 @@ export function createProjectStorybookDir(
|
||||
usesVite?: boolean,
|
||||
viteConfigFilePath?: string
|
||||
) {
|
||||
const projectDirectory =
|
||||
let projectDirectory =
|
||||
projectType === 'application'
|
||||
? isNextJs
|
||||
? 'components'
|
||||
: 'src/app'
|
||||
: 'src/lib';
|
||||
|
||||
if (uiFramework === '@storybook/vue3-vite') {
|
||||
projectDirectory = 'src/components';
|
||||
}
|
||||
|
||||
const storybookConfigExists = projectIsRootProjectInStandaloneWorkspace
|
||||
? tree.exists('.storybook/main.js') || tree.exists('.storybook/main.ts')
|
||||
: tree.exists(join(root, '.storybook/main.ts')) ||
|
||||
|
||||
@ -83,6 +83,15 @@ function checkDependenciesInstalled(host: Tree, schema: Schema) {
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.uiFramework === '@storybook/vue3-vite') {
|
||||
if (
|
||||
!packageJson.dependencies['@storybook/vue3'] &&
|
||||
!packageJson.devDependencies['@storybook/vue3']
|
||||
) {
|
||||
devDependencies['@storybook/vue3'] = storybook7VersionToInstall;
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.uiFramework === '@storybook/angular') {
|
||||
if (
|
||||
!packageJson.dependencies['@angular/forms'] &&
|
||||
|
||||
@ -33,7 +33,8 @@
|
||||
"nx",
|
||||
"typescript",
|
||||
"@nx/cypress",
|
||||
"@nx/playwright"
|
||||
"@nx/playwright",
|
||||
"@nx/storybook"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
38
packages/vue/docs/stories-examples.md
Normal file
38
packages/vue/docs/stories-examples.md
Normal file
@ -0,0 +1,38 @@
|
||||
This generator will generate stories for all your components in your project. The stories will be generated using [Component Story Format 3 (CSF3)](https://storybook.js.org/blog/storybook-csf3-is-here/).
|
||||
|
||||
```bash
|
||||
nx g @nx/vue:stories project-name
|
||||
```
|
||||
|
||||
You can read more about how this generator works, in the [Storybook for Vue overview page](/recipes/storybook/overview-vue#auto-generate-stories).
|
||||
|
||||
When running this generator, you will be prompted to provide the following:
|
||||
|
||||
- The `name` of the project you want to generate the configuration for.
|
||||
- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests)..
|
||||
|
||||
You must provide a `name` for the generator to work.
|
||||
|
||||
By default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.
|
||||
|
||||
There are a number of other options available. Let's take a look at some examples.
|
||||
|
||||
## Examples
|
||||
|
||||
### Ignore certain paths when generating stories
|
||||
|
||||
```bash
|
||||
nx g @nx/vue:stories --name=ui --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*
|
||||
```
|
||||
|
||||
This will generate stories for all the components in the `ui` project, except for the ones in the `libs/ui/src/not-stories` directory, and also for components that their file name is of the pattern `*.other.*`.
|
||||
|
||||
This is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.
|
||||
|
||||
### Generate stories using JavaScript instead of TypeScript
|
||||
|
||||
```bash
|
||||
nx g @nx/vue:stories --name=ui --js=true
|
||||
```
|
||||
|
||||
This will generate stories for all the components in the `ui` project using JavaScript instead of TypeScript. So, you will have `.stories.js` files next to your components.
|
||||
55
packages/vue/docs/storybook-configuration-examples.md
Normal file
55
packages/vue/docs/storybook-configuration-examples.md
Normal file
@ -0,0 +1,55 @@
|
||||
This generator will set up Storybook for your **Vue** project. You can also use this generator to generate Storybook configuration for your **Next.js** project. By default, starting Nx 16, Storybook v7 is used.
|
||||
|
||||
```bash
|
||||
nx g @nx/vue:storybook-configuration project-name
|
||||
```
|
||||
|
||||
You can read more about how this generator works, in the [Storybook for Vue overview page](/recipes/storybook/overview-vue#generate-storybook-configuration-for-a-vue-project).
|
||||
|
||||
When running this generator, you will be prompted to provide the following:
|
||||
|
||||
- The `name` of the project you want to generate the configuration for.
|
||||
- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/vue/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/vue/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests)..
|
||||
- Whether you want to `generateStories` for the components in your project. If you choose `yes`, a `.stories.ts` file will be generated next to each of your components in your project.
|
||||
|
||||
You must provide a `name` for the generator to work.
|
||||
|
||||
By default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/vue/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.
|
||||
|
||||
There are a number of other options available. Let's take a look at some examples.
|
||||
|
||||
## Examples
|
||||
|
||||
### Generate Storybook configuration
|
||||
|
||||
```bash
|
||||
nx g @nx/vue:storybook-configuration ui
|
||||
```
|
||||
|
||||
This will generate Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory, eg. `.storybook/main.ts`).
|
||||
|
||||
### Ignore certain paths when generating stories
|
||||
|
||||
```bash
|
||||
nx g @nx/vue:storybook-configuration ui --generateStories=true --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*,apps/my-app/**/*.something.ts
|
||||
```
|
||||
|
||||
This will generate a Storybook configuration for the `ui` project and generate stories for all components in the `libs/ui/src/lib` directory, except for the ones in the `libs/ui/src/not-stories` directory, and the ones in the `apps/my-app` directory that end with `.something.ts`, and also for components that their file name is of the pattern `*.other.*`.
|
||||
|
||||
This is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.
|
||||
|
||||
### Generate stories using JavaScript instead of TypeScript
|
||||
|
||||
```bash
|
||||
nx g @nx/vue:storybook-configuration ui --generateStories=true --js=true
|
||||
```
|
||||
|
||||
This will generate stories for all the components in the `ui` project using JavaScript instead of TypeScript. So, you will have `.stories.js` files next to your components.
|
||||
|
||||
### Generate Storybook configuration using JavaScript
|
||||
|
||||
```bash
|
||||
nx g @nx/vue:storybook-configuration ui --tsConfiguration=false
|
||||
```
|
||||
|
||||
By default, our generator generates TypeScript Storybook configuration files. You can choose to use JavaScript for the Storybook configuration files of your project (the files inside the `.storybook` directory, eg. `.storybook/main.js`).
|
||||
@ -33,6 +33,18 @@
|
||||
"factory": "./src/generators/setup-tailwind/setup-tailwind",
|
||||
"schema": "./src/generators/setup-tailwind/schema.json",
|
||||
"description": "Set up Tailwind configuration for a project."
|
||||
},
|
||||
"storybook-configuration": {
|
||||
"factory": "./src/generators/storybook-configuration/configuration",
|
||||
"schema": "./src/generators/storybook-configuration/schema.json",
|
||||
"description": "Set up storybook for a Vue app or library.",
|
||||
"hidden": false
|
||||
},
|
||||
"stories": {
|
||||
"factory": "./src/generators/stories/stories",
|
||||
"schema": "./src/generators/stories/schema.json",
|
||||
"description": "Create stories for all components declared in an app or library.",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
"migrations": "./migrations.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimatch": "3.0.5",
|
||||
"tslib": "^2.3.0",
|
||||
"@nx/devkit": "file:../devkit",
|
||||
"@nx/jest": "file:../jest",
|
||||
|
||||
@ -138,27 +138,6 @@ describe('component', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: figure out routing
|
||||
xdescribe('--routing', () => {
|
||||
it('should add routes to the component', async () => {
|
||||
await componentGenerator(appTree, {
|
||||
name: 'hello',
|
||||
project: projectName,
|
||||
routing: true,
|
||||
});
|
||||
|
||||
const content = appTree
|
||||
.read('my-lib/src/components/hello/hello.tsx')
|
||||
.toString();
|
||||
expect(content).toContain('react-router-dom');
|
||||
expect(content).toMatch(/<Route\s*path="\/"/);
|
||||
expect(content).toMatch(/<Link\s*to="\/"/);
|
||||
|
||||
const packageJSON = readJson(appTree, 'package.json');
|
||||
expect(packageJSON.dependencies['react-router-dom']).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('--directory', () => {
|
||||
it('should create component under the directory', async () => {
|
||||
await componentGenerator(appTree, {
|
||||
|
||||
@ -114,7 +114,7 @@ async function normalizeOptions(
|
||||
|
||||
const { sourceRoot: projectSourceRoot, projectType } = project;
|
||||
|
||||
const directory = await getDirectory(host, options);
|
||||
const directory = await getDirectory(options);
|
||||
|
||||
if (options.export && projectType === 'application') {
|
||||
logger.warn(
|
||||
@ -134,7 +134,7 @@ async function normalizeOptions(
|
||||
};
|
||||
}
|
||||
|
||||
async function getDirectory(host: Tree, options: Schema) {
|
||||
async function getDirectory(options: Schema) {
|
||||
if (options.directory) return options.directory;
|
||||
if (options.flat) return 'components';
|
||||
const { className, fileName } = names(options.name);
|
||||
|
||||
@ -0,0 +1,108 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`vue:stories for applications should create the stories with interaction tests 1`] = `
|
||||
"import type { Meta, StoryObj } from '@storybook/vue3';
|
||||
import NxWelcome from './NxWelcome.vue';
|
||||
|
||||
import { within } from '@storybook/testing-library';
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
const meta: Meta<typeof NxWelcome> = {
|
||||
component: NxWelcome,
|
||||
title: 'NxWelcome',
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Primary = {
|
||||
args: {},
|
||||
};
|
||||
|
||||
export const Heading: Story = {
|
||||
args: {},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await expect(canvas.getByText(/Welcome to NxWelcome!/gi)).toBeTruthy();
|
||||
},
|
||||
};
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`vue:stories for applications should create the stories with interaction tests 2`] = `
|
||||
"import type { Meta, StoryObj } from '@storybook/vue3';
|
||||
import anotherCmp from './another-cmp.vue';
|
||||
|
||||
import { within } from '@storybook/testing-library';
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
const meta: Meta<typeof anotherCmp> = {
|
||||
component: anotherCmp,
|
||||
title: 'anotherCmp',
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Primary = {
|
||||
args: {
|
||||
name: 'name',
|
||||
displayAge: false,
|
||||
age: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export const Heading: Story = {
|
||||
args: {
|
||||
name: 'name',
|
||||
displayAge: false,
|
||||
age: 0,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await expect(canvas.getByText(/Welcome to anotherCmp!/gi)).toBeTruthy();
|
||||
},
|
||||
};
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`vue:stories for applications should create the stories without interaction tests 1`] = `
|
||||
"import type { Meta, StoryObj } from '@storybook/vue3';
|
||||
import NxWelcome from './NxWelcome.vue';
|
||||
|
||||
const meta: Meta<typeof NxWelcome> = {
|
||||
component: NxWelcome,
|
||||
title: 'NxWelcome',
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Primary = {
|
||||
args: {},
|
||||
};
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`vue:stories for applications should create the stories without interaction tests 2`] = `
|
||||
"import type { Meta, StoryObj } from '@storybook/vue3';
|
||||
import anotherCmp from './another-cmp.vue';
|
||||
|
||||
const meta: Meta<typeof anotherCmp> = {
|
||||
component: anotherCmp,
|
||||
title: 'anotherCmp',
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Primary = {
|
||||
args: {
|
||||
name: 'name',
|
||||
displayAge: false,
|
||||
age: 0,
|
||||
},
|
||||
};
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`vue:stories for applications should not update existing stories 1`] = `
|
||||
"import { ComponentStory, ComponentMeta } from '@storybook/vue3';
|
||||
"
|
||||
`;
|
||||
@ -0,0 +1,103 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`vue:stories for libraries should create the stories with interaction tests 1`] = `
|
||||
"import type { Meta, StoryObj } from '@storybook/vue3';
|
||||
import testUiLib from './test-ui-lib.vue';
|
||||
|
||||
import { within } from '@storybook/testing-library';
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
const meta: Meta<typeof testUiLib> = {
|
||||
component: testUiLib,
|
||||
title: 'testUiLib',
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Primary = {
|
||||
args: {},
|
||||
};
|
||||
|
||||
export const Heading: Story = {
|
||||
args: {},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await expect(canvas.getByText(/Welcome to testUiLib!/gi)).toBeTruthy();
|
||||
},
|
||||
};
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`vue:stories for libraries should create the stories with interaction tests 2`] = `
|
||||
"import type { Meta, StoryObj } from '@storybook/vue3';
|
||||
import anotherCmp from './another-cmp.vue';
|
||||
|
||||
import { within } from '@storybook/testing-library';
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
const meta: Meta<typeof anotherCmp> = {
|
||||
component: anotherCmp,
|
||||
title: 'anotherCmp',
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Primary = {
|
||||
args: {
|
||||
name: 'name',
|
||||
displayAge: false,
|
||||
age: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export const Heading: Story = {
|
||||
args: {
|
||||
name: 'name',
|
||||
displayAge: false,
|
||||
age: 0,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await expect(canvas.getByText(/Welcome to anotherCmp!/gi)).toBeTruthy();
|
||||
},
|
||||
};
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`vue:stories for libraries should create the stories without interaction tests 1`] = `
|
||||
"import type { Meta, StoryObj } from '@storybook/vue3';
|
||||
import testUiLib from './test-ui-lib.vue';
|
||||
|
||||
const meta: Meta<typeof testUiLib> = {
|
||||
component: testUiLib,
|
||||
title: 'testUiLib',
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Primary = {
|
||||
args: {},
|
||||
};
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`vue:stories for libraries should create the stories without interaction tests 2`] = `
|
||||
"import type { Meta, StoryObj } from '@storybook/vue3';
|
||||
import anotherCmp from './another-cmp.vue';
|
||||
|
||||
const meta: Meta<typeof anotherCmp> = {
|
||||
component: anotherCmp,
|
||||
title: 'anotherCmp',
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Primary = {
|
||||
args: {
|
||||
name: 'name',
|
||||
displayAge: false,
|
||||
age: 0,
|
||||
},
|
||||
};
|
||||
"
|
||||
`;
|
||||
@ -0,0 +1,115 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`vue:component-story default setup component with other syntax of props defined should create a story with controls 1`] = `
|
||||
"import type { Meta, StoryObj } from '@storybook/vue3';
|
||||
import testUiLib from './test-ui-lib.vue';
|
||||
|
||||
import { within } from '@storybook/testing-library';
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
|
||||
const meta: Meta<typeof testUiLib> = {
|
||||
component: testUiLib,
|
||||
title: 'testUiLib',
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
|
||||
export const Primary = {
|
||||
args: {
|
||||
name: 'name',
|
||||
displayAge: false,
|
||||
age: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export const Heading: Story = {
|
||||
args: {
|
||||
name: 'name',
|
||||
displayAge: false,
|
||||
age: 0,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await expect(canvas.getByText(/Welcome to testUiLib!/gi)).toBeTruthy();
|
||||
},
|
||||
};
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`vue:component-story default setup component with props defined should create a story with controls 1`] = `
|
||||
"import type { Meta, StoryObj } from '@storybook/vue3';
|
||||
import testUiLib from './test-ui-lib.vue';
|
||||
|
||||
import { within } from '@storybook/testing-library';
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
|
||||
const meta: Meta<typeof testUiLib> = {
|
||||
component: testUiLib,
|
||||
title: 'testUiLib',
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
|
||||
export const Primary = {
|
||||
args: {
|
||||
name: 'name',
|
||||
displayAge: false,
|
||||
age: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export const Heading: Story = {
|
||||
args: {
|
||||
name: 'name',
|
||||
displayAge: false,
|
||||
age: 0,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await expect(canvas.getByText(/Welcome to testUiLib!/gi)).toBeTruthy();
|
||||
},
|
||||
};
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`vue:component-story default setup default component setup should properly set up the story 1`] = `
|
||||
"import type { Meta, StoryObj } from '@storybook/vue3';
|
||||
import testUiLib from './test-ui-lib.vue';
|
||||
|
||||
import { within } from '@storybook/testing-library';
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
|
||||
const meta: Meta<typeof testUiLib> = {
|
||||
component: testUiLib,
|
||||
title: 'testUiLib',
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
|
||||
export const Primary = {
|
||||
args: {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export const Heading: Story = {
|
||||
args: {
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await expect(canvas.getByText(/Welcome to testUiLib!/gi)).toBeTruthy();
|
||||
},
|
||||
};
|
||||
|
||||
"
|
||||
`;
|
||||
141
packages/vue/src/generators/stories/lib/component-story.spec.ts
Normal file
141
packages/vue/src/generators/stories/lib/component-story.spec.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import { getProjects, Tree, updateProjectConfiguration } from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import libraryGenerator from '../../library/library';
|
||||
import { createComponentStories } from './component-story';
|
||||
import { Linter } from '@nx/linter';
|
||||
|
||||
describe('vue:component-story', () => {
|
||||
let appTree: Tree;
|
||||
let cmpPath = 'test-ui-lib/src/components/test-ui-lib.vue';
|
||||
let storyFilePath = 'test-ui-lib/src/components/test-ui-lib.stories.ts';
|
||||
|
||||
describe('default setup', () => {
|
||||
beforeEach(async () => {
|
||||
appTree = await createTestUILib('test-ui-lib');
|
||||
});
|
||||
|
||||
describe('default component setup', () => {
|
||||
beforeEach(async () => {
|
||||
createComponentStories(
|
||||
appTree,
|
||||
{
|
||||
interactionTests: true,
|
||||
project: 'test-ui-lib',
|
||||
},
|
||||
'components/test-ui-lib.vue'
|
||||
);
|
||||
});
|
||||
|
||||
it('should properly set up the story', () => {
|
||||
expect(appTree.read(storyFilePath, 'utf-8')).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('component with props defined', () => {
|
||||
beforeEach(async () => {
|
||||
appTree.write(
|
||||
cmpPath,
|
||||
`<script setup lang="ts">
|
||||
defineProps<{
|
||||
name: string;
|
||||
displayAge: boolean;
|
||||
age: number;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p>Welcome to Vlv!</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
color: pink;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
);
|
||||
|
||||
createComponentStories(
|
||||
appTree,
|
||||
{
|
||||
interactionTests: true,
|
||||
project: 'test-ui-lib',
|
||||
},
|
||||
'components/test-ui-lib.vue'
|
||||
);
|
||||
});
|
||||
|
||||
it('should create a story with controls', () => {
|
||||
expect(appTree.read(storyFilePath, 'utf-8')).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('component with other syntax of props defined', () => {
|
||||
beforeEach(async () => {
|
||||
appTree.write(
|
||||
cmpPath,
|
||||
`<script>
|
||||
export default {
|
||||
name: 'HelloWorld',
|
||||
props: {
|
||||
name: string;
|
||||
displayAge: boolean;
|
||||
age: number;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p>Welcome to Vlv!</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
color: pink;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
);
|
||||
|
||||
createComponentStories(
|
||||
appTree,
|
||||
{
|
||||
interactionTests: true,
|
||||
project: 'test-ui-lib',
|
||||
},
|
||||
'components/test-ui-lib.vue'
|
||||
);
|
||||
});
|
||||
|
||||
it('should create a story with controls', () => {
|
||||
expect(appTree.read(storyFilePath, 'utf-8')).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export async function createTestUILib(libName: string): Promise<Tree> {
|
||||
let appTree = createTreeWithEmptyWorkspace();
|
||||
await libraryGenerator(appTree, {
|
||||
name: libName,
|
||||
linter: Linter.EsLint,
|
||||
component: true,
|
||||
skipFormat: true,
|
||||
skipTsConfig: false,
|
||||
unitTestRunner: 'jest',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const currentWorkspaceJson = getProjects(appTree);
|
||||
|
||||
const projectConfig = currentWorkspaceJson.get(libName);
|
||||
projectConfig.targets.lint.options.linter = 'eslint';
|
||||
|
||||
updateProjectConfiguration(appTree, libName, projectConfig);
|
||||
|
||||
return appTree;
|
||||
}
|
||||
60
packages/vue/src/generators/stories/lib/component-story.ts
Normal file
60
packages/vue/src/generators/stories/lib/component-story.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import {
|
||||
generateFiles,
|
||||
getProjects,
|
||||
joinPathFragments,
|
||||
normalizePath,
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
||||
import { StorybookStoriesSchema } from '../stories';
|
||||
import {
|
||||
camelCase,
|
||||
createDefautPropsObject,
|
||||
getDefinePropsObject,
|
||||
} from './utils';
|
||||
|
||||
let tsModule: typeof import('typescript');
|
||||
|
||||
export function createComponentStories(
|
||||
host: Tree,
|
||||
{ project, js, interactionTests }: StorybookStoriesSchema,
|
||||
componentPath: string
|
||||
) {
|
||||
if (!tsModule) {
|
||||
tsModule = ensureTypescript();
|
||||
}
|
||||
const proj = getProjects(host).get(project);
|
||||
const sourceRoot = proj.sourceRoot;
|
||||
|
||||
const componentFilePath = joinPathFragments(sourceRoot, componentPath);
|
||||
|
||||
const componentDirectory = componentFilePath.replace(
|
||||
componentFilePath.slice(componentFilePath.lastIndexOf('/')),
|
||||
''
|
||||
);
|
||||
|
||||
const componentFileName = componentFilePath
|
||||
.slice(componentFilePath.lastIndexOf('/') + 1)
|
||||
.replace('.vue', '');
|
||||
|
||||
const name = componentFileName;
|
||||
const contents = host.read(componentFilePath, 'utf-8');
|
||||
const propsObject = getDefinePropsObject(contents);
|
||||
|
||||
generateFiles(
|
||||
host,
|
||||
joinPathFragments(__dirname, `./files${js ? '/js' : '/ts'}`),
|
||||
normalizePath(componentDirectory),
|
||||
{
|
||||
tmpl: '',
|
||||
componentFileName: name,
|
||||
componentImportFileName: `${name}.vue`,
|
||||
props: createDefautPropsObject(propsObject),
|
||||
componentName: camelCase(name),
|
||||
interactionTests,
|
||||
}
|
||||
);
|
||||
if (contents === null) {
|
||||
throw new Error(`Failed to read ${componentFilePath}`);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
import componentName from './<%= componentImportFileName %>';
|
||||
<% if ( interactionTests ) { %>
|
||||
import { within } from '@storybook/testing-library';
|
||||
import { expect } from '@storybook/jest';
|
||||
<% } %>
|
||||
|
||||
export default {
|
||||
component: <%= componentName %>,
|
||||
title: '<%= componentName %>'
|
||||
};
|
||||
|
||||
export const Primary = {
|
||||
args: {<% for (let prop of props) { %>
|
||||
<%= prop.name %>: <%- prop.defaultValue %>,<% } %>
|
||||
},
|
||||
};
|
||||
|
||||
<% if ( interactionTests ) { %>
|
||||
export const Heading: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await expect(canvas.getByText(/Welcome to <%=componentName%>!/gi)).toBeTruthy();
|
||||
},
|
||||
};
|
||||
<% } %>
|
||||
@ -0,0 +1,32 @@
|
||||
import type { Meta, StoryObj } from '@storybook/vue3';
|
||||
import <%= componentName %> from './<%= componentImportFileName %>';
|
||||
<% if ( interactionTests ) { %>
|
||||
import { within } from '@storybook/testing-library';
|
||||
import { expect } from '@storybook/jest';
|
||||
<% } %>
|
||||
|
||||
const meta: Meta<typeof <%= componentName %>> = {
|
||||
component: <%= componentName %>,
|
||||
title: '<%= componentName %>',
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
|
||||
export const Primary = {
|
||||
args: {<% for (let prop of props) { %>
|
||||
<%= prop.name %>: <%- prop.defaultValue %>,<% } %>
|
||||
},
|
||||
};
|
||||
|
||||
<% if ( interactionTests ) { %>
|
||||
export const Heading: Story = {
|
||||
args: {<% for (let prop of props) { %>
|
||||
<%= prop.name %>: <%- prop.defaultValue %>,<% } %>
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await expect(canvas.getByText(/Welcome to <%=componentName%>!/gi)).toBeTruthy();
|
||||
},
|
||||
};
|
||||
<% } %>
|
||||
76
packages/vue/src/generators/stories/lib/utils.ts
Normal file
76
packages/vue/src/generators/stories/lib/utils.ts
Normal file
@ -0,0 +1,76 @@
|
||||
export function camelCase(input: string): string {
|
||||
if (input.indexOf('-') > 1) {
|
||||
return input
|
||||
.toLowerCase()
|
||||
.replace(/-(.)/g, (_match, group1) => group1.toUpperCase())
|
||||
.replace('.', '');
|
||||
} else {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
export function createDefautPropsObject(propsObject: {
|
||||
[key: string]: string;
|
||||
}): {
|
||||
name: string;
|
||||
defaultValue: any;
|
||||
}[] {
|
||||
const props = [];
|
||||
for (const key in propsObject) {
|
||||
if (Object.prototype.hasOwnProperty.call(propsObject, key)) {
|
||||
let defaultValueOfProp;
|
||||
const element = propsObject[key];
|
||||
if (element === 'string') {
|
||||
defaultValueOfProp = `'${key}'`;
|
||||
} else if (element === 'boolean') {
|
||||
defaultValueOfProp = false;
|
||||
} else if (element === 'number') {
|
||||
defaultValueOfProp = 0;
|
||||
}
|
||||
props.push({
|
||||
name: key,
|
||||
defaultValue: defaultValueOfProp,
|
||||
});
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
export function getDefinePropsObject(vueComponentFileContent: string): {
|
||||
[key: string]: string;
|
||||
} {
|
||||
const scriptTagRegex = /<script[^>]*>([\s\S]*?)<\/script>/;
|
||||
const match = vueComponentFileContent?.match(scriptTagRegex);
|
||||
let propsContent;
|
||||
if (match && match[1]) {
|
||||
const scriptContent = match[1].trim();
|
||||
const definePropsRegex = /defineProps<([\s\S]*?)>/;
|
||||
const definePropsMatch = scriptContent.match(definePropsRegex);
|
||||
|
||||
if (definePropsMatch && definePropsMatch[1]) {
|
||||
propsContent = definePropsMatch[1].trim();
|
||||
} else {
|
||||
const propsRegex = /(props:\s*\{[\s\S]*?\})/;
|
||||
const match = scriptContent.match(propsRegex);
|
||||
|
||||
if (match && match[1]) {
|
||||
propsContent = match[1].trim();
|
||||
} else {
|
||||
// No props found
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No props found
|
||||
}
|
||||
const attributes = {};
|
||||
|
||||
if (propsContent) {
|
||||
const keyTypeRegex = /(\w+):\s*(\w+);/g;
|
||||
let match;
|
||||
|
||||
while ((match = keyTypeRegex.exec(propsContent)) !== null) {
|
||||
attributes[match[1]] = match[2];
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
64
packages/vue/src/generators/stories/schema.json
Normal file
64
packages/vue/src/generators/stories/schema.json
Normal file
@ -0,0 +1,64 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"cli": "nx",
|
||||
"$id": "NxVueStorybookStories",
|
||||
"title": "Generate Vue Storybook stories",
|
||||
"description": "Generate stories/specs for all components declared in a project.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"aliases": ["name", "projectName"],
|
||||
"description": "Project for which to generate stories.",
|
||||
"$default": {
|
||||
"$source": "projectName",
|
||||
"index": 0
|
||||
},
|
||||
"x-prompt": "For which project do you want to generate stories?",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"generateCypressSpecs": {
|
||||
"type": "boolean",
|
||||
"description": "Automatically generate `*.spec.ts` files in the cypress e2e app generated by the cypress-configure generator."
|
||||
},
|
||||
"cypressProject": {
|
||||
"type": "string",
|
||||
"description": "The Cypress project to generate the stories under. This is inferred from `project` by default."
|
||||
},
|
||||
"interactionTests": {
|
||||
"type": "boolean",
|
||||
"description": "Set up Storybook interaction tests.",
|
||||
"x-prompt": "Do you want to set up Storybook interaction tests?",
|
||||
"x-priority": "important",
|
||||
"default": true
|
||||
},
|
||||
"js": {
|
||||
"type": "boolean",
|
||||
"description": "Generate JavaScript files rather than TypeScript files.",
|
||||
"default": false
|
||||
},
|
||||
"ignorePaths": {
|
||||
"type": "array",
|
||||
"description": "Paths to ignore when looking for components.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "Path to ignore."
|
||||
},
|
||||
"examples": [
|
||||
"apps/my-app/src/not-stories/**",
|
||||
"**/**/src/**/not-stories/**",
|
||||
"libs/my-lib/**/*.something.ts",
|
||||
"**/**/src/**/*.other.*",
|
||||
"libs/my-lib/src/not-stories/**,**/**/src/**/*.other.*,apps/my-app/**/*.something.ts"
|
||||
]
|
||||
},
|
||||
"skipFormat": {
|
||||
"description": "Skip formatting files.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"x-priority": "internal"
|
||||
}
|
||||
},
|
||||
"required": ["project"],
|
||||
"examplesFile": "../../../docs/stories-examples.md"
|
||||
}
|
||||
272
packages/vue/src/generators/stories/stories.app.spec.ts
Normal file
272
packages/vue/src/generators/stories/stories.app.spec.ts
Normal file
@ -0,0 +1,272 @@
|
||||
import { Tree } from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { Linter } from '@nx/linter';
|
||||
import applicationGenerator from '../application/application';
|
||||
import storiesGenerator from './stories';
|
||||
|
||||
const componentContent = `<script setup lang="ts">
|
||||
defineProps<{
|
||||
name: string;
|
||||
displayAge: boolean;
|
||||
age: number;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p>Welcome to Vlv!</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
color: pink;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
describe('vue:stories for applications', () => {
|
||||
let appTree: Tree;
|
||||
|
||||
beforeEach(async () => {
|
||||
appTree = await createTestUIApp('test-ui-app');
|
||||
|
||||
// create another component
|
||||
appTree.write(
|
||||
'test-ui-app/src/components/another-cmp/another-cmp.vue',
|
||||
componentContent
|
||||
);
|
||||
});
|
||||
|
||||
it('should create the stories with interaction tests', async () => {
|
||||
await storiesGenerator(appTree, {
|
||||
project: 'test-ui-app',
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.read('test-ui-app/src/components/NxWelcome.stories.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
expect(
|
||||
appTree.read(
|
||||
'test-ui-app/src/components/another-cmp/another-cmp.stories.ts',
|
||||
'utf-8'
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should create the stories without interaction tests', async () => {
|
||||
await storiesGenerator(appTree, {
|
||||
project: 'test-ui-app',
|
||||
interactionTests: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.read('test-ui-app/src/components/NxWelcome.stories.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
expect(
|
||||
appTree.read(
|
||||
'test-ui-app/src/components/another-cmp/another-cmp.stories.ts',
|
||||
'utf-8'
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not update existing stories', async () => {
|
||||
appTree.write(
|
||||
'test-ui-app/src/components/NxWelcome.stories.ts',
|
||||
`import { ComponentStory, ComponentMeta } from '@storybook/vue3'`
|
||||
);
|
||||
|
||||
await storiesGenerator(appTree, {
|
||||
project: 'test-ui-app',
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.read('test-ui-app/src/components/NxWelcome.stories.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('ignore paths', () => {
|
||||
beforeEach(() => {
|
||||
appTree.write(
|
||||
'test-ui-app/src/components/test-path/ignore-it/another-one.vue',
|
||||
componentContent
|
||||
);
|
||||
|
||||
appTree.write(
|
||||
'test-ui-app/src/components/another-cmp/another-cmp-test.skip.vue',
|
||||
componentContent
|
||||
);
|
||||
});
|
||||
it('should generate stories for all if no ignorePaths', async () => {
|
||||
await storiesGenerator(appTree, {
|
||||
project: 'test-ui-app',
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.exists('test-ui-app/src/components/NxWelcome.stories.ts')
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/another-cmp/another-cmp.stories.ts'
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/test-path/ignore-it/another-one.stories.ts'
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/another-cmp/another-cmp-test.skip.stories.ts'
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should ignore entire paths', async () => {
|
||||
await storiesGenerator(appTree, {
|
||||
project: 'test-ui-app',
|
||||
ignorePaths: [
|
||||
`test-ui-app/src/components/another-cmp/**`,
|
||||
`**/**/src/**/test-path/ignore-it/**`,
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.exists('test-ui-app/src/components/NxWelcome.stories.ts')
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/another-cmp/another-cmp.stories.ts'
|
||||
)
|
||||
).toBeFalsy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/test-path/ignore-it/another-one.stories.ts'
|
||||
)
|
||||
).toBeFalsy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/another-cmp/another-cmp-test.skip.stories.ts'
|
||||
)
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should ignore path or a pattern', async () => {
|
||||
await storiesGenerator(appTree, {
|
||||
project: 'test-ui-app',
|
||||
ignorePaths: [
|
||||
'test-ui-app/src/components/another-cmp/**/*.skip.*',
|
||||
'**/**/src/**/test-path/**',
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.exists('test-ui-app/src/components/NxWelcome.stories.ts')
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/another-cmp/another-cmp.stories.ts'
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/test-path/ignore-it/another-one.stories.ts'
|
||||
)
|
||||
).toBeFalsy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/another-cmp/another-cmp-test.skip.stories.ts'
|
||||
)
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should ignore direct path to component', async () => {
|
||||
await storiesGenerator(appTree, {
|
||||
project: 'test-ui-app',
|
||||
ignorePaths: ['test-ui-app/src/components/another-cmp/**/*.skip.vue'],
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.exists('test-ui-app/src/components/NxWelcome.stories.ts')
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/another-cmp/another-cmp.stories.ts'
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/test-path/ignore-it/another-one.stories.ts'
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/another-cmp/another-cmp-test.skip.stories.ts'
|
||||
)
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should ignore a path that has a nested component, but still generate nested component stories', async () => {
|
||||
appTree.write(
|
||||
'test-ui-app/src/components/another-cmp/comp-a/comp-a.vue',
|
||||
componentContent
|
||||
);
|
||||
|
||||
await storiesGenerator(appTree, {
|
||||
project: 'test-ui-app',
|
||||
ignorePaths: [
|
||||
'test-ui-app/src/components/another-cmp/another-cmp-test.skip.vue',
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.exists('test-ui-app/src/components/NxWelcome.stories.ts')
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/another-cmp/another-cmp.stories.ts'
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/another-cmp/comp-a/comp-a.stories.ts'
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-app/src/components/another-cmp/another-cmp-test.skip.stories.ts'
|
||||
)
|
||||
).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export async function createTestUIApp(
|
||||
libName: string,
|
||||
plainJS = false
|
||||
): Promise<Tree> {
|
||||
let appTree = createTreeWithEmptyWorkspace();
|
||||
|
||||
await applicationGenerator(appTree, {
|
||||
e2eTestRunner: 'none',
|
||||
linter: Linter.EsLint,
|
||||
skipFormat: true,
|
||||
style: 'css',
|
||||
unitTestRunner: 'none',
|
||||
name: libName,
|
||||
js: plainJS,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
return appTree;
|
||||
}
|
||||
201
packages/vue/src/generators/stories/stories.lib.spec.ts
Normal file
201
packages/vue/src/generators/stories/stories.lib.spec.ts
Normal file
@ -0,0 +1,201 @@
|
||||
import { Tree } from '@nx/devkit';
|
||||
import storiesGenerator from './stories';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { Linter } from '@nx/linter';
|
||||
import libraryGenerator from '../library/library';
|
||||
|
||||
const componentContent = `<script setup lang="ts">
|
||||
defineProps<{
|
||||
name: string;
|
||||
displayAge: boolean;
|
||||
age: number;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p>Welcome to Vlv!</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
color: pink;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
describe('vue:stories for libraries', () => {
|
||||
let appTree: Tree;
|
||||
|
||||
beforeEach(async () => {
|
||||
appTree = await createTestUILib('test-ui-lib');
|
||||
appTree.write(
|
||||
'test-ui-lib/src/components/another-cmp/another-cmp.vue',
|
||||
componentContent
|
||||
);
|
||||
});
|
||||
|
||||
it('should create the stories with interaction tests', async () => {
|
||||
await storiesGenerator(appTree, {
|
||||
project: 'test-ui-lib',
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.read('test-ui-lib/src/components/test-ui-lib.stories.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
expect(
|
||||
appTree.read(
|
||||
'test-ui-lib/src/components/another-cmp/another-cmp.stories.ts',
|
||||
'utf-8'
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
|
||||
const packageJson = JSON.parse(appTree.read('package.json', 'utf-8'));
|
||||
expect(
|
||||
packageJson.devDependencies['@storybook/addon-interactions']
|
||||
).toBeDefined();
|
||||
expect(packageJson.devDependencies['@storybook/test-runner']).toBeDefined();
|
||||
expect(
|
||||
packageJson.devDependencies['@storybook/testing-library']
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create the stories without interaction tests', async () => {
|
||||
await storiesGenerator(appTree, {
|
||||
project: 'test-ui-lib',
|
||||
interactionTests: false,
|
||||
});
|
||||
expect(
|
||||
appTree.read('test-ui-lib/src/components/test-ui-lib.stories.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
expect(
|
||||
appTree.read(
|
||||
'test-ui-lib/src/components/another-cmp/another-cmp.stories.ts',
|
||||
'utf-8'
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
const packageJson = JSON.parse(appTree.read('package.json', 'utf-8'));
|
||||
expect(
|
||||
packageJson.devDependencies['@storybook/addon-interactions']
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
packageJson.devDependencies['@storybook/test-runner']
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
packageJson.devDependencies['@storybook/testing-library']
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('ignore paths', () => {
|
||||
beforeEach(() => {
|
||||
appTree.write(
|
||||
'test-ui-lib/src/components/test-path/ignore-it/another-one.vue',
|
||||
componentContent
|
||||
);
|
||||
|
||||
appTree.write(
|
||||
'test-ui-lib/src/components/another-cmp/another-cmp.skip.vue',
|
||||
componentContent
|
||||
);
|
||||
});
|
||||
it('should generate stories for all if no ignorePaths', async () => {
|
||||
await storiesGenerator(appTree, {
|
||||
project: 'test-ui-lib',
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-lib/src/components/another-cmp/another-cmp.stories.ts'
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-lib/src/components/test-path/ignore-it/another-one.stories.ts'
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-lib/src/components/another-cmp/another-cmp.skip.stories.ts'
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should ignore entire paths', async () => {
|
||||
await storiesGenerator(appTree, {
|
||||
project: 'test-ui-lib',
|
||||
ignorePaths: [
|
||||
'test-ui-lib/src/components/another-cmp/**',
|
||||
'**/**/src/**/test-path/ignore-it/**',
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-lib/src/components/another-cmp/another-cmp.stories.ts'
|
||||
)
|
||||
).toBeFalsy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-lib/src/components/test-path/ignore-it/another-one.stories.ts'
|
||||
)
|
||||
).toBeFalsy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-lib/src/components/another-cmp/another-cmp.skip.stories.ts'
|
||||
)
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should ignore path or a pattern', async () => {
|
||||
await storiesGenerator(appTree, {
|
||||
project: 'test-ui-lib',
|
||||
ignorePaths: [
|
||||
'test-ui-lib/src/components/another-cmp/**/*.skip.*',
|
||||
'**/test-ui-lib/src/**/test-path/**',
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-lib/src/components/another-cmp/another-cmp.stories.ts'
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-lib/src/components/test-path/ignore-it/another-one.stories.ts'
|
||||
)
|
||||
).toBeFalsy();
|
||||
|
||||
expect(
|
||||
appTree.exists(
|
||||
'test-ui-lib/src/components/another-cmp/another-cmp.skip.stories.ts'
|
||||
)
|
||||
).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export async function createTestUILib(
|
||||
libName: string,
|
||||
plainJS = false
|
||||
): Promise<Tree> {
|
||||
let appTree = createTreeWithEmptyWorkspace();
|
||||
|
||||
await libraryGenerator(appTree, {
|
||||
linter: Linter.EsLint,
|
||||
component: true,
|
||||
skipFormat: true,
|
||||
skipTsConfig: false,
|
||||
unitTestRunner: 'none',
|
||||
name: libName,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
return appTree;
|
||||
}
|
||||
106
packages/vue/src/generators/stories/stories.ts
Normal file
106
packages/vue/src/generators/stories/stories.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
convertNxGenerator,
|
||||
ensurePackage,
|
||||
formatFiles,
|
||||
GeneratorCallback,
|
||||
getProjects,
|
||||
joinPathFragments,
|
||||
ProjectConfiguration,
|
||||
runTasksInSerial,
|
||||
Tree,
|
||||
visitNotIgnoredFiles,
|
||||
} from '@nx/devkit';
|
||||
import { basename, join } from 'path';
|
||||
import minimatch = require('minimatch');
|
||||
import { nxVersion } from '../../utils/versions';
|
||||
import { createComponentStories } from './lib/component-story';
|
||||
|
||||
export interface StorybookStoriesSchema {
|
||||
project: string;
|
||||
interactionTests?: boolean;
|
||||
js?: boolean;
|
||||
ignorePaths?: string[];
|
||||
skipFormat?: boolean;
|
||||
cypressProject?: string;
|
||||
generateCypressSpecs?: boolean;
|
||||
}
|
||||
|
||||
export async function createAllStories(
|
||||
tree: Tree,
|
||||
projectName: string,
|
||||
interactionTests: boolean,
|
||||
js: boolean,
|
||||
projectConfiguration: ProjectConfiguration,
|
||||
ignorePaths?: string[]
|
||||
) {
|
||||
const { sourceRoot, root } = projectConfiguration;
|
||||
let componentPaths: string[] = [];
|
||||
const projectPath = joinPathFragments(sourceRoot, 'components');
|
||||
visitNotIgnoredFiles(tree, projectPath, (path) => {
|
||||
// Ignore private files starting with "_".
|
||||
if (basename(path).startsWith('_')) return;
|
||||
if (ignorePaths?.some((pattern) => minimatch(path, pattern))) return;
|
||||
if (path.endsWith('.vue')) {
|
||||
// Let's see if the .stories.* file exists
|
||||
const ext = path.slice(path.lastIndexOf('.'));
|
||||
const storyPathJs = `${path.split(ext)[0]}.stories.js`;
|
||||
const storyPathTs = `${path.split(ext)[0]}.stories.ts`;
|
||||
|
||||
if (!tree.exists(storyPathJs) && !tree.exists(storyPathTs)) {
|
||||
componentPaths.push(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
componentPaths.map(async (componentPath) => {
|
||||
const relativeCmpDir = componentPath.replace(join(sourceRoot, '/'), '');
|
||||
createComponentStories(
|
||||
tree,
|
||||
{
|
||||
project: projectName,
|
||||
interactionTests,
|
||||
js,
|
||||
},
|
||||
relativeCmpDir
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function storiesGenerator(
|
||||
host: Tree,
|
||||
schema: StorybookStoriesSchema
|
||||
) {
|
||||
const projects = getProjects(host);
|
||||
const projectConfiguration = projects.get(schema.project);
|
||||
schema.interactionTests = schema.interactionTests ?? true;
|
||||
await createAllStories(
|
||||
host,
|
||||
schema.project,
|
||||
schema.interactionTests,
|
||||
schema.js,
|
||||
projectConfiguration,
|
||||
schema.ignorePaths
|
||||
);
|
||||
|
||||
const tasks: GeneratorCallback[] = [];
|
||||
|
||||
if (schema.interactionTests) {
|
||||
const { interactionTestsDependencies, addInteractionsInAddons } =
|
||||
ensurePackage<typeof import('@nx/storybook')>('@nx/storybook', nxVersion);
|
||||
tasks.push(
|
||||
addDependenciesToPackageJson(host, {}, interactionTestsDependencies())
|
||||
);
|
||||
addInteractionsInAddons(host, projectConfiguration);
|
||||
}
|
||||
|
||||
if (!schema.skipFormat) {
|
||||
await formatFiles(host);
|
||||
}
|
||||
return runTasksInSerial(...tasks);
|
||||
}
|
||||
|
||||
export default storiesGenerator;
|
||||
export const storiesSchematic = convertNxGenerator(storiesGenerator);
|
||||
@ -0,0 +1,33 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`vue:storybook-configuration should configure everything and install correct dependencies 1`] = `
|
||||
"import type { StorybookConfig } from '@storybook/vue3-vite';
|
||||
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { mergeConfig } from 'vite';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/components/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||
framework: {
|
||||
name: '@storybook/vue3-vite',
|
||||
options: {},
|
||||
},
|
||||
|
||||
viteFinal: async (config) =>
|
||||
mergeConfig(config, {
|
||||
plugins: [nxViteTsPaths()],
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
// To customize your Vite configuration you can use the viteFinal field.
|
||||
// Check https://storybook.js.org/docs/react/builders/vite#configuration
|
||||
// and https://nx.dev/recipes/storybook/custom-builder-configs
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`vue:storybook-configuration should generate stories for components 1`] = `null`;
|
||||
|
||||
exports[`vue:storybook-configuration should generate stories for components without interaction tests 1`] = `null`;
|
||||
@ -0,0 +1,165 @@
|
||||
import { logger, Tree } from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { Linter } from '@nx/linter';
|
||||
import applicationGenerator from '../application/application';
|
||||
import componentGenerator from '../component/component';
|
||||
import libraryGenerator from '../library/library';
|
||||
import storybookConfigurationGenerator from './configuration';
|
||||
|
||||
// nested code imports graph from the repo, which might have innacurate graph version
|
||||
jest.mock('nx/src/project-graph/project-graph', () => ({
|
||||
...jest.requireActual<any>('nx/src/project-graph/project-graph'),
|
||||
createProjectGraphAsync: jest
|
||||
.fn()
|
||||
.mockImplementation(async () => ({ nodes: {}, dependencies: {} })),
|
||||
}));
|
||||
const componentContent = `<script setup lang="ts">
|
||||
defineProps<{
|
||||
name: string;
|
||||
displayAge: boolean;
|
||||
age: number;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p>Welcome to Vlv!</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
color: pink;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
describe('vue:storybook-configuration', () => {
|
||||
let appTree;
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(logger, 'warn').mockImplementation(() => {});
|
||||
jest.spyOn(logger, 'debug').mockImplementation(() => {});
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should configure everything and install correct dependencies', async () => {
|
||||
appTree = await createTestUILib('test-ui-lib');
|
||||
await storybookConfigurationGenerator(appTree, {
|
||||
name: 'test-ui-lib',
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.read('test-ui-lib/.storybook/main.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
expect(appTree.exists('test-ui-lib/tsconfig.storybook.json')).toBeTruthy();
|
||||
|
||||
const packageJson = JSON.parse(appTree.read('package.json', 'utf-8'));
|
||||
expect(packageJson.devDependencies['@storybook/vue3-vite']).toBeDefined();
|
||||
expect(packageJson.devDependencies['@storybook/vue3']).toBeDefined();
|
||||
expect(
|
||||
packageJson.devDependencies['@storybook/addon-interactions']
|
||||
).toBeDefined();
|
||||
expect(packageJson.devDependencies['@storybook/test-runner']).toBeDefined();
|
||||
expect(
|
||||
packageJson.devDependencies['@storybook/testing-library']
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it('should generate stories for components', async () => {
|
||||
appTree = await createTestUILib('test-ui-lib');
|
||||
await storybookConfigurationGenerator(appTree, {
|
||||
name: 'test-ui-lib',
|
||||
generateStories: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.exists('test-ui-lib/src/components/test-ui-lib.stories.ts')
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should configure everything at once', async () => {
|
||||
appTree = await createTestAppLib('test-ui-app');
|
||||
await storybookConfigurationGenerator(appTree, {
|
||||
name: 'test-ui-app',
|
||||
});
|
||||
|
||||
expect(appTree.exists('test-ui-app/.storybook/main.ts')).toBeTruthy();
|
||||
expect(appTree.exists('test-ui-app/tsconfig.storybook.json')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should generate stories for components', async () => {
|
||||
appTree = await createTestAppLib('test-ui-app');
|
||||
await storybookConfigurationGenerator(appTree, {
|
||||
name: 'test-ui-app',
|
||||
generateStories: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.read(
|
||||
'test-ui-app/src/components/my-component/my-component.stories.ts',
|
||||
'utf-8'
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate stories for components without interaction tests', async () => {
|
||||
appTree = await createTestAppLib('test-ui-app');
|
||||
await storybookConfigurationGenerator(appTree, {
|
||||
name: 'test-ui-app',
|
||||
generateStories: true,
|
||||
interactionTests: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
appTree.read(
|
||||
'test-ui-app/src/components/my-component/my-component.stories.ts',
|
||||
'utf-8'
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
export async function createTestUILib(libName: string): Promise<Tree> {
|
||||
let appTree = createTreeWithEmptyWorkspace();
|
||||
|
||||
await libraryGenerator(appTree, {
|
||||
linter: Linter.EsLint,
|
||||
component: true,
|
||||
skipFormat: true,
|
||||
skipTsConfig: false,
|
||||
unitTestRunner: 'none',
|
||||
name: libName,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
return appTree;
|
||||
}
|
||||
|
||||
export async function createTestAppLib(
|
||||
libName: string,
|
||||
plainJS = false
|
||||
): Promise<Tree> {
|
||||
let appTree = createTreeWithEmptyWorkspace();
|
||||
|
||||
await applicationGenerator(appTree, {
|
||||
e2eTestRunner: 'none',
|
||||
linter: Linter.EsLint,
|
||||
skipFormat: false,
|
||||
style: 'css',
|
||||
unitTestRunner: 'none',
|
||||
name: libName,
|
||||
js: plainJS,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
await componentGenerator(appTree, {
|
||||
name: 'my-component',
|
||||
project: libName,
|
||||
directory: 'app',
|
||||
});
|
||||
|
||||
return appTree;
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
import { StorybookConfigureSchema } from './schema';
|
||||
import storiesGenerator from '../stories/stories';
|
||||
import {
|
||||
convertNxGenerator,
|
||||
ensurePackage,
|
||||
formatFiles,
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
import { nxVersion } from '../../utils/versions';
|
||||
|
||||
async function generateStories(host: Tree, schema: StorybookConfigureSchema) {
|
||||
await storiesGenerator(host, {
|
||||
project: schema.name,
|
||||
js: schema.js,
|
||||
ignorePaths: schema.ignorePaths,
|
||||
skipFormat: true,
|
||||
interactionTests: schema.interactionTests ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function storybookConfigurationGenerator(
|
||||
host: Tree,
|
||||
schema: StorybookConfigureSchema
|
||||
) {
|
||||
const { configurationGenerator } = ensurePackage<
|
||||
typeof import('@nx/storybook')
|
||||
>('@nx/storybook', nxVersion);
|
||||
|
||||
const installTask = await configurationGenerator(host, {
|
||||
name: schema.name,
|
||||
js: schema.js,
|
||||
linter: schema.linter,
|
||||
tsConfiguration: schema.tsConfiguration ?? true, // default is true
|
||||
interactionTests: schema.interactionTests ?? true, // default is true
|
||||
configureStaticServe: schema.configureStaticServe,
|
||||
uiFramework: '@storybook/vue3-vite',
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
if (schema.generateStories) {
|
||||
await generateStories(host, schema);
|
||||
}
|
||||
|
||||
await formatFiles(host);
|
||||
|
||||
return installTask;
|
||||
}
|
||||
|
||||
export default storybookConfigurationGenerator;
|
||||
export const storybookConfigurationSchematic = convertNxGenerator(
|
||||
storybookConfigurationGenerator
|
||||
);
|
||||
12
packages/vue/src/generators/storybook-configuration/schema.d.ts
vendored
Normal file
12
packages/vue/src/generators/storybook-configuration/schema.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import { Linter } from '@nx/linter';
|
||||
|
||||
export interface StorybookConfigureSchema {
|
||||
name: string;
|
||||
interactionTests?: boolean;
|
||||
generateStories?: boolean;
|
||||
js?: boolean;
|
||||
tsConfiguration?: boolean;
|
||||
linter?: Linter;
|
||||
ignorePaths?: string[];
|
||||
configureStaticServe?: boolean;
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"cli": "nx",
|
||||
"$id": "NxVueStorybookConfigure",
|
||||
"title": "Vue Storybook Configure",
|
||||
"description": "Set up Storybook for a Vue project.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"aliases": ["project", "projectName"],
|
||||
"description": "Project for which to generate Storybook configuration.",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
},
|
||||
"x-prompt": "For which project do you want to generate Storybook configuration?",
|
||||
"x-dropdown": "projects",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"interactionTests": {
|
||||
"type": "boolean",
|
||||
"description": "Set up Storybook interaction tests.",
|
||||
"x-prompt": "Do you want to set up Storybook interaction tests?",
|
||||
"x-priority": "important",
|
||||
"alias": ["configureTestRunner"],
|
||||
"default": true
|
||||
},
|
||||
"generateStories": {
|
||||
"type": "boolean",
|
||||
"description": "Automatically generate `*.stories.ts` files for components declared in this project?",
|
||||
"x-prompt": "Automatically generate *.stories.ts files for components declared in this project?",
|
||||
"default": true,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"configureStaticServe": {
|
||||
"type": "boolean",
|
||||
"description": "Specifies whether to configure a static file server target for serving storybook. Helpful for speeding up CI build/test times.",
|
||||
"x-prompt": "Configure a static file server for the storybook instance?",
|
||||
"default": true,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"js": {
|
||||
"type": "boolean",
|
||||
"description": "Generate JavaScript story files rather than TypeScript story files.",
|
||||
"default": false
|
||||
},
|
||||
"tsConfiguration": {
|
||||
"type": "boolean",
|
||||
"description": "Configure your project with TypeScript. Generate main.ts and preview.ts files, instead of main.js and preview.js.",
|
||||
"default": true
|
||||
},
|
||||
"linter": {
|
||||
"description": "The tool to use for running lint checks.",
|
||||
"type": "string",
|
||||
"enum": ["eslint"],
|
||||
"default": "eslint"
|
||||
},
|
||||
"ignorePaths": {
|
||||
"type": "array",
|
||||
"description": "Paths to ignore when looking for components.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "Path to ignore."
|
||||
},
|
||||
"examples": [
|
||||
"apps/my-app/src/not-stories/**",
|
||||
"**/**/src/**/not-stories/**",
|
||||
"libs/my-lib/**/*.something.ts",
|
||||
"**/**/src/**/*.other.*",
|
||||
"libs/my-lib/src/not-stories/**,**/**/src/**/*.other.*,apps/my-app/**/*.something.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"examplesFile": "../../../docs/storybook-configuration-examples.md"
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user