mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-04 18:41:06 +08:00
Merge branch 'next' into vue3-reactivemode
This commit is contained in:
commit
e179c14b81
@ -109,6 +109,7 @@ For additional help, join us in the [Storybook Discord](https://discord.gg/story
|
||||
| [Svelte](code/renderers/svelte) | [](https://storybookjs.netlify.com/svelte-kitchen-sink/) | [](code/renderers/svelte) |
|
||||
| [Preact](code/renderers/preact) | [](https://storybookjs.netlify.com/preact-kitchen-sink/) | [](code/renderers/preact) |
|
||||
| [Marionette.js](https://github.com/storybookjs/marionette) | - | [](https://github.com/storybookjs/marionette) |
|
||||
| [Qwik](https://github.com/literalpie/storybook-framework-qwik) | - | [](https://github.com/literalpie/storybook-framework-qwik) |
|
||||
| [Android, iOS, Flutter](https://github.com/storybookjs/native) | [](https://storybookjs.github.io/native/@storybook/native-flutter-example/index.html) | [](https://github.com/storybookjs/native) |
|
||||
|
||||
### Sub Projects
|
||||
|
@ -63,6 +63,9 @@
|
||||
],
|
||||
"preview": [
|
||||
"dist/preview.d.ts"
|
||||
],
|
||||
"react": [
|
||||
"dist/react/index.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -89,6 +92,7 @@
|
||||
"ts-dedent": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"fs-extra": "^9.0.1",
|
||||
"typescript": "~4.9.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@ -112,7 +116,8 @@
|
||||
"./src/manager.ts",
|
||||
"./src/preview.ts",
|
||||
"./src/react/index.ts"
|
||||
]
|
||||
],
|
||||
"post": "./scripts/fix-preview-api-reference.ts"
|
||||
},
|
||||
"gitHead": "6d1ea7647fce605b2029077cbd02f655cafe1807",
|
||||
"storybook": {
|
||||
|
3
code/addons/links/react.d.ts
vendored
3
code/addons/links/react.d.ts
vendored
@ -1,2 +1 @@
|
||||
export * from './dist/react';
|
||||
export { default } from './dist/react';
|
||||
export * from './dist/react/index';
|
||||
|
2
code/addons/links/react.js
vendored
2
code/addons/links/react.js
vendored
@ -1,3 +1,3 @@
|
||||
import LinkTo from './dist/react';
|
||||
import LinkTo from './dist/react/index';
|
||||
|
||||
export default LinkTo;
|
||||
|
19
code/addons/links/scripts/fix-preview-api-reference.ts
Normal file
19
code/addons/links/scripts/fix-preview-api-reference.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { readFile, writeFile } from 'fs-extra';
|
||||
|
||||
/* I wish this wasn't needed..
|
||||
* There seems to be some bug in tsup / the unlaying lib that does DTS bundling
|
||||
* ...that makes it mess up the generation.
|
||||
*/
|
||||
const run = async () => {
|
||||
const content = await readFile('./dist/index.d.ts', 'utf-8');
|
||||
|
||||
const regexp = /'lib\/preview-api/;
|
||||
const replaced = content.replace(regexp, "'@storybook/preview-api");
|
||||
|
||||
await writeFile('./dist/index.d.ts', replaced);
|
||||
};
|
||||
|
||||
run().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
@ -4,6 +4,9 @@ import { dedent } from 'ts-dedent';
|
||||
|
||||
let hasWarned = false;
|
||||
|
||||
/**
|
||||
* @deprecated please import this specific function from @storybook/addon-links/react
|
||||
*/
|
||||
export function LinkTo(): null {
|
||||
if (!hasWarned) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -28,7 +28,11 @@ export const runCompodoc = (
|
||||
const packageManager = JsPackageManagerFactory.getPackageManager();
|
||||
|
||||
try {
|
||||
const stdout = packageManager.runScript('compodoc', finalCompodocArgs, context.workspaceRoot);
|
||||
const stdout = packageManager.runPackageCommand(
|
||||
'compodoc',
|
||||
finalCompodocArgs,
|
||||
context.workspaceRoot
|
||||
);
|
||||
|
||||
context.logger.info(stdout);
|
||||
observer.next();
|
||||
|
@ -66,8 +66,26 @@ For other details about the differences between vite and webpack projects, be su
|
||||
### Customize Vite config
|
||||
|
||||
The builder _will_ read your `vite.config.js` file, though it may change some of the options in order to work correctly.
|
||||
It looks for the Vite config in the CWD. If your config is located elsewhere, specify the path using the `viteConfigPath` builder option:
|
||||
|
||||
In `.storybook/main.js` (or whatever your Storybook config file is named), you can override the merged Vite config:
|
||||
```javascript
|
||||
// .storybook/main.mjs
|
||||
|
||||
const config = {
|
||||
framework: {
|
||||
name: '@storybook/react-vite', // Your framework name here.
|
||||
options: {
|
||||
builder: {
|
||||
viteConfigPath: '.storybook/customViteConfig.js',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
You can also override the merged Vite config:
|
||||
|
||||
```javascript
|
||||
// use `mergeConfig` to recursively merge Vite options
|
||||
|
@ -17,4 +17,9 @@ export type StorybookConfigVite = {
|
||||
viteFinal?: ViteFinal;
|
||||
};
|
||||
|
||||
export type BuilderOptions = {};
|
||||
export type BuilderOptions = {
|
||||
/**
|
||||
* Path to vite.config file, relative to CWD.
|
||||
*/
|
||||
viteConfigPath?: string;
|
||||
};
|
||||
|
@ -8,7 +8,7 @@ import type {
|
||||
InlineConfig,
|
||||
} from 'vite';
|
||||
import { viteExternalsPlugin } from 'vite-plugin-externals';
|
||||
import { isPreservingSymlinks, getFrameworkName } from '@storybook/core-common';
|
||||
import { isPreservingSymlinks, getFrameworkName, getBuilderOptions } from '@storybook/core-common';
|
||||
import { globals } from '@storybook/preview/globals';
|
||||
import type { Options } from '@storybook/types';
|
||||
import {
|
||||
@ -18,6 +18,7 @@ import {
|
||||
mdxPlugin,
|
||||
stripStoryHMRBoundary,
|
||||
} from './plugins';
|
||||
import type { BuilderOptions } from './types';
|
||||
|
||||
export type PluginConfigType = 'build' | 'development';
|
||||
|
||||
@ -39,12 +40,13 @@ export async function commonConfig(
|
||||
_type: PluginConfigType
|
||||
): Promise<ViteInlineConfig> {
|
||||
const configEnv = _type === 'development' ? configEnvServe : configEnvBuild;
|
||||
const { viteConfigPath } = await getBuilderOptions<BuilderOptions>(options);
|
||||
|
||||
// I destructure away the `build` property from the user's config object
|
||||
// I do this because I can contain config that breaks storybook, such as we had in a lit project.
|
||||
// If the user needs to configure the `build` they need to do so in the viteFinal function in main.js.
|
||||
const { config: { build: buildProperty = undefined, ...userConfig } = {} } =
|
||||
(await loadConfigFromFile(configEnv)) ?? {};
|
||||
(await loadConfigFromFile(configEnv, viteConfigPath)) ?? {};
|
||||
|
||||
const sbConfig: InlineConfig = {
|
||||
configFile: false,
|
||||
|
@ -38,7 +38,7 @@ export const mainjsFramework: Fix<MainjsFrameworkRunOptions> = {
|
||||
}
|
||||
|
||||
const main = await readConfig(mainConfig);
|
||||
const currentFramework = main.getFieldValue(['framework']);
|
||||
const currentFramework = main.getFieldNode(['framework']);
|
||||
const features = main.getFieldValue(['features']);
|
||||
|
||||
if (currentFramework) return null;
|
||||
|
@ -49,18 +49,33 @@ describe('missing-babelrc fix', () => {
|
||||
|
||||
// different babel extensions
|
||||
await expect(
|
||||
check({ extraFiles: { '.babelrc': babelContent }, packageJson })
|
||||
check({
|
||||
extraFiles: { '.babelrc': babelContent },
|
||||
packageJson,
|
||||
main: { framework: '@storybook/react' },
|
||||
})
|
||||
).resolves.toBeNull();
|
||||
await expect(
|
||||
check({ extraFiles: { '.babelrc.json': babelContent }, packageJson })
|
||||
check({
|
||||
extraFiles: { '.babelrc.json': babelContent },
|
||||
packageJson,
|
||||
main: { framework: '@storybook/react' },
|
||||
})
|
||||
).resolves.toBeNull();
|
||||
await expect(
|
||||
check({ extraFiles: { 'babel.config.json': babelContent }, packageJson })
|
||||
check({
|
||||
extraFiles: { 'babel.config.json': babelContent },
|
||||
packageJson,
|
||||
main: { framework: '@storybook/react' },
|
||||
})
|
||||
).resolves.toBeNull();
|
||||
|
||||
// babel field in package.json
|
||||
await expect(
|
||||
check({ packageJson: { ...packageJson, babel: babelContent } })
|
||||
check({
|
||||
packageJson: { ...packageJson, babel: babelContent },
|
||||
main: { framework: '@storybook/react' },
|
||||
})
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
|
||||
@ -72,7 +87,9 @@ describe('missing-babelrc fix', () => {
|
||||
},
|
||||
};
|
||||
|
||||
await expect(check({ packageJson })).resolves.toBeNull();
|
||||
await expect(
|
||||
check({ packageJson, main: { framework: '@storybook/nextjs' } })
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it('skips when using CRA preset', async () => {
|
||||
@ -84,7 +101,10 @@ describe('missing-babelrc fix', () => {
|
||||
};
|
||||
|
||||
await expect(
|
||||
check({ packageJson, main: { addons: ['@storybook/preset-create-react-app'] } })
|
||||
check({
|
||||
packageJson,
|
||||
main: { framework: '@storybook/react', addons: ['@storybook/preset-create-react-app'] },
|
||||
})
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
|
||||
|
@ -17,7 +17,6 @@ const frameworksThatNeedBabelConfig = [
|
||||
'@storybook/react-webpack5',
|
||||
'@storybook/vue-webpack5',
|
||||
'@storybook/vue3-webpack5',
|
||||
'@storybook/preact-webpack5',
|
||||
'@storybook/html-webpack5',
|
||||
];
|
||||
|
||||
@ -47,18 +46,18 @@ export const missingBabelRc: Fix<MissingBabelRcOptions> = {
|
||||
|
||||
const main = await readConfig(mainConfig);
|
||||
|
||||
const frameworkField = main.getFieldValue(['framework']);
|
||||
const frameworkPackage =
|
||||
typeof frameworkField === 'string' ? frameworkField : frameworkField?.name;
|
||||
const frameworkPackage = main.getNameFromPath(['framework']);
|
||||
|
||||
const addons: any[] = main.getFieldValue(['addons']) || [];
|
||||
const addons = main.getNamesFromPath(['addons']);
|
||||
|
||||
const hasCraPreset = addons.find((addon) => {
|
||||
const name = typeof addon === 'string' ? addon : addon.name;
|
||||
return name === '@storybook/preset-create-react-app';
|
||||
});
|
||||
const hasCraPreset =
|
||||
addons && addons.find((addon) => addon === '@storybook/preset-create-react-app');
|
||||
|
||||
if (frameworksThatNeedBabelConfig.includes(frameworkPackage) && !hasCraPreset) {
|
||||
if (
|
||||
frameworkPackage &&
|
||||
frameworksThatNeedBabelConfig.includes(frameworkPackage) &&
|
||||
!hasCraPreset
|
||||
) {
|
||||
const config = await loadPartialConfigAsync({
|
||||
babelrc: true,
|
||||
filename: '__fake__.js', // somehow needed to detect .babelrc.* files
|
||||
|
@ -127,7 +127,7 @@ export const newFrameworks: Fix<NewFrameworkRunOptions> = {
|
||||
// If in the future the eslint plugin has a framework option, using main to extract the framework field will be very useful
|
||||
const main = await readConfig(mainConfig);
|
||||
|
||||
const frameworkPackage = main.getFieldValue(['framework']) as keyof typeof packagesMap;
|
||||
const frameworkPackage = main.getNameFromPath(['framework']) as keyof typeof packagesMap;
|
||||
const builder = main.getFieldValue(['core', 'builder']);
|
||||
|
||||
if (!frameworkPackage) {
|
||||
|
@ -57,9 +57,9 @@ export const sveltekitFramework: Fix<SvelteKitFrameworkRunOptions> = {
|
||||
}
|
||||
|
||||
const main = await readConfig(mainConfig);
|
||||
const frameworkConfig = main.getFieldValue(['framework']);
|
||||
const framework = main.getNameFromPath(['framework']);
|
||||
|
||||
if (!frameworkConfig) {
|
||||
if (!framework) {
|
||||
logger.warn(dedent`
|
||||
❌ Unable to determine Storybook framework, skipping ${chalk.cyan(fixId)} fix.
|
||||
🤔 Are you running automigrate from your project directory?
|
||||
@ -67,8 +67,6 @@ export const sveltekitFramework: Fix<SvelteKitFrameworkRunOptions> = {
|
||||
return null;
|
||||
}
|
||||
|
||||
const framework = typeof frameworkConfig === 'string' ? frameworkConfig : frameworkConfig.name;
|
||||
|
||||
if (framework === '@storybook/sveltekit') {
|
||||
// already using the new framework
|
||||
return null;
|
||||
|
@ -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: {
|
||||
|
@ -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
code/lib/cli/src/generators/QWIK/index.ts
Normal file
8
code/lib/cli/src/generators/QWIK/index.ts
Normal file
@ -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;
|
@ -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';
|
||||
@ -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')))%%`;
|
||||
|
||||
@ -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}`;
|
||||
@ -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) {
|
||||
@ -194,7 +208,7 @@ export async function baseGenerator(
|
||||
|
||||
const packages = [
|
||||
'storybook',
|
||||
`@storybook/${rendererId}`,
|
||||
getExternalFramework(rendererId) ? undefined : `@storybook/${rendererId}`,
|
||||
...frameworkPackages,
|
||||
...addonPackages,
|
||||
...extraPackages,
|
||||
|
@ -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';
|
||||
@ -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')
|
||||
|
@ -376,7 +376,7 @@ export abstract class JsPackageManager {
|
||||
): // Use generic and conditional type to force `string[]` if fetchAllVersions is true and `string` if false
|
||||
Promise<T extends true ? string[] : string>;
|
||||
|
||||
public abstract runScript(script: string, args: string[], cwd?: string): string;
|
||||
public abstract runPackageCommand(command: string, args: string[], cwd?: string): string;
|
||||
|
||||
public executeCommand(
|
||||
command: string,
|
||||
|
@ -59,14 +59,14 @@ describe('NPM Proxy', () => {
|
||||
|
||||
describe('runScript', () => {
|
||||
describe('npm6', () => {
|
||||
it('should execute script `npm run compodoc -- -e json -d .`', () => {
|
||||
it('should execute script `npm exec -- compodoc -e json -d .`', () => {
|
||||
const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('6.0.0');
|
||||
|
||||
npmProxy.runScript('compodoc', ['-e', 'json', '-d', '.']);
|
||||
npmProxy.runPackageCommand('compodoc', ['-e', 'json', '-d', '.']);
|
||||
|
||||
expect(executeCommandSpy).toHaveBeenLastCalledWith(
|
||||
'npm',
|
||||
['run', 'compodoc', '--', '-e', 'json', '-d', '.'],
|
||||
['exec', '--', 'compodoc', '-e', 'json', '-d', '.'],
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
@ -76,11 +76,11 @@ describe('NPM Proxy', () => {
|
||||
it('should execute script `npm run compodoc -- -e json -d .`', () => {
|
||||
const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('7.1.0');
|
||||
|
||||
npmProxy.runScript('compodoc', ['-e', 'json', '-d', '.']);
|
||||
npmProxy.runPackageCommand('compodoc', ['-e', 'json', '-d', '.']);
|
||||
|
||||
expect(executeCommandSpy).toHaveBeenLastCalledWith(
|
||||
'npm',
|
||||
['run', 'compodoc', '--', '-e', 'json', '-d', '.'],
|
||||
['exec', '--', 'compodoc', '-e', 'json', '-d', '.'],
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
|
@ -38,8 +38,8 @@ export class NPMProxy extends JsPackageManager {
|
||||
return this.uninstallArgs;
|
||||
}
|
||||
|
||||
public runScript(command: string, args: string[], cwd?: string): string {
|
||||
return this.executeCommand(`npm`, ['run', command, '--', ...args], undefined, cwd);
|
||||
public runPackageCommand(command: string, args: string[], cwd?: string): string {
|
||||
return this.executeCommand(`npm`, ['exec', '--', command, ...args], undefined, cwd);
|
||||
}
|
||||
|
||||
protected getResolutions(packageJson: PackageJson, versions: Record<string, string>) {
|
||||
|
@ -50,7 +50,7 @@ describe('NPM Proxy', () => {
|
||||
it('should execute script `yarn compodoc -- -e json -d .`', () => {
|
||||
const executeCommandSpy = jest.spyOn(pnpmProxy, 'executeCommand').mockReturnValue('7.1.0');
|
||||
|
||||
pnpmProxy.runScript('compodoc', ['-e', 'json', '-d', '.']);
|
||||
pnpmProxy.runPackageCommand('compodoc', ['-e', 'json', '-d', '.']);
|
||||
|
||||
expect(executeCommandSpy).toHaveBeenLastCalledWith(
|
||||
'pnpm',
|
||||
|
@ -24,7 +24,7 @@ export class PNPMProxy extends JsPackageManager {
|
||||
return this.executeCommand('pnpm', ['--version']);
|
||||
}
|
||||
|
||||
runScript(command: string, args: string[], cwd?: string): string {
|
||||
runPackageCommand(command: string, args: string[], cwd?: string): string {
|
||||
return this.executeCommand(`pnpm`, ['run', command, ...args], undefined, cwd);
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ describe('Yarn 1 Proxy', () => {
|
||||
it('should execute script `yarn compodoc -- -e json -d .`', () => {
|
||||
const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockReturnValue('7.1.0');
|
||||
|
||||
yarn1Proxy.runScript('compodoc', ['-e', 'json', '-d', '.']);
|
||||
yarn1Proxy.runPackageCommand('compodoc', ['-e', 'json', '-d', '.']);
|
||||
|
||||
expect(executeCommandSpy).toHaveBeenLastCalledWith(
|
||||
'yarn',
|
||||
|
@ -16,7 +16,7 @@ export class Yarn1Proxy extends JsPackageManager {
|
||||
return `yarn ${command}`;
|
||||
}
|
||||
|
||||
runScript(command: string, args: string[], cwd?: string): string {
|
||||
runPackageCommand(command: string, args: string[], cwd?: string): string {
|
||||
return this.executeCommand(`yarn`, [command, ...args], undefined, cwd);
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ describe('Yarn 2 Proxy', () => {
|
||||
it('should execute script `yarn compodoc -- -e json -d .`', () => {
|
||||
const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockReturnValue('7.1.0');
|
||||
|
||||
yarn2Proxy.runScript('compodoc', ['-e', 'json', '-d', '.']);
|
||||
yarn2Proxy.runPackageCommand('compodoc', ['-e', 'json', '-d', '.']);
|
||||
|
||||
expect(executeCommandSpy).toHaveBeenLastCalledWith(
|
||||
'yarn',
|
||||
|
@ -17,7 +17,7 @@ export class Yarn2Proxy extends JsPackageManager {
|
||||
return `yarn ${command}`;
|
||||
}
|
||||
|
||||
runScript(command: string, args: string[], cwd?: string): string {
|
||||
runPackageCommand(command: string, args: string[], cwd?: string): string {
|
||||
return this.executeCommand(`yarn`, [command, ...args], undefined, cwd);
|
||||
}
|
||||
|
||||
|
@ -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 =
|
||||
@ -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',
|
||||
];
|
||||
@ -74,6 +81,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'],
|
||||
|
@ -354,6 +354,17 @@ 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,
|
||||
skipTasks: ['e2e-tests'],
|
||||
expected: {
|
||||
framework: 'storybook-framework-qwik',
|
||||
renderer: 'storybook-framework-qwik',
|
||||
builder: 'storybook-framework-qwik',
|
||||
},
|
||||
},
|
||||
} satisfies Record<string, Template>;
|
||||
|
||||
/**
|
||||
@ -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',
|
||||
];
|
||||
|
@ -6,6 +6,7 @@ export * from './utils/cache';
|
||||
export * from './utils/check-addon-order';
|
||||
export * from './utils/envs';
|
||||
export * from './utils/findDistEsm';
|
||||
export * from './utils/get-builder-options';
|
||||
export * from './utils/get-framework-name';
|
||||
export * from './utils/get-renderer-name';
|
||||
export * from './utils/get-storybook-configuration';
|
||||
|
23
code/lib/core-common/src/utils/get-builder-options.ts
Normal file
23
code/lib/core-common/src/utils/get-builder-options.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import type { Options } from '@storybook/types';
|
||||
|
||||
/**
|
||||
* Builder options can be specified in `core.builder.options` or `framework.options.builder`.
|
||||
* Preference is given here to `framework.options.builder` if both are specified.
|
||||
*/
|
||||
export async function getBuilderOptions<T extends Record<string, any>>(
|
||||
options: Options
|
||||
): Promise<T | Record<string, never>> {
|
||||
const framework = await options.presets.apply('framework', {}, options);
|
||||
|
||||
if (typeof framework !== 'string' && framework?.options?.builder) {
|
||||
return framework.options.builder;
|
||||
}
|
||||
|
||||
const { builder } = await options.presets.apply('core', {}, options);
|
||||
|
||||
if (typeof builder !== 'string' && builder?.options) {
|
||||
return builder.options as T;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
@ -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',
|
||||
};
|
||||
|
@ -747,4 +747,89 @@ describe('ConfigFile', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('config helpers', () => {
|
||||
describe('getNameFromPath', () => {
|
||||
it(`supports string literal node`, () => {
|
||||
const source = dedent`
|
||||
import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
framework: 'foo',
|
||||
}
|
||||
export default config;
|
||||
`;
|
||||
const config = loadConfig(source).parse();
|
||||
expect(config.getNameFromPath(['framework'])).toEqual('foo');
|
||||
});
|
||||
|
||||
it(`supports object expression node with name property`, () => {
|
||||
const source = dedent`
|
||||
import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
framework: { name: 'foo', options: { bar: require('baz') } },
|
||||
}
|
||||
export default config;
|
||||
`;
|
||||
const config = loadConfig(source).parse();
|
||||
expect(config.getNameFromPath(['framework'])).toEqual('foo');
|
||||
});
|
||||
|
||||
it(`returns undefined when accessing a field that does not exist`, () => {
|
||||
const source = dedent`
|
||||
import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||
|
||||
const config: StorybookConfig = { }
|
||||
export default config;
|
||||
`;
|
||||
const config = loadConfig(source).parse();
|
||||
expect(config.getNameFromPath(['framework'])).toBeUndefined();
|
||||
});
|
||||
|
||||
it(`throws an error when node is of unexpected type`, () => {
|
||||
const source = dedent`
|
||||
import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
framework: makesNoSense(),
|
||||
}
|
||||
export default config;
|
||||
`;
|
||||
const config = loadConfig(source).parse();
|
||||
expect(() => config.getNameFromPath(['framework'])).toThrowError(
|
||||
`The given node must be a string literal or an object expression with a "name" property that is a string literal.`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNamesFromPath', () => {
|
||||
it(`supports an array with string literal and object expression with name property`, () => {
|
||||
const source = dedent`
|
||||
import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
addons: [
|
||||
'foo',
|
||||
{ name: 'bar', options: {} },
|
||||
]
|
||||
}
|
||||
export default config;
|
||||
`;
|
||||
const config = loadConfig(source).parse();
|
||||
expect(config.getNamesFromPath(['addons'])).toEqual(['foo', 'bar']);
|
||||
});
|
||||
});
|
||||
|
||||
it(`returns undefined when accessing a field that does not exist`, () => {
|
||||
const source = dedent`
|
||||
import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||
|
||||
const config: StorybookConfig = { }
|
||||
export default config;
|
||||
`;
|
||||
const config = loadConfig(source).parse();
|
||||
expect(config.getNamesFromPath(['addons'])).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -254,6 +254,88 @@ export class ConfigFile {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of a node in a given path, supporting the following formats:
|
||||
* 1. { framework: 'value' }
|
||||
* 2. { framework: { name: 'value', options: {} } }
|
||||
*/
|
||||
/**
|
||||
* Returns the name of a node in a given path, supporting the following formats:
|
||||
* @example
|
||||
* // 1. { framework: 'framework-name' }
|
||||
* // 2. { framework: { name: 'framework-name', options: {} }
|
||||
* getNameFromPath(['framework']) // => 'framework-name'
|
||||
*/
|
||||
getNameFromPath(path: string[]): string | undefined {
|
||||
const node = this.getFieldNode(path);
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this._getPresetValue(node, 'name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of names of a node in a given path, supporting the following formats:
|
||||
* @example
|
||||
* const config = {
|
||||
* addons: [
|
||||
* 'first-addon',
|
||||
* { name: 'second-addon', options: {} }
|
||||
* ]
|
||||
* }
|
||||
* // => ['first-addon', 'second-addon']
|
||||
* getNamesFromPath(['addons'])
|
||||
*
|
||||
*/
|
||||
getNamesFromPath(path: string[]): string[] | undefined {
|
||||
const node = this.getFieldNode(path);
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const pathNames: string[] = [];
|
||||
if (t.isArrayExpression(node)) {
|
||||
node.elements.forEach((element) => {
|
||||
pathNames.push(this._getPresetValue(element, 'name'));
|
||||
});
|
||||
}
|
||||
|
||||
return pathNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a node and a fallback property, returns a **non-evaluated** string value of the node.
|
||||
* 1. { node: 'value' }
|
||||
* 2. { node: { fallbackProperty: 'value' } }
|
||||
*/
|
||||
_getPresetValue(node: t.Node, fallbackProperty: string) {
|
||||
let value;
|
||||
if (t.isStringLiteral(node)) {
|
||||
value = node.value;
|
||||
} else if (t.isObjectExpression(node)) {
|
||||
node.properties.forEach((prop) => {
|
||||
if (
|
||||
t.isObjectProperty(prop) &&
|
||||
t.isIdentifier(prop.key) &&
|
||||
prop.key.name === fallbackProperty
|
||||
) {
|
||||
if (t.isStringLiteral(prop.value)) {
|
||||
value = prop.value.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
throw new Error(
|
||||
`The given node must be a string literal or an object expression with a "${fallbackProperty}" property that is a string literal.`
|
||||
);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
removeField(path: string[]) {
|
||||
const removeProperty = (properties: t.ObjectProperty[], prop: string) => {
|
||||
const index = properties.findIndex(
|
||||
|
@ -1,20 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`inject-decorator positive - ts - csf includes storySource parameter in the default exported variable 1`] = `
|
||||
"import React from \\"react\\";
|
||||
import { action } from \\"@storybook/addon-actions\\";
|
||||
import { Button } from \\"@storybook/react/demo\\";
|
||||
|
||||
const meta = {parameters: {\\"storySource\\":{\\"source\\":\\"import React from \\\\\\"react\\\\\\";\\\\nimport { action } from \\\\\\"@storybook/addon-actions\\\\\\";\\\\nimport { Button } from \\\\\\"@storybook/react/demo\\\\\\";\\\\n\\\\nconst meta = {\\\\n title: \\\\\\"Button\\\\\\",\\\\n excludeStories: [\\\\\\"text\\\\\\"],\\\\n includeStories: /emoji.*/\\\\n};\\\\n\\\\nexport default meta;\\\\n\\\\nexport const text = () => (\\\\n <Button onClick={action(\\\\\\"clicked\\\\\\")}>Hello Button</Button>\\\\n);\\\\n\\",\\"locationsMap\\":{\\"text\\":{\\"startLoc\\":{\\"col\\":20,\\"line\\":13},\\"endLoc\\":{\\"col\\":1,\\"line\\":15},\\"startBody\\":{\\"col\\":20,\\"line\\":13},\\"endBody\\":{\\"col\\":1,\\"line\\":15}}}},},
|
||||
title: \\"Button\\",
|
||||
excludeStories: [\\"text\\"],
|
||||
includeStories: /emoji.*/
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const text = () => (
|
||||
<Button onClick={action(\\"clicked\\")}>Hello Button</Button>
|
||||
);
|
||||
"
|
||||
`;
|
@ -1,14 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`inject-decorator injectStoryParameters - ts - csf includes storySource parameter in the default exported object 1`] = `
|
||||
"
|
||||
|
||||
|
||||
Basic.parameters = { storySource: { source: \\"() => (\\\\n <Button onClick={action(\\\\\\"clicked\\\\\\")}>Hello Button</Button>\\\\n)\\" }, ...Basic.parameters };
|
||||
WithParams.parameters = { storySource: { source: \\"() => <Button>WithParams</Button>\\" }, ...WithParams.parameters };
|
||||
WithDocsParams.parameters = { storySource: { source: \\"() => <Button>WithDocsParams</Button>\\" }, ...WithDocsParams.parameters };
|
||||
WithStorySourceParams.parameters = { storySource: { source: \\"() => <Button>WithStorySourceParams</Button>\\" }, ...WithStorySourceParams.parameters };
|
||||
WithTemplate.parameters = { storySource: { source: \\"(args: Args) => <Button {...args} />\\" }, ...WithTemplate.parameters };
|
||||
WithEmptyTemplate.parameters = { storySource: { source: \\"(args: Args) => <Button {...args} />\\" }, ...WithEmptyTemplate.parameters };
|
||||
WithAddFunctionParameters.parameters = { storySource: { source: \\"() => null\\" }, ...WithAddFunctionParameters.parameters };"
|
||||
`;
|
@ -1,14 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`inject-decorator injectStoryParameters - ts - csf includes storySource parameter in the default exported object 1`] = `
|
||||
"
|
||||
|
||||
|
||||
Basic.parameters = { storySource: { source: \\"() => (\\\\r\\\\n <Button onClick={action(\\\\\\"clicked\\\\\\")}>Hello Button</Button>\\\\r\\\\n)\\" }, ...Basic.parameters };
|
||||
WithParams.parameters = { storySource: { source: \\"() => <Button>WithParams</Button>\\" }, ...WithParams.parameters };
|
||||
WithDocsParams.parameters = { storySource: { source: \\"() => <Button>WithDocsParams</Button>\\" }, ...WithDocsParams.parameters };
|
||||
WithStorySourceParams.parameters = { storySource: { source: \\"() => <Button>WithStorySourceParams</Button>\\" }, ...WithStorySourceParams.parameters };
|
||||
WithTemplate.parameters = { storySource: { source: \\"(args: Args) => <Button {...args} />\\" }, ...WithTemplate.parameters };
|
||||
WithEmptyTemplate.parameters = { storySource: { source: \\"(args: Args) => <Button {...args} />\\" }, ...WithEmptyTemplate.parameters };
|
||||
WithAddFunctionParameters.parameters = { storySource: { source: \\"() => null\\" }, ...WithAddFunctionParameters.parameters };"
|
||||
`;
|
@ -1,36 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`inject-decorator positive - ts - csf includes storySource parameter in the default exported object 1`] = `
|
||||
"import React from \\"react\\";
|
||||
import { action } from \\"@storybook/addon-actions\\";
|
||||
import { Button } from \\"@storybook/react/demo\\";
|
||||
|
||||
export default {parameters: {\\"storySource\\":{\\"source\\":\\"import React from \\\\\\"react\\\\\\";\\\\nimport { action } from \\\\\\"@storybook/addon-actions\\\\\\";\\\\nimport { Button } from \\\\\\"@storybook/react/demo\\\\\\";\\\\n\\\\nexport default {\\\\n title: \\\\\\"Button\\\\\\",\\\\n excludeStories: [\\\\\\"text\\\\\\"],\\\\n includeStories: /emoji.*/\\\\n};\\\\n\\\\nexport const text = () => (\\\\n <Button onClick={action(\\\\\\"clicked\\\\\\")}>Hello Button</Button>\\\\n);\\\\n\\\\nexport const emoji = () => (\\\\n <Button onClick={action(\\\\\\"clicked\\\\\\")}>\\\\n <span role=\\\\\\"img\\\\\\" aria-label=\\\\\\"so cool\\\\\\">\\\\n 😀 😎 👍 💯\\\\n </span>\\\\n </Button>\\\\n);\\\\n\\\\nexport function emojiFn() {\\\\n return (\\\\n <Button onClick={action(\\\\\\"clicked\\\\\\")}>\\\\n <span role=\\\\\\"img\\\\\\" aria-label=\\\\\\"so cool\\\\\\">\\\\n 😀 😎 👍 💯\\\\n </span>\\\\n </Button>\\\\n )\\\\n};\\\\n\\",\\"locationsMap\\":{\\"text\\":{\\"startLoc\\":{\\"col\\":20,\\"line\\":11},\\"endLoc\\":{\\"col\\":1,\\"line\\":13},\\"startBody\\":{\\"col\\":20,\\"line\\":11},\\"endBody\\":{\\"col\\":1,\\"line\\":13}},\\"emoji\\":{\\"startLoc\\":{\\"col\\":21,\\"line\\":15},\\"endLoc\\":{\\"col\\":1,\\"line\\":21},\\"startBody\\":{\\"col\\":21,\\"line\\":15},\\"endBody\\":{\\"col\\":1,\\"line\\":21}},\\"emoji-fn\\":{\\"startLoc\\":{\\"col\\":7,\\"line\\":23},\\"endLoc\\":{\\"col\\":1,\\"line\\":31},\\"startBody\\":{\\"col\\":7,\\"line\\":23},\\"endBody\\":{\\"col\\":1,\\"line\\":31}}}},},
|
||||
title: \\"Button\\",
|
||||
excludeStories: [\\"text\\"],
|
||||
includeStories: /emoji.*/
|
||||
};
|
||||
|
||||
export const text = () => (
|
||||
<Button onClick={action(\\"clicked\\")}>Hello Button</Button>
|
||||
);
|
||||
|
||||
export const emoji = () => (
|
||||
<Button onClick={action(\\"clicked\\")}>
|
||||
<span role=\\"img\\" aria-label=\\"so cool\\">
|
||||
😀 😎 👍 💯
|
||||
</span>
|
||||
</Button>
|
||||
);;
|
||||
|
||||
export const emojiFn = function emojiFn() {
|
||||
return (
|
||||
<Button onClick={action(\\"clicked\\")}>
|
||||
<span role=\\"img\\" aria-label=\\"so cool\\">
|
||||
😀 😎 👍 💯
|
||||
</span>
|
||||
</Button>
|
||||
)
|
||||
};
|
||||
"
|
||||
`;
|
@ -1,36 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`inject-decorator positive - ts - csf includes storySource parameter in the default exported object 1`] = `
|
||||
"import React from \\"react\\";
|
||||
import { action } from \\"@storybook/addon-actions\\";
|
||||
import { Button } from \\"@storybook/react/demo\\";
|
||||
|
||||
export default {parameters: {\\"storySource\\":{\\"source\\":\\"import React from \\\\\\"react\\\\\\";\\\\r\\\\nimport { action } from \\\\\\"@storybook/addon-actions\\\\\\";\\\\r\\\\nimport { Button } from \\\\\\"@storybook/react/demo\\\\\\";\\\\r\\\\n\\\\r\\\\nexport default {\\\\r\\\\n title: \\\\\\"Button\\\\\\",\\\\r\\\\n excludeStories: [\\\\\\"text\\\\\\"],\\\\r\\\\n includeStories: /emoji.*/\\\\r\\\\n};\\\\r\\\\n\\\\r\\\\nexport const text = () => (\\\\r\\\\n <Button onClick={action(\\\\\\"clicked\\\\\\")}>Hello Button</Button>\\\\r\\\\n);\\\\r\\\\n\\\\r\\\\nexport const emoji = () => (\\\\r\\\\n <Button onClick={action(\\\\\\"clicked\\\\\\")}>\\\\r\\\\n <span role=\\\\\\"img\\\\\\" aria-label=\\\\\\"so cool\\\\\\">\\\\r\\\\n 😀 😎 👍 💯\\\\r\\\\n </span>\\\\r\\\\n </Button>\\\\r\\\\n);\\\\r\\\\n\\\\r\\\\nexport function emojiFn() {\\\\r\\\\n return (\\\\r\\\\n <Button onClick={action(\\\\\\"clicked\\\\\\")}>\\\\r\\\\n <span role=\\\\\\"img\\\\\\" aria-label=\\\\\\"so cool\\\\\\">\\\\r\\\\n 😀 😎 👍 💯\\\\r\\\\n </span>\\\\r\\\\n </Button>\\\\r\\\\n )\\\\r\\\\n};\\\\r\\\\n\\",\\"locationsMap\\":{\\"text\\":{\\"startLoc\\":{\\"col\\":20,\\"line\\":11},\\"endLoc\\":{\\"col\\":1,\\"line\\":13},\\"startBody\\":{\\"col\\":20,\\"line\\":11},\\"endBody\\":{\\"col\\":1,\\"line\\":13}},\\"emoji\\":{\\"startLoc\\":{\\"col\\":21,\\"line\\":15},\\"endLoc\\":{\\"col\\":1,\\"line\\":21},\\"startBody\\":{\\"col\\":21,\\"line\\":15},\\"endBody\\":{\\"col\\":1,\\"line\\":21}},\\"emoji-fn\\":{\\"startLoc\\":{\\"col\\":7,\\"line\\":23},\\"endLoc\\":{\\"col\\":1,\\"line\\":31},\\"startBody\\":{\\"col\\":7,\\"line\\":23},\\"endBody\\":{\\"col\\":1,\\"line\\":31}}}},},
|
||||
title: \\"Button\\",
|
||||
excludeStories: [\\"text\\"],
|
||||
includeStories: /emoji.*/
|
||||
};
|
||||
|
||||
export const text = () => (
|
||||
<Button onClick={action(\\"clicked\\")}>Hello Button</Button>
|
||||
);
|
||||
|
||||
export const emoji = () => (
|
||||
<Button onClick={action(\\"clicked\\")}>
|
||||
<span role=\\"img\\" aria-label=\\"so cool\\">
|
||||
😀 😎 👍 💯
|
||||
</span>
|
||||
</Button>
|
||||
);;
|
||||
|
||||
export const emojiFn = function emojiFn() {
|
||||
return (
|
||||
<Button onClick={action(\\"clicked\\")}>
|
||||
<span role=\\"img\\" aria-label=\\"so cool\\">
|
||||
😀 😎 👍 💯
|
||||
</span>
|
||||
</Button>
|
||||
)
|
||||
};
|
||||
"
|
||||
`;
|
@ -12,7 +12,8 @@ import {
|
||||
import { extractSource } from '../extract-source';
|
||||
|
||||
export function sanitizeSource(source) {
|
||||
return JSON.stringify(source)
|
||||
return JSON.stringify(source, null, 2)
|
||||
.trim()
|
||||
.replace(/\u2028/g, '\\u2028')
|
||||
.replace(/\u2029/g, '\\u2029');
|
||||
}
|
||||
@ -155,9 +156,15 @@ export function generateSourcesInExportedParameters(source, ast, additionalParam
|
||||
const { splicedSource, parametersSliceOfCode, indexWhereToAppend, foundParametersProperty } =
|
||||
popParametersObjectFromDefaultExport(source, ast);
|
||||
if (indexWhereToAppend !== -1) {
|
||||
const additionalParametersAsJson = JSON.stringify({
|
||||
storySource: transformLocationMapToIds(additionalParameters),
|
||||
}).slice(0, -1);
|
||||
const additionalParametersAsJson = JSON.stringify(
|
||||
{
|
||||
storySource: transformLocationMapToIds(additionalParameters),
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
.trim()
|
||||
.slice(0, -1);
|
||||
const propertyDeclaration = foundParametersProperty ? '' : 'parameters: ';
|
||||
const comma = foundParametersProperty ? '' : ',';
|
||||
const newParameters = `${propertyDeclaration}${additionalParametersAsJson},${parametersSliceOfCode.substring(
|
||||
|
@ -1,63 +1,52 @@
|
||||
import fs from 'fs';
|
||||
import { readFile } from 'fs/promises';
|
||||
import path from 'path';
|
||||
import 'jest-specific-snapshot';
|
||||
import injectDecorator from './inject-decorator';
|
||||
import getParser from './parsers';
|
||||
|
||||
const { SNAPSHOT_OS } = global;
|
||||
const regex = /\\r\\n|\r\n|\r|\n/g;
|
||||
|
||||
describe('inject-decorator', () => {
|
||||
const snapshotDir = path.join(__dirname, '__snapshots__');
|
||||
|
||||
describe('positive - ts - csf', () => {
|
||||
it('includes storySource parameter in the default exported object', () => {
|
||||
it('includes storySource parameter in the default exported object', async () => {
|
||||
const mockFilePath = './__mocks__/inject-decorator.ts.csf.txt';
|
||||
const source = fs.readFileSync(mockFilePath, 'utf-8');
|
||||
const source = await readFile(mockFilePath, 'utf-8');
|
||||
const result = injectDecorator(source, path.resolve(__dirname, mockFilePath), {
|
||||
parser: 'typescript',
|
||||
});
|
||||
|
||||
expect(result.source).toMatchSpecificSnapshot(
|
||||
path.join(snapshotDir, `inject-decorator.csf.test.js.${SNAPSHOT_OS}.snapshot`)
|
||||
);
|
||||
expect(result.source).toEqual(
|
||||
expect.stringContaining(
|
||||
'export default {parameters: {"storySource":{"source":"import React from'
|
||||
)
|
||||
);
|
||||
expect(getParser('typescript').parse(result.source)).toBeTruthy();
|
||||
|
||||
expect(result.source).toEqual(expect.stringContaining('"source": "import React from'));
|
||||
});
|
||||
|
||||
it('includes storySource parameter in the default exported variable', () => {
|
||||
it('includes storySource parameter in the default exported variable', async () => {
|
||||
const mockFilePath = './__mocks__/inject-decorator.ts.csf-meta-var.txt';
|
||||
const source = fs.readFileSync(mockFilePath, 'utf-8');
|
||||
const source = await readFile(mockFilePath, 'utf-8');
|
||||
const result = injectDecorator(source, path.resolve(__dirname, mockFilePath), {
|
||||
parser: 'typescript',
|
||||
});
|
||||
|
||||
expect(result.source).toMatchSpecificSnapshot(
|
||||
path.join(snapshotDir, `inject-decorator.csf-meta-var.test.js.${SNAPSHOT_OS}.snapshot`)
|
||||
);
|
||||
expect(result.source).toEqual(
|
||||
expect.stringContaining(
|
||||
'const meta = {parameters: {"storySource":{"source":"import React from'
|
||||
)
|
||||
);
|
||||
expect(getParser('typescript').parse(result.source)).toBeTruthy();
|
||||
|
||||
expect(result.source).toEqual(expect.stringContaining('"source": "import React from'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('injectStoryParameters - ts - csf', () => {
|
||||
it('includes storySource parameter in the default exported object', () => {
|
||||
it('includes storySource parameter in the default exported object', async () => {
|
||||
const mockFilePath = './__mocks__/inject-parameters.ts.csf.txt';
|
||||
const source = fs.readFileSync(mockFilePath, 'utf-8');
|
||||
const source = await readFile(mockFilePath, 'utf-8');
|
||||
const result = injectDecorator(source, path.resolve(__dirname, mockFilePath), {
|
||||
injectStoryParameters: true,
|
||||
parser: 'typescript',
|
||||
});
|
||||
expect(result.source).toMatchSpecificSnapshot(
|
||||
path.join(
|
||||
snapshotDir,
|
||||
`inject-decorator.csf.test.js.injectStoryParameters-${SNAPSHOT_OS}.snapshot`
|
||||
)
|
||||
);
|
||||
|
||||
expect(result.source).toContain('Basic.parameters = { storySource: { source:');
|
||||
expect(result.source).toContain('WithParams.parameters = { storySource: { source:');
|
||||
expect(result.source).toContain('WithDocsParams.parameters = { storySource: { source:');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -19,7 +19,7 @@ export async function transform(inputSource) {
|
||||
// @ts-expect-error (Converted from ts-ignore)
|
||||
var __STORY__ = ${sourceJson};
|
||||
// @ts-expect-error (Converted from ts-ignore)
|
||||
var __LOCATIONS_MAP__ = ${JSON.stringify(addsMap)};
|
||||
var __LOCATIONS_MAP__ = ${JSON.stringify(addsMap, null, 2).trim()};
|
||||
`;
|
||||
return `${preamble}\n${source}`;
|
||||
}
|
||||
|
@ -5367,6 +5367,7 @@ __metadata:
|
||||
"@storybook/preview-api": 7.0.0-beta.31
|
||||
"@storybook/router": 7.0.0-beta.31
|
||||
"@storybook/types": 7.0.0-beta.31
|
||||
fs-extra: ^9.0.1
|
||||
prop-types: ^15.7.2
|
||||
ts-dedent: ^2.0.0
|
||||
typescript: ~4.9.3
|
||||
|
@ -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
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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'
|
||||
@ -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')}`]);
|
||||
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 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);
|
||||
|
||||
// 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;
|
||||
@ -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 });
|
||||
|
Loading…
x
Reference in New Issue
Block a user