Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI: Add init support for qwik projects #20411

Merged
merged 6 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions code/lib/cli/src/detect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
7 changes: 6 additions & 1 deletion code/lib/cli/src/dirs.ts
Original file line number Diff line number Diff line change
@@ -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()],
})
);
}
8 changes: 8 additions & 0 deletions code/lib/cli/src/generators/QWIK/index.ts
Original file line number Diff line number Diff line change
@@ -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;
26 changes: 20 additions & 6 deletions code/lib/cli/src/generators/baseGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -44,6 +44,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')))%%`;

Expand All @@ -60,9 +73,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}`;
Expand All @@ -71,7 +83,9 @@ const getFrameworkDetails = (
const builderPackage = getBuilderDetails(builder);
const builderPackagePath = pnp ? wrapForPnp(builderPackage) : builderPackage;

const isKnownFramework = !!(packageVersions as Record<string, string>)[frameworkPackage];
const isExternalFramework = !!getExternalFramework(frameworkPackage);
const isKnownFramework =
isExternalFramework || !!(packageVersions as Record<string, string>)[frameworkPackage];
const isKnownRenderer = !!(packageVersions as Record<string, string>)[rendererPackage];

if (isKnownFramework) {
Expand Down Expand Up @@ -194,7 +208,7 @@ export async function baseGenerator(

const packages = [
'storybook',
`@storybook/${rendererId}`,
getExternalFramework(rendererId) ? undefined : `@storybook/${rendererId}`,
...frameworkPackages,
...addonPackages,
...extraPackages,
Expand Down
7 changes: 7 additions & 0 deletions code/lib/cli/src/initiate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -94,6 +95,12 @@ const installStorybook = <Project extends ProjectType>(
.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')
Expand Down
17 changes: 16 additions & 1 deletion code/lib/cli/src/project_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/<framework>
export type SupportedFrameworks = 'nextjs' | 'angular' | 'sveltekit';
export type SupportedFrameworks = 'nextjs' | 'angular' | 'sveltekit' | 'qwik';

// Should match @storybook/<renderer>
export type SupportedRenderers =
Expand All @@ -32,6 +37,7 @@ export type SupportedRenderers =
| 'marko'
| 'preact'
| 'svelte'
| 'qwik'
| 'rax'
| 'aurelia'
| 'html'
Expand All @@ -51,6 +57,7 @@ export const SUPPORTED_RENDERERS: SupportedRenderers[] = [
'marko',
'preact',
'svelte',
'qwik',
'rax',
'aurelia',
];
Expand All @@ -74,6 +81,7 @@ export enum ProjectType {
MARIONETTE = 'MARIONETTE',
MARKO = 'MARKO',
HTML = 'HTML',
QWIK = 'QWIK',
RIOT = 'RIOT',
PREACT = 'PREACT',
SVELTE = 'SVELTE',
Expand Down Expand Up @@ -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'],
Expand Down
12 changes: 12 additions & 0 deletions code/lib/cli/src/sandbox-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,17 @@ const baseTemplates = {
builder: '@storybook/builder-vite',
},
},
'qwik-vite/default-ts': {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to add this to export const daily below? It's not currently getting run in CI.

name: 'Qwik CLI (Default TS)',
script: 'yarn create qwik basic {{beforeDir}} --no-install',
inDevelopment: true,
skipTasks: ['e2e-tests'],
expected: {
framework: 'storybook-framework-qwik',
renderer: 'storybook-framework-qwik',
builder: 'storybook-framework-qwik',
},
},
} satisfies Record<string, Template>;

/**
Expand Down Expand Up @@ -429,6 +440,7 @@ export const daily: TemplateKey[] = [
'svelte-vite/default-js',
'nextjs/12-js',
'nextjs/default-js',
'qwik-vite/default-ts',
'preact-webpack5/default-js',
'preact-vite/default-js',
];
Expand Down
1 change: 1 addition & 0 deletions code/lib/core-common/src/utils/get-storybook-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const rendererPackages: Record<string, string> = {
'@storybook/riot': 'riot',
'@storybook/svelte': 'svelte',
'@storybook/preact': 'preact',
'storybook-framework-qwik': 'qwik',
'@storybook/rax': 'rax',
'@storybook/server': 'server',
};
Expand Down
2 changes: 1 addition & 1 deletion docs/configure/frameworks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/frameworks.js
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
98 changes: 56 additions & 42 deletions scripts/tasks/sandbox-parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,9 @@ async function linkPackageStories(

await ensureSymlink(source, target);

if (!linkInDir) addStoriesEntry(mainConfig, packageDir);
if (!linkInDir) {
addStoriesEntry(mainConfig, packageDir);
}

// Add `previewAnnotation` entries of the form
// './template-stories/lib/store/preview.[tj]s'
Expand Down Expand Up @@ -354,53 +356,62 @@ export const addStories: Task['run'] = async (
const packageJson = await import(join(cwd, 'package.json'));
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);
await ensureSymlink(
join(codeDir, rendererPath, 'template', 'components'),
resolve(cwd, storiesPath, 'components')
);
addPreviewAnnotations(mainConfig, [`.${sep}${join(storiesPath, 'components')}`]);

// Add stories for the renderer. NOTE: these *do* need to be processed by the framework build system
await linkPackageStories(rendererPath, {
mainConfig,
cwd,
linkInDir: resolve(cwd, storiesPath),
});

const frameworkPath = await workspacePath('frameworks', template.expected.framework);
const isCoreRenderer = template.expected.renderer.startsWith('@storybook/');
if (isCoreRenderer) {
// Link in the template/components/index.js from store, the renderer and the addons
const rendererPath = await workspacePath('renderer', template.expected.renderer);
await ensureSymlink(
join(codeDir, rendererPath, 'template', 'components'),
resolve(cwd, storiesPath, 'components')
);
addPreviewAnnotations(mainConfig, [`.${sep}${join(storiesPath, 'components')}`]);

// Add stories for the framework if it has one. NOTE: these *do* need to be processed by the framework build system
if (await pathExists(resolve(codeDir, frameworkPath, join('template', 'stories')))) {
await linkPackageStories(frameworkPath, {
// Add stories for the renderer. NOTE: these *do* need to be processed by the framework build system
await linkPackageStories(rendererPath, {
mainConfig,
cwd,
linkInDir: resolve(cwd, storiesPath),
});
}

const frameworkVariant = key.split('/')[1];
const storiesVariantFolder = addVariantToFolder(frameworkVariant);
const isCoreFramework = template.expected.framework.startsWith('@storybook/');

if (await pathExists(resolve(codeDir, frameworkPath, join('template', storiesVariantFolder)))) {
await linkPackageStories(
frameworkPath,
{
if (isCoreFramework) {
const frameworkPath = await workspacePath('frameworks', template.expected.framework);

// Add stories for the framework if it has one. NOTE: these *do* need to be processed by the framework build system
if (await pathExists(resolve(codeDir, frameworkPath, join('template', 'stories')))) {
await linkPackageStories(frameworkPath, {
mainConfig,
cwd,
linkInDir: resolve(cwd, storiesPath),
},
frameworkVariant
);
});
}

const frameworkVariant = key.split('/')[1];
const storiesVariantFolder = addVariantToFolder(frameworkVariant);

if (await pathExists(resolve(codeDir, frameworkPath, join('template', storiesVariantFolder)))) {
await linkPackageStories(
frameworkPath,
{
mainConfig,
cwd,
linkInDir: resolve(cwd, storiesPath),
},
frameworkVariant
);
}
}

// Add stories for lib/store (and addons below). NOTE: these stories will be in the
// template-stories folder and *not* processed by the framework build config (instead by esbuild-loader)
await linkPackageStories(await workspacePath('core package', '@storybook/store'), {
mainConfig,
cwd,
});
if (isCoreRenderer) {
// Add stories for lib/store (and addons below). NOTE: these stories will be in the
// template-stories folder and *not* processed by the framework build config (instead by esbuild-loader)
await linkPackageStories(await workspacePath('core package', '@storybook/store'), {
mainConfig,
cwd,
});
}

const mainAddons = mainConfig.getFieldValue(['addons']).reduce((acc: string[], addon: any) => {
const name = typeof addon === 'string' ? addon : addon.name;
Expand All @@ -419,14 +430,17 @@ export const addStories: Task['run'] = async (
)
);

const existingStories = await filterExistsInCodeDir(addonDirs, join('template', 'stories'));
await Promise.all(
existingStories.map(async (packageDir) => linkPackageStories(packageDir, { mainConfig, cwd }))
);
if (isCoreRenderer) {
const existingStories = await filterExistsInCodeDir(addonDirs, join('template', 'stories'));
await Promise.all(
existingStories.map(async (packageDir) => linkPackageStories(packageDir, { mainConfig, cwd }))
);

// Add some extra settings (see above for what these do)
if (template.expected.builder === '@storybook/builder-webpack5')
addEsbuildLoaderToStories(mainConfig);
// Add some extra settings (see above for what these do)
if (template.expected.builder === '@storybook/builder-webpack5') {
addEsbuildLoaderToStories(mainConfig);
}
}

// Some addon stories require extra dependencies
addExtraDependencies({ cwd, dryRun, debug });
Expand Down