diff --git a/README.md b/README.md index 09db01f2fc1f..ab9c9cab4456 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ For additional help, join us in the [Storybook Discord](https://discord.gg/story | [Svelte](code/renderers/svelte) | [![Storybook demo](https://img.shields.io/npm/v/@storybook/svelte/latest?style=flat-square&color=blue&label)](https://storybookjs.netlify.com/svelte-kitchen-sink/) | [![Svelte](https://img.shields.io/npm/dm/@storybook/svelte?style=flat-square&color=eee)](code/renderers/svelte) | | [Preact](code/renderers/preact) | [![Storybook demo](https://img.shields.io/npm/v/@storybook/preact/latest?style=flat-square&color=blue&label)](https://storybookjs.netlify.com/preact-kitchen-sink/) | [![Preact](https://img.shields.io/npm/dm/@storybook/preact?style=flat-square&color=eee)](code/renderers/preact) | | [Marionette.js](https://github.com/storybookjs/marionette) | - | [![Marionette.js](https://img.shields.io/npm/dm/@storybook/marionette?style=flat-square&color=eee)](https://github.com/storybookjs/marionette) | +| [Qwik](https://github.com/literalpie/storybook-framework-qwik) | - | [![Qwik](https://img.shields.io/npm/dm/storybook-framework-qwik?style=flat-square&color=eee)](https://github.com/literalpie/storybook-framework-qwik) | | [Android, iOS, Flutter](https://github.com/storybookjs/native) | [![Storybook demo](https://img.shields.io/npm/v/@storybook/native/latest?style=flat-square&color=blue&label)](https://storybookjs.github.io/native/@storybook/native-flutter-example/index.html) | [![Native](https://img.shields.io/npm/dm/@storybook/native?style=flat-square&color=eee)](https://github.com/storybookjs/native) | ### Sub Projects diff --git a/code/lib/cli/src/detect.test.ts b/code/lib/cli/src/detect.test.ts index 597e2fc7818d..f709345b10b9 100644 --- a/code/lib/cli/src/detect.test.ts +++ b/code/lib/cli/src/detect.test.ts @@ -89,6 +89,16 @@ const MOCK_FRAMEWORK_FILES: { }, }, }, + { + name: ProjectType.QWIK, + files: { + 'package.json': { + devDependencies: { + '@builder.io/qwik': '1.0.0', + }, + }, + }, + }, { name: ProjectType.REACT_NATIVE, files: { diff --git a/code/lib/cli/src/dirs.ts b/code/lib/cli/src/dirs.ts index 69a411752360..1ac239604cf6 100644 --- a/code/lib/cli/src/dirs.ts +++ b/code/lib/cli/src/dirs.ts @@ -1,12 +1,17 @@ import { dirname } from 'path'; import type { SupportedFrameworks, SupportedRenderers } from './project_types'; +import { externalFrameworks } from './project_types'; export function getCliDir() { return dirname(require.resolve('@storybook/cli/package.json')); } export function getRendererDir(renderer: SupportedFrameworks | SupportedRenderers) { + const externalFramework = externalFrameworks.find((framework) => framework.name === renderer); + const frameworkPackageName = externalFramework?.packageName ?? `@storybook/${renderer}`; return dirname( - require.resolve(`@storybook/${renderer}/package.json`, { paths: [process.cwd()] }) + require.resolve(`${frameworkPackageName}/package.json`, { + paths: [process.cwd()], + }) ); } diff --git a/code/lib/cli/src/generators/QWIK/index.ts b/code/lib/cli/src/generators/QWIK/index.ts new file mode 100644 index 000000000000..baa3c86ac94d --- /dev/null +++ b/code/lib/cli/src/generators/QWIK/index.ts @@ -0,0 +1,8 @@ +import { baseGenerator } from '../baseGenerator'; +import type { Generator } from '../types'; + +const generator: Generator = async (packageManager, npmOptions, options) => { + await baseGenerator(packageManager, npmOptions, options, 'qwik', {}, 'qwik'); +}; + +export default generator; diff --git a/code/lib/cli/src/generators/baseGenerator.ts b/code/lib/cli/src/generators/baseGenerator.ts index bfd1ab8ac3e3..662e214ae878 100644 --- a/code/lib/cli/src/generators/baseGenerator.ts +++ b/code/lib/cli/src/generators/baseGenerator.ts @@ -3,7 +3,7 @@ import fse from 'fs-extra'; import { dedent } from 'ts-dedent'; import type { NpmOptions } from '../NpmOptions'; import type { SupportedRenderers, SupportedFrameworks, Builder } from '../project_types'; -import { CoreBuilder } from '../project_types'; +import { externalFrameworks, CoreBuilder } from '../project_types'; import { getBabelDependencies, copyComponents } from '../helpers'; import { configureMain, configurePreview } from './configure'; import type { JsPackageManager } from '../js-package-manager'; @@ -41,6 +41,19 @@ const getBuilderDetails = (builder: string) => { return builder; }; +const getExternalFramework = (framework: string) => + externalFrameworks.find( + (exFramework) => exFramework.name === framework || exFramework.packageName === framework + ); + +const getFrameworkPackage = (framework: string, renderer: string, builder: string) => { + const externalFramework = getExternalFramework(framework); + if (externalFramework) { + return externalFramework.packageName; + } + return framework ? `@storybook/${framework}` : `@storybook/${renderer}-${builder}`; +}; + const wrapForPnp = (packageName: string) => `%%path.dirname(require.resolve(path.join('${packageName}', 'package.json')))%%`; @@ -57,9 +70,8 @@ const getFrameworkDetails = ( renderer?: string; rendererId: SupportedRenderers; } => { - const frameworkPackage = framework - ? `@storybook/${framework}` - : `@storybook/${renderer}-${builder}`; + const frameworkPackage = getFrameworkPackage(framework, renderer, builder); + const frameworkPackagePath = pnp ? wrapForPnp(frameworkPackage) : frameworkPackage; const rendererPackage = `@storybook/${renderer}`; @@ -68,7 +80,9 @@ const getFrameworkDetails = ( const builderPackage = getBuilderDetails(builder); const builderPackagePath = pnp ? wrapForPnp(builderPackage) : builderPackage; - const isKnownFramework = !!(packageVersions as Record)[frameworkPackage]; + const isExternalFramework = !!getExternalFramework(frameworkPackage); + const isKnownFramework = + isExternalFramework || !!(packageVersions as Record)[frameworkPackage]; const isKnownRenderer = !!(packageVersions as Record)[rendererPackage]; if (isKnownFramework) { @@ -182,7 +196,7 @@ export async function baseGenerator( const packages = [ 'storybook', - `@storybook/${rendererId}`, + getExternalFramework(rendererId) ? undefined : `@storybook/${rendererId}`, ...frameworkPackages, ...addonPackages, ...extraPackages, diff --git a/code/lib/cli/src/initiate.ts b/code/lib/cli/src/initiate.ts index a9741facc52f..99c6daffe02d 100644 --- a/code/lib/cli/src/initiate.ts +++ b/code/lib/cli/src/initiate.ts @@ -26,6 +26,7 @@ import webComponentsGenerator from './generators/WEB-COMPONENTS'; import riotGenerator from './generators/RIOT'; import preactGenerator from './generators/PREACT'; import svelteGenerator from './generators/SVELTE'; +import qwikGenerator from './generators/QWIK'; import svelteKitGenerator from './generators/SVELTEKIT'; import raxGenerator from './generators/RAX'; import serverGenerator from './generators/SERVER'; @@ -104,6 +105,12 @@ const installStorybook = ( .then(commandLog('Adding Storybook support to your "React Native" app\n')); } + case ProjectType.QWIK: { + return qwikGenerator(packageManager, npmOptions, generatorOptions).then( + commandLog('Adding Storybook support to your "Qwik" app\n') + ); + } + case ProjectType.WEBPACK_REACT: return webpackReactGenerator(packageManager, npmOptions, generatorOptions).then( commandLog('Adding Storybook support to your "Webpack React" app\n') diff --git a/code/lib/cli/src/project_types.ts b/code/lib/cli/src/project_types.ts index 5de0f9c3812d..8067a3625e13 100644 --- a/code/lib/cli/src/project_types.ts +++ b/code/lib/cli/src/project_types.ts @@ -15,8 +15,13 @@ function eqMajor(versionRange: string, major: number) { return validRange(versionRange) && minVersion(versionRange).major === major; } +/** A list of all frameworks that are supported, but use a package outside the storybook monorepo */ +export const externalFrameworks: { name: SupportedFrameworks; packageName: string }[] = [ + { name: 'qwik', packageName: 'storybook-framework-qwik' }, +]; + // Should match @storybook/ -export type SupportedFrameworks = 'nextjs' | 'angular' | 'sveltekit'; +export type SupportedFrameworks = 'nextjs' | 'angular' | 'sveltekit' | 'qwik'; // Should match @storybook/ export type SupportedRenderers = @@ -32,6 +37,7 @@ export type SupportedRenderers = | 'marko' | 'preact' | 'svelte' + | 'qwik' | 'rax' | 'aurelia' | 'html' @@ -51,6 +57,7 @@ export const SUPPORTED_RENDERERS: SupportedRenderers[] = [ 'marko', 'preact', 'svelte', + 'qwik', 'rax', 'aurelia', ]; @@ -75,6 +82,7 @@ export enum ProjectType { MARIONETTE = 'MARIONETTE', MARKO = 'MARKO', HTML = 'HTML', + QWIK = 'QWIK', RIOT = 'RIOT', PREACT = 'PREACT', SVELTE = 'SVELTE', @@ -168,6 +176,13 @@ export const supportedTemplates: TemplateConfiguration[] = [ return dependencies.every(Boolean); }, }, + { + preset: ProjectType.QWIK, + dependencies: ['@builder.io/qwik'], + matcherFunction: ({ dependencies }) => { + return dependencies.every(Boolean); + }, + }, { preset: ProjectType.REACT_PROJECT, peerDependencies: ['react'], diff --git a/code/lib/cli/src/repro-templates.ts b/code/lib/cli/src/repro-templates.ts index 999086f368a5..8cfcef556efb 100644 --- a/code/lib/cli/src/repro-templates.ts +++ b/code/lib/cli/src/repro-templates.ts @@ -364,6 +364,16 @@ const baseTemplates = { builder: '@storybook/builder-vite', }, }, + 'qwik-vite/default-ts': { + name: 'Qwik CLI (Default TS)', + script: 'yarn create qwik basic {{beforeDir}} --no-install', + inDevelopment: true, + expected: { + framework: 'storybook-framework-qwik', + renderer: 'storybook-framework-qwik', + builder: 'storybook-framework-qwik', + }, + }, } satisfies Record; /** diff --git a/code/lib/core-common/src/utils/get-storybook-info.ts b/code/lib/core-common/src/utils/get-storybook-info.ts index 679ab828c03d..556d57049f00 100644 --- a/code/lib/core-common/src/utils/get-storybook-info.ts +++ b/code/lib/core-common/src/utils/get-storybook-info.ts @@ -17,6 +17,7 @@ const rendererPackages: Record = { '@storybook/riot': 'riot', '@storybook/svelte': 'svelte', '@storybook/preact': 'preact', + 'storybook-framework-qwik': 'qwik', '@storybook/rax': 'rax', '@storybook/server': 'server', }; diff --git a/docs/configure/frameworks.md b/docs/configure/frameworks.md index 9807642fffb4..079f4b3b0de4 100644 --- a/docs/configure/frameworks.md +++ b/docs/configure/frameworks.md @@ -15,7 +15,7 @@ Storybook provides support for the leading industry builders and frameworks. How | Builder | Framework | | ------- | ------------------------------------------------------------------------ | | Webpack | React, Angular, Vue, Web Components, NextJS, HTML, Ember, Preact, Svelte | -| Vite | React, Vue, Web Components, HTML, Svelte, SvelteKit | +| Vite | React, Vue, Web Components, HTML, Svelte, SvelteKit, Qwik | ## Configure diff --git a/docs/frameworks.js b/docs/frameworks.js index ce957e01d08b..f37ffd6cd8cc 100644 --- a/docs/frameworks.js +++ b/docs/frameworks.js @@ -1,6 +1,6 @@ module.exports = { coreFrameworks: ['react', 'vue', 'angular', 'web-components'], - communityFrameworks: ['ember', 'html', 'svelte', 'preact'], + communityFrameworks: ['ember', 'html', 'svelte', 'preact', 'qwik'], featureGroups: [ { name: 'Essentials', diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 4721d7fdfbb2..97656ea73faa 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -340,7 +340,9 @@ export const addStories: Task['run'] = async ( updateStoriesField(mainConfig, detectLanguage(packageJson) === SupportedLanguage.JAVASCRIPT); // Link in the template/components/index.js from store, the renderer and the addons - const rendererPath = await workspacePath('renderer', template.expected.renderer); + const rendererPath = template.expected.renderer.startsWith('@storybook/') + ? await workspacePath('renderer', template.expected.renderer) + : require.resolve(template.expected.renderer); await ensureSymlink( join(codeDir, rendererPath, 'template', 'components'), resolve(cwd, storiesPath, 'components')