diff --git a/e2e/qwik-nx-e2e/tests/storybook.spec.ts b/e2e/qwik-nx-e2e/tests/storybook.spec.ts
new file mode 100644
index 0000000..b514bf8
--- /dev/null
+++ b/e2e/qwik-nx-e2e/tests/storybook.spec.ts
@@ -0,0 +1,113 @@
+import {
+ checkFilesExist,
+ ensureNxProject,
+ runNxCommandAsync,
+ uniq,
+} from '@nrwl/nx-plugin/testing';
+
+import {
+ runCommandUntil,
+ promisifiedTreeKill,
+ killPort,
+ killPorts,
+} from '@qwikifiers/e2e/utils';
+
+const STORYBOOK_PORT = 4400;
+
+describe('qwikNxVite plugin e2e', () => {
+ beforeAll(async () => {
+ await killPorts(STORYBOOK_PORT);
+ ensureNxProject('qwik-nx', 'dist/packages/qwik-nx');
+ }, 10000);
+
+ afterAll(async () => {
+ // `nx reset` kills the daemon, and performs
+ // some work which can help clean up e2e leftovers
+ await runNxCommandAsync('reset');
+ });
+
+ describe('should be able to import components from libraries', () => {
+ const appProject = uniq('qwik-nx');
+ const libProject = uniq('qwik-nx');
+ const secondLibProject = uniq('qwik-nx');
+ beforeAll(async () => {
+ await runNxCommandAsync(
+ `generate qwik-nx:app ${appProject} --e2eTestRunner=none --no-interactive`
+ );
+ await runNxCommandAsync(
+ `generate qwik-nx:library ${libProject} --no-interactive`
+ );
+ await runNxCommandAsync(
+ `generate qwik-nx:storybook-configuration ${appProject} --no-interactive`
+ );
+ await runNxCommandAsync(
+ `generate qwik-nx:storybook-configuration ${libProject} --no-interactive`
+ );
+ }, 200000);
+
+ describe('Applying storybook for existing application', () => {
+ checkStorybookIsBuiltAndServed(appProject, 'apps', false);
+ });
+ describe('Applying storybook for existing library', () => {
+ checkStorybookIsBuiltAndServed(libProject, 'libs', false);
+ });
+
+ describe('Generating a new library with storybook configuration', () => {
+ beforeAll(async () => {
+ await runNxCommandAsync(
+ `generate qwik-nx:library ${secondLibProject} --storybookConfiguration=true --no-interactive`
+ );
+ }, 200000);
+ checkStorybookIsBuiltAndServed(secondLibProject, 'libs', true);
+ });
+ });
+});
+
+function checkStorybookIsBuiltAndServed(
+ projectName: string,
+ type: 'apps' | 'libs',
+ hasTsStories: boolean
+) {
+ it(`should be able to build storybook for the "${projectName}"`, async () => {
+ const result = await runNxCommandAsync(`build-storybook ${projectName}`);
+ expect(result.stdout).toContain(
+ `Successfully ran target build-storybook for project ${projectName}`
+ );
+ expect(() =>
+ checkFilesExist(`dist/storybook/${projectName}/index.html`)
+ ).not.toThrow();
+ }, 200000);
+
+ it(`should serve storybook for the "${projectName}"`, async () => {
+ let resultOutput: string | undefined;
+ const p = await runCommandUntil(
+ `run ${projectName}:storybook`,
+ (output) => {
+ if (
+ output.includes('Local:') &&
+ output.includes(`:${STORYBOOK_PORT}`)
+ ) {
+ resultOutput = output;
+ return true;
+ }
+ return false;
+ }
+ );
+
+ // it is expected that projects won't have stories by default and storybook should recognize it.
+ expect(resultOutput).toContain(
+ `No story files found for the specified pattern: ${type}/${projectName}/**/*.stories.mdx`
+ );
+ if (!hasTsStories) {
+ expect(resultOutput).toContain(
+ `No story files found for the specified pattern: ${type}/${projectName}/**/*.stories.@(js|jsx|ts|tsx)`
+ );
+ }
+ try {
+ await promisifiedTreeKill(p.pid!, 'SIGKILL');
+ await killPort(STORYBOOK_PORT);
+ } catch {
+ // ignore
+ }
+ }, 200000);
+}
diff --git a/package.json b/package.json
index 78c87ba..5923c60 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"@nrwl/js": "15.8.2",
"@nrwl/linter": "15.8.2",
"@nrwl/nx-plugin": "15.8.2",
+ "@nrwl/storybook": "15.8.2",
"@nrwl/vite": "15.8.2",
"@nrwl/workspace": "15.8.2",
"@nxkit/playwright": "^2.1.2",
diff --git a/packages/qwik-nx/generators.json b/packages/qwik-nx/generators.json
index e78bc4a..2337d0c 100644
--- a/packages/qwik-nx/generators.json
+++ b/packages/qwik-nx/generators.json
@@ -64,6 +64,11 @@
"factory": "./src/generators/remote/generator",
"schema": "./src/generators/remote/schema.json",
"description": "Generate a remote Qwik application for the micro frontend setup"
+ },
+ "storybook-configuration": {
+ "factory": "./src/generators/storybook-configuration/generator",
+ "schema": "./src/generators/storybook-configuration/schema.json",
+ "description": "Adds Storybook configuration to a project."
}
}
}
diff --git a/packages/qwik-nx/src/generators/component/files/storybook/__fileName__.doc.mdx.template b/packages/qwik-nx/src/generators/component/files/storybook/__fileName__.doc.mdx.template
new file mode 100644
index 0000000..91376be
--- /dev/null
+++ b/packages/qwik-nx/src/generators/component/files/storybook/__fileName__.doc.mdx.template
@@ -0,0 +1,26 @@
+import { Canvas, Story } from '@storybook/addon-docs';
+import { <%- className %> } from './<%- fileName %>';
+
+# <%- className %> Component
+
+## Purpose
+
+{/* Why the component is needed */}
+
+## Example
+
+{/* Common copy/paste example that people can throw into their templates and ts */}
+
+~~~tsx
+<<%- className %> param="value" />
+~~~
+
+## Use case examples
+
+{/* Examples based on use cases */}
+
+### Primary
+
+
diff --git a/packages/qwik-nx/src/generators/component/files/storybook/__fileName__.stories.tsx.template b/packages/qwik-nx/src/generators/component/files/storybook/__fileName__.stories.tsx.template
new file mode 100644
index 0000000..0cc867f
--- /dev/null
+++ b/packages/qwik-nx/src/generators/component/files/storybook/__fileName__.stories.tsx.template
@@ -0,0 +1,21 @@
+import type { Meta } from 'storybook-framework-qwik';
+import { <%- className %> } from './<%- fileName %>';
+import doc from './<%- fileName %>.doc.mdx';
+
+export default {
+ title: '<%- className %>',
+ tags: ['autodocs'],
+ parameters: {
+ docs: {
+ page: doc,
+ },
+ },
+ argTypes: {
+ // put component params here
+ },
+ render(args) {
+ return <<%- className %> {...args}/>;
+ },
+} as Meta;
+
+export const Primary = {};
diff --git a/packages/qwik-nx/src/generators/component/generator.ts b/packages/qwik-nx/src/generators/component/generator.ts
index 3debf69..d9fe110 100644
--- a/packages/qwik-nx/src/generators/component/generator.ts
+++ b/packages/qwik-nx/src/generators/component/generator.ts
@@ -9,6 +9,7 @@ import {
Tree,
} from '@nrwl/devkit';
import { addStyledModuleDependencies } from '../../utils/add-styled-dependencies';
+import { ensureMdxTypeInTsConfig } from '../../utils/ensure-file-utils';
import { ComponentGeneratorSchema } from './schema';
interface NormalizedSchema extends ComponentGeneratorSchema {
@@ -95,6 +96,15 @@ function createComponentFiles(tree: Tree, options: NormalizedSchema) {
templateOptions
);
}
+ if (options.generateStories) {
+ generateFiles(
+ tree,
+ joinPathFragments(__dirname, 'files/storybook'),
+ componentDir,
+ templateOptions
+ );
+ ensureMdxTypeInTsConfig(tree, options.project);
+ }
}
export async function componentGenerator(
diff --git a/packages/qwik-nx/src/generators/component/schema.d.ts b/packages/qwik-nx/src/generators/component/schema.d.ts
index 1045346..ab68c38 100644
--- a/packages/qwik-nx/src/generators/component/schema.d.ts
+++ b/packages/qwik-nx/src/generators/component/schema.d.ts
@@ -5,4 +5,5 @@ export interface ComponentGeneratorSchema {
style?: 'none' | 'css' | 'scss' | 'styl' | 'less';
skipTests?: boolean;
flat?: boolean;
+ generateStories?: boolean;
}
diff --git a/packages/qwik-nx/src/generators/component/schema.json b/packages/qwik-nx/src/generators/component/schema.json
index 2cf3390..40996b5 100644
--- a/packages/qwik-nx/src/generators/component/schema.json
+++ b/packages/qwik-nx/src/generators/component/schema.json
@@ -68,6 +68,10 @@
"type": "boolean",
"description": "Create component at the source root rather than its own directory.",
"default": false
+ },
+ "generateStories": {
+ "description": "Create Storybook stories for the component",
+ "type": "boolean"
}
},
"required": ["name", "project"]
diff --git a/packages/qwik-nx/src/generators/e2e-project/generator.spec.ts b/packages/qwik-nx/src/generators/e2e-project/generator.spec.ts
index 1f5b4ef..41aff1c 100644
--- a/packages/qwik-nx/src/generators/e2e-project/generator.spec.ts
+++ b/packages/qwik-nx/src/generators/e2e-project/generator.spec.ts
@@ -20,9 +20,9 @@ describe('e2e project', () => {
.spyOn(getInstalledNxVersionModule, 'getInstalledNxVersion')
.mockReturnValue('15.6.0');
- beforeEach(() => {
+ beforeEach(async () => {
appTree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
- appGenerator(appTree, {
+ await appGenerator(appTree, {
name: 'myapp',
e2eTestRunner: 'none',
});
diff --git a/packages/qwik-nx/src/generators/integrations/cloudflare-pages-integration/generator.spec.ts b/packages/qwik-nx/src/generators/integrations/cloudflare-pages-integration/generator.spec.ts
index 40ebf4d..4bfd1a6 100644
--- a/packages/qwik-nx/src/generators/integrations/cloudflare-pages-integration/generator.spec.ts
+++ b/packages/qwik-nx/src/generators/integrations/cloudflare-pages-integration/generator.spec.ts
@@ -18,10 +18,10 @@ describe('cloudflare-pages-integration generator', () => {
project: projectName,
};
- beforeEach(() => {
+ beforeEach(async () => {
appTree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
- applicationGenerator(appTree, {
+ await applicationGenerator(appTree, {
name: projectName,
e2eTestRunner: 'none',
linter: Linter.None,
diff --git a/packages/qwik-nx/src/generators/library/__snapshots__/generator.spec.ts.snap b/packages/qwik-nx/src/generators/library/__snapshots__/generator.spec.ts.snap
index 4e23660..82ed1b6 100644
--- a/packages/qwik-nx/src/generators/library/__snapshots__/generator.spec.ts.snap
+++ b/packages/qwik-nx/src/generators/library/__snapshots__/generator.spec.ts.snap
@@ -67,8 +67,7 @@ export default defineConfig({
}),
],
- mode: 'lib',
- // Configuration for building your library.
+ // Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
lib: {
@@ -164,6 +163,10 @@ Array [
"path": "libs/mylib/vite.config.ts",
"type": "CREATE",
},
+ Object {
+ "path": "libs/mylib/src/root.tsx",
+ "type": "CREATE",
+ },
Object {
"path": "libs/mylib/src/lib/mylib.tsx",
"type": "CREATE",
@@ -261,6 +264,10 @@ Array [
"path": "libs/mylib/.eslintrc.json",
"type": "CREATE",
},
+ Object {
+ "path": "libs/mylib/src/root.tsx",
+ "type": "CREATE",
+ },
Object {
"path": "libs/mylib/src/lib/mylib.tsx",
"type": "CREATE",
@@ -404,6 +411,10 @@ Array [
"path": "libs/mylib/vite.config.ts",
"type": "CREATE",
},
+ Object {
+ "path": "libs/mylib/src/root.tsx",
+ "type": "CREATE",
+ },
Object {
"path": "libs/mylib/src/lib/mylib.tsx",
"type": "CREATE",
diff --git a/packages/qwik-nx/src/generators/library/files/tsconfig.json.template b/packages/qwik-nx/src/generators/library/files/tsconfig.json.template
index 0482a45..06c49ec 100644
--- a/packages/qwik-nx/src/generators/library/files/tsconfig.json.template
+++ b/packages/qwik-nx/src/generators/library/files/tsconfig.json.template
@@ -19,7 +19,12 @@
"isolatedModules": true,
"outDir": "tmp",
"noEmit": true,
- "types": ["node", "vite/client"<% if(setupVitest) { %> , "vitest" <% } %>]
+ "types": [
+ "node",
+ "vite/client",
+ <% if(setupVitest) { %>"vitest", <% } %>
+ <% if(storybookConfiguration) { %>"mdx", <% } %>
+ ]
},
"files": [],
"include": [],
diff --git a/packages/qwik-nx/src/generators/library/files/vite.config.ts.template b/packages/qwik-nx/src/generators/library/files/vite.config.ts.template
index 060dc96..dcebac6 100644
--- a/packages/qwik-nx/src/generators/library/files/vite.config.ts.template
+++ b/packages/qwik-nx/src/generators/library/files/vite.config.ts.template
@@ -19,8 +19,7 @@ export default defineConfig({
}),
<% } %>
],
- <% if(buildable) { %> mode: 'lib',
- // Configuration for building your library.
+ <% if(buildable) { %> // Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
lib: {
diff --git a/packages/qwik-nx/src/generators/library/generator.ts b/packages/qwik-nx/src/generators/library/generator.ts
index 8a3a48e..8959bc5 100644
--- a/packages/qwik-nx/src/generators/library/generator.ts
+++ b/packages/qwik-nx/src/generators/library/generator.ts
@@ -22,6 +22,8 @@ import { initGenerator } from '@nrwl/vite';
import { addCommonQwikDependencies } from '../../utils/add-common-qwik-dependencies';
import { getQwikLibProjectTargets } from './utils/get-qwik-lib-project-params';
import { normalizeOptions } from './utils/normalize-options';
+import storybookConfigurationGenerator from '../storybook-configuration/generator';
+import { ensureRootTsxExists } from '../../utils/ensure-file-utils';
export async function libraryGenerator(
tree: Tree,
@@ -62,7 +64,6 @@ async function addLibrary(
linter: Linter.None,
importPath: options.importPath,
strict: options.strict,
- standaloneConfig: options.standaloneConfig,
unitTestRunner: 'none',
skipBabelrc: true,
skipFormat: true,
@@ -84,6 +85,8 @@ async function addLibrary(
templateOptions
);
+ ensureRootTsxExists(tree, options.projectName);
+
if (!options.setupVitest) {
tree.delete(`${options.projectRoot}/tsconfig.spec.json`);
}
@@ -95,11 +98,16 @@ async function addLibrary(
}
}
+ if (options.storybookConfiguration) {
+ tasks.push(await configureStorybook(tree, options));
+ }
+
const componentGeneratorTask = await componentGenerator(tree, {
name: options.name,
skipTests: !options.setupVitest,
style: options.style,
project: options.projectName,
+ generateStories: options.storybookConfiguration,
flat: true,
});
@@ -126,4 +134,13 @@ async function configureVite(tree: Tree, options: NormalizedSchema) {
return callback;
}
+async function configureStorybook(
+ tree: Tree,
+ options: NormalizedSchema
+): Promise {
+ return storybookConfigurationGenerator(tree, {
+ name: options.projectName,
+ });
+}
+
export default libraryGenerator;
diff --git a/packages/qwik-nx/src/generators/library/schema.d.ts b/packages/qwik-nx/src/generators/library/schema.d.ts
index 594a743..beacb04 100644
--- a/packages/qwik-nx/src/generators/library/schema.d.ts
+++ b/packages/qwik-nx/src/generators/library/schema.d.ts
@@ -11,13 +11,14 @@ export interface LibraryGeneratorSchema {
importPath?: string;
strict?: boolean;
buildable?: boolean;
- standaloneConfig?: boolean;
+ storybookConfiguration?: boolean;
}
type NormalizedRequiredPropsNames =
| 'style'
| 'unitTestRunner'
| 'linter'
+ | 'storybookConfiguration'
| 'buildable';
type NormalizedRequiredProps = Required<
Pick
diff --git a/packages/qwik-nx/src/generators/library/schema.json b/packages/qwik-nx/src/generators/library/schema.json
index fe0c6e5..defcb56 100644
--- a/packages/qwik-nx/src/generators/library/schema.json
+++ b/packages/qwik-nx/src/generators/library/schema.json
@@ -88,9 +88,10 @@
"description": "Whether to enable `tsconfig` strict mode or not.",
"default": true
},
- "standaloneConfig": {
- "description": "Split the project configuration into `/project.json` rather than including it inside `workspace.json`.",
- "type": "boolean"
+ "storybookConfiguration": {
+ "description": "Whether to include storybook configuration for the generated library.",
+ "type": "boolean",
+ "default": false
}
},
"required": ["name"]
diff --git a/packages/qwik-nx/src/generators/library/utils/normalize-options.ts b/packages/qwik-nx/src/generators/library/utils/normalize-options.ts
index d34b0e1..5ce5854 100644
--- a/packages/qwik-nx/src/generators/library/utils/normalize-options.ts
+++ b/packages/qwik-nx/src/generators/library/utils/normalize-options.ts
@@ -22,6 +22,7 @@ export function normalizeOptions(
unitTestRunner: schema.unitTestRunner ?? 'vitest',
linter: schema.linter ?? Linter.EsLint,
buildable: !!schema.buildable,
+ storybookConfiguration: !!schema.storybookConfiguration,
};
return {
diff --git a/packages/qwik-nx/src/generators/storybook-configuration/__snapshots__/generator.spec.ts.snap b/packages/qwik-nx/src/generators/storybook-configuration/__snapshots__/generator.spec.ts.snap
new file mode 100644
index 0000000..5d97d1f
--- /dev/null
+++ b/packages/qwik-nx/src/generators/storybook-configuration/__snapshots__/generator.spec.ts.snap
@@ -0,0 +1,146 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`storybook-configuration generator should add required targets 1`] = `
+Array [
+ Object {
+ "path": ".prettierrc",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "package.json",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "nx.json",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "tsconfig.base.json",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/.gitignore",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "libs/.gitignore",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/project.json",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/.eslintrc.json",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/.prettierignore",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/README.md",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/package.json",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/public/favicon.svg",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/public/manifest.json",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/public/robots.txt",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/src/components/header/header.css",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/src/components/header/header.tsx",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/src/components/icons/qwik.tsx",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/src/components/router-head/router-head.tsx",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/src/entry.dev.tsx",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/src/entry.preview.tsx",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/src/entry.ssr.tsx",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/src/global.css",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/src/root.tsx",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/src/routes/flower/flower.css",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/src/routes/flower/index.tsx",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/src/routes/index.tsx",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/src/routes/layout.tsx",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/src/routes/service-worker.ts",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/tsconfig.app.json",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/tsconfig.json",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/tsconfig.spec.json",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/vite.config.ts",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/.storybook/main.ts",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/.storybook/preview.ts",
+ "type": "CREATE",
+ },
+ Object {
+ "path": "apps/test-project/.storybook/tsconfig.json",
+ "type": "CREATE",
+ },
+]
+`;
diff --git a/packages/qwik-nx/src/generators/storybook-configuration/files/.storybook/main.__configExtension__.template b/packages/qwik-nx/src/generators/storybook-configuration/files/.storybook/main.__configExtension__.template
new file mode 100644
index 0000000..866ce4f
--- /dev/null
+++ b/packages/qwik-nx/src/generators/storybook-configuration/files/.storybook/main.__configExtension__.template
@@ -0,0 +1,16 @@
+import { mergeConfig, UserConfig } from 'vite';
+import viteConfig from './../vite.config';
+
+const config = {
+ stories: [
+ '../**/*.stories.mdx',
+ '../**/*.stories.@(js|jsx|ts|tsx)'
+ ],
+ addons: ['@storybook/addon-essentials'],
+ framework: { name: 'storybook-framework-qwik', },
+ async viteFinal(config: UserConfig) {
+ return mergeConfig(config, viteConfig);
+ },
+};
+
+export default config;
diff --git a/packages/qwik-nx/src/generators/storybook-configuration/generator.spec.ts b/packages/qwik-nx/src/generators/storybook-configuration/generator.spec.ts
new file mode 100644
index 0000000..36302de
--- /dev/null
+++ b/packages/qwik-nx/src/generators/storybook-configuration/generator.spec.ts
@@ -0,0 +1,70 @@
+import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
+import { Tree, readProjectConfiguration } from '@nrwl/devkit';
+
+import { storybookConfigurationGenerator } from './generator';
+import { StorybookConfigurationGeneratorSchema } from './schema';
+import appGenerator from '../application/generator';
+import { Linter } from '@nrwl/linter';
+
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const devkit = require('@nrwl/devkit');
+const getInstalledNxVersionModule = require('../../utils/get-installed-nx-version');
+
+describe('storybook-configuration generator', () => {
+ let appTree: Tree;
+ const projectName = 'test-project';
+ const options: StorybookConfigurationGeneratorSchema = { name: projectName };
+
+ jest.spyOn(devkit, 'ensurePackage').mockReturnValue(Promise.resolve());
+ jest
+ .spyOn(getInstalledNxVersionModule, 'getInstalledNxVersion')
+ .mockReturnValue('15.6.0');
+
+ beforeEach(async () => {
+ appTree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+
+ await appGenerator(appTree, {
+ name: projectName,
+ e2eTestRunner: 'none',
+ linter: Linter.None,
+ skipFormat: false,
+ strict: true,
+ style: 'css',
+ unitTestRunner: 'none',
+ });
+ });
+
+ it('should add required targets', async () => {
+ await storybookConfigurationGenerator(appTree, options);
+ const config = readProjectConfiguration(appTree, projectName);
+ expect(config.targets!['storybook']).toEqual({
+ executor: '@nrwl/storybook:storybook',
+ options: {
+ port: 4400,
+ configDir: `apps/${projectName}/.storybook`,
+ },
+ configurations: {
+ ci: {
+ quiet: true,
+ },
+ },
+ });
+ expect(config.targets!['build-storybook']).toEqual({
+ executor: '@nrwl/storybook:build',
+ outputs: ['{options.outputDir}'],
+ options: {
+ configDir: `apps/${projectName}/.storybook`,
+ outputDir: `dist/storybook/${projectName}`,
+ },
+ configurations: {
+ ci: {
+ quiet: true,
+ },
+ },
+ });
+
+ expect(
+ appTree.listChanges().map((c) => ({ path: c.path, type: c.type }))
+ ).toMatchSnapshot();
+ });
+});
diff --git a/packages/qwik-nx/src/generators/storybook-configuration/generator.ts b/packages/qwik-nx/src/generators/storybook-configuration/generator.ts
new file mode 100644
index 0000000..4d0e4ae
--- /dev/null
+++ b/packages/qwik-nx/src/generators/storybook-configuration/generator.ts
@@ -0,0 +1,105 @@
+import {
+ addDependenciesToPackageJson,
+ ensurePackage,
+ formatFiles,
+ generateFiles,
+ GeneratorCallback,
+ names,
+ offsetFromRoot,
+ readProjectConfiguration,
+ Tree,
+} from '@nrwl/devkit';
+import { Linter } from '@nrwl/linter';
+import * as path from 'path';
+import {
+ ensureMdxTypeInTsConfig,
+ ensureRootTsxExists,
+} from '../../utils/ensure-file-utils';
+import { getInstalledNxVersion } from '../../utils/get-installed-nx-version';
+import {
+ storybookFrameworkQwikVersion,
+ storybookReactDOMVersion,
+ storybookReactVersion,
+ typesMdx,
+} from '../../utils/versions';
+import {
+ NormalizedSchema,
+ StorybookConfigurationGeneratorSchema,
+} from './schema';
+
+function addFiles(tree: Tree, options: StorybookConfigurationGeneratorSchema) {
+ const { root } = readProjectConfiguration(tree, options.name);
+
+ tree.delete(path.join(root, '.storybook/main.js'));
+
+ const templateOptions = {
+ ...options,
+ ...names(options.name),
+ offsetFromRoot: offsetFromRoot(root),
+ projectRoot: root,
+ configExtension: options.tsConfiguration ? 'ts' : 'js',
+ };
+ generateFiles(tree, path.join(__dirname, 'files'), root, templateOptions);
+
+ ensureRootTsxExists(tree, options.name);
+ ensureMdxTypeInTsConfig(tree, options.name);
+}
+
+function normalizeOptions(
+ options: StorybookConfigurationGeneratorSchema
+): NormalizedSchema {
+ return {
+ ...options,
+ js: !!options.js,
+ linter: options.linter ?? Linter.EsLint,
+ tsConfiguration: options.tsConfiguration ?? true,
+ };
+}
+
+export async function storybookConfigurationGenerator(
+ tree: Tree,
+ options: StorybookConfigurationGeneratorSchema
+): Promise {
+ const normalizedOptions = normalizeOptions(options);
+ ensurePackage('@nrwl/storybook', getInstalledNxVersion(tree));
+ const { configurationGenerator } = await import('@nrwl/storybook');
+
+ await configurationGenerator(tree, {
+ uiFramework: '@storybook/html',
+ bundler: 'vite',
+ name: normalizedOptions.name,
+ js: normalizedOptions.js,
+ linter: normalizedOptions.linter,
+ tsConfiguration: normalizedOptions.tsConfiguration,
+ storybook7betaConfiguration: true,
+ configureCypress: false,
+ });
+
+ addFiles(tree, normalizedOptions);
+ await formatFiles(tree);
+
+ return addStorybookDependencies(tree);
+}
+
+async function addStorybookDependencies(
+ tree: Tree
+): Promise {
+ const { storybook7Version } = await import(
+ '@nrwl/storybook/src/utils/versions'
+ );
+
+ return addDependenciesToPackageJson(
+ tree,
+ {},
+ {
+ 'storybook-framework-qwik': storybookFrameworkQwikVersion,
+ '@storybook/builder-vite': storybook7Version,
+ '@storybook/addon-docs': storybook7Version,
+ react: storybookReactVersion,
+ 'react-dom': storybookReactDOMVersion,
+ '@types/mdx': typesMdx,
+ }
+ );
+}
+
+export default storybookConfigurationGenerator;
diff --git a/packages/qwik-nx/src/generators/storybook-configuration/schema.d.ts b/packages/qwik-nx/src/generators/storybook-configuration/schema.d.ts
new file mode 100644
index 0000000..178772b
--- /dev/null
+++ b/packages/qwik-nx/src/generators/storybook-configuration/schema.d.ts
@@ -0,0 +1,19 @@
+import { Linter } from '@nrwl/linter';
+
+export interface StorybookConfigurationGeneratorSchema {
+ name: string;
+ linter?: Linter;
+ js?: boolean;
+ tsConfiguration?: boolean;
+}
+
+type NormalizedRequiredPropsNames = 'js' | 'linter' | 'tsConfiguration';
+type NormalizedRequiredProps = Required<
+ Pick
+>;
+
+export type NormalizedSchema = Omit<
+ StorybookConfigurationGeneratorSchema,
+ NormalizedRequiredPropsNames
+> &
+ NormalizedRequiredProps;
diff --git a/packages/qwik-nx/src/generators/storybook-configuration/schema.json b/packages/qwik-nx/src/generators/storybook-configuration/schema.json
new file mode 100644
index 0000000..82fc1e6
--- /dev/null
+++ b/packages/qwik-nx/src/generators/storybook-configuration/schema.json
@@ -0,0 +1,40 @@
+{
+ "$schema": "http://json-schema.org/schema",
+ "cli": "nx",
+ "$id": "StorybookConfiguration",
+ "title": "Adds Storybook configuration to a project.",
+ "description": "Adds Storybook configuration to a project to be able to use and create stories.",
+ "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"
+ },
+ "linter": {
+ "description": "The tool to use for running lint checks.",
+ "type": "string",
+ "enum": ["eslint", "none"],
+ "default": "eslint"
+ },
+ "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,
+ "x-priority": "important"
+ }
+ },
+ "required": ["name"]
+}
diff --git a/packages/qwik-nx/src/utils/ensure-file-utils.ts b/packages/qwik-nx/src/utils/ensure-file-utils.ts
new file mode 100644
index 0000000..94a9cf1
--- /dev/null
+++ b/packages/qwik-nx/src/utils/ensure-file-utils.ts
@@ -0,0 +1,32 @@
+import { readJson, readProjectConfiguration, Tree } from '@nrwl/devkit';
+import { join } from 'path';
+
+/** Creates root.tsx if it is not found */
+export function ensureRootTsxExists(tree: Tree, projectName: string) {
+ const projectConfig = readProjectConfiguration(tree, projectName);
+
+ const sourceRoot =
+ projectConfig.sourceRoot ?? join(projectConfig.root, 'src');
+ const rootTsxPath = join(sourceRoot, 'root.tsx');
+
+ if (!tree.exists(rootTsxPath)) {
+ tree.write(rootTsxPath, rootTsxContent);
+ }
+}
+
+const rootTsxContent = `// This is explicitly empty, but serves as a compilation entry-point for the client mode.
+export default {};`;
+
+export function ensureMdxTypeInTsConfig(tree: Tree, projectName: string) {
+ const projectConfig = readProjectConfiguration(tree, projectName);
+
+ const tsConfigPath = join(projectConfig.root, 'tsconfig.json');
+ if (tree.exists(tsConfigPath)) {
+ const tsConfig = readJson(tree, tsConfigPath);
+
+ if (!((tsConfig.compilerOptions ??= {}).types ??= []).includes('mdx')) {
+ tsConfig.compilerOptions.types.push('mdx');
+ tree.write(tsConfigPath, JSON.stringify(tsConfig));
+ }
+ }
+}
diff --git a/packages/qwik-nx/src/utils/versions.ts b/packages/qwik-nx/src/utils/versions.ts
index 9e1b385..d6ca5fe 100644
--- a/packages/qwik-nx/src/utils/versions.ts
+++ b/packages/qwik-nx/src/utils/versions.ts
@@ -24,6 +24,12 @@ export const nxKitVersion = '^2.1.2';
export const wranglerVersion = '^2.8.0';
export const nxCloudflareWrangler = '^2.0.0';
+// storybook
+export const storybookFrameworkQwikVersion = '^0.0.8';
+export const storybookReactVersion = '^18.0.0';
+export const storybookReactDOMVersion = '^18.0.0';
+export const typesMdx = '^2.0.3';
+
// other
export const eslintVersion = '~8.36.0';
export const tsEslintVersion = '~5.43.0';
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7452e0a..da0ff3a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -16,6 +16,7 @@ importers:
'@nrwl/js': 15.8.2
'@nrwl/linter': 15.8.2
'@nrwl/nx-plugin': 15.8.2
+ '@nrwl/storybook': 15.8.2
'@nrwl/vite': 15.8.2
'@nrwl/workspace': 15.8.2
'@nxkit/playwright': ^2.1.2
@@ -74,6 +75,7 @@ importers:
'@nrwl/js': 15.8.2_f5tjcz6mhtzkzfqnengccsh7d4
'@nrwl/linter': 15.8.2_f5tjcz6mhtzkzfqnengccsh7d4
'@nrwl/nx-plugin': 15.8.2_bp5jfpqvuv6yvfesjqccwfqew4
+ '@nrwl/storybook': 15.8.2_f5tjcz6mhtzkzfqnengccsh7d4
'@nrwl/vite': 15.8.2_rr7rokjmekkdx2xr3kv4tzxvui
'@nrwl/workspace': 15.8.2_53wkrcrrxffq2surs33fwlwix4
'@nxkit/playwright': 2.1.2_@playwright+test@1.30.0
@@ -3292,6 +3294,32 @@ packages:
dev: true
optional: true
+ /@nrwl/storybook/15.8.2_f5tjcz6mhtzkzfqnengccsh7d4:
+ resolution:
+ {
+ integrity: sha512-rSA6utYeCn07+0Z/w0vlkNeF1eE4Slct5EMoZKhgvw+4z0ZLQMABOxj7rnsifakmMr4tXKEJK+VZiR33xHlmwQ==,
+ }
+ dependencies:
+ '@nrwl/cypress': 15.8.2_f5tjcz6mhtzkzfqnengccsh7d4
+ '@nrwl/devkit': 15.8.2_nx@15.8.2+typescript@4.9.5
+ '@nrwl/js': 15.8.2_f5tjcz6mhtzkzfqnengccsh7d4
+ '@nrwl/linter': 15.8.2_f5tjcz6mhtzkzfqnengccsh7d4
+ '@nrwl/workspace': 15.8.2_53wkrcrrxffq2surs33fwlwix4
+ '@phenomnomnominal/tsquery': 4.1.1_typescript@4.9.5
+ dotenv: 10.0.0
+ semver: 7.3.4
+ transitivePeerDependencies:
+ - '@swc-node/register'
+ - '@swc/core'
+ - cypress
+ - debug
+ - eslint
+ - nx
+ - prettier
+ - supports-color
+ - typescript
+ dev: true
+
/@nrwl/tao/15.8.2_lwc5ciab46qbgcufzept4zyhgi:
resolution:
{
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 28fb8e4..5f1f818 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -18,7 +18,7 @@
"baseUrl": ".",
"paths": {
"@qwikifiers/e2e/utils": ["e2e/utils"],
- "qwik-nx": ["packages/qwik-nx/src/index.ts"]
+ "qwik-nx": ["packages/qwik-nx/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]