mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-02 05:03:44 +08:00
Addon-docs: Add opt-in MDX2 support
This commit is contained in:
parent
99f9de1144
commit
b749473a98
19
MIGRATION.md
19
MIGRATION.md
@ -1,6 +1,7 @@
|
||||
<h1>Migration</h1>
|
||||
|
||||
- [From version 6.4.x to 6.5.0](#from-version-64x-to-650)
|
||||
- [Opt-in MDX2 support](#opt-in-mdx2-support)
|
||||
- [CSF3 auto-title redundant filename](#csf3-auto-title-redundant-filename)
|
||||
- [From version 6.3.x to 6.4.0](#from-version-63x-to-640)
|
||||
- [Automigrate](#automigrate)
|
||||
@ -192,6 +193,24 @@
|
||||
|
||||
## From version 6.4.x to 6.5.0
|
||||
|
||||
### Opt-in MDX2 support
|
||||
|
||||
SB6.5 adds experimental opt-in support for MDXv2. To install:
|
||||
|
||||
```sh
|
||||
yarn add @storybook/mdx2-csf -D
|
||||
```
|
||||
|
||||
Then add the `previewMdx2` feature flag to your `.storybook/main.js` config:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
features: {
|
||||
previewMdx2: true,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### CSF3 auto-title redundant filename
|
||||
|
||||
SB 6.4 introduced experimental "auto-title", in which a story's location in the sidebar (aka `title`) can be automatically inferred from its location on disk. For example, the file `atoms/Button.stories.js` might result in the title `Atoms/Button`.
|
||||
|
@ -1,11 +1,8 @@
|
||||
const path = require('path');
|
||||
const mdx = require('@mdx-js/mdx');
|
||||
const { ScriptTransformer } = require('@jest/transform');
|
||||
const { dedent } = require('ts-dedent');
|
||||
|
||||
const { createCompiler } = require('@storybook/csf-mdx1');
|
||||
|
||||
const compilers = [createCompiler({})];
|
||||
const { compileSync } = require('@storybook/mdx1-csf');
|
||||
|
||||
module.exports = {
|
||||
process(src, filename, config, { instrument }) {
|
||||
@ -13,7 +10,7 @@ module.exports = {
|
||||
/* @jsx mdx */
|
||||
import React from 'react'
|
||||
import { mdx } from '@mdx-js/react'
|
||||
${mdx.sync(src, { compilers, filepath: filename })}
|
||||
${compileSync(src, { filepath: filename })}
|
||||
`;
|
||||
|
||||
const extension = path.extname(filename);
|
||||
|
@ -1 +1 @@
|
||||
module.exports = require('@storybook/csf-mdx1').createCompiler;
|
||||
module.exports = require('@storybook/mdx1-csf').createCompiler;
|
||||
|
@ -61,8 +61,6 @@
|
||||
"@babel/plugin-transform-react-jsx": "^7.12.12",
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@jest/transform": "^26.6.2",
|
||||
"@mdx-js/loader": "^1.6.22",
|
||||
"@mdx-js/mdx": "^1.6.22",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"@storybook/addons": "6.5.0-alpha.39",
|
||||
"@storybook/api": "6.5.0-alpha.39",
|
||||
@ -72,7 +70,7 @@
|
||||
"@storybook/core": "6.5.0-alpha.39",
|
||||
"@storybook/core-events": "6.5.0-alpha.39",
|
||||
"@storybook/csf": "0.0.2--canary.87bc651.0",
|
||||
"@storybook/csf-mdx1": "6.5.0-alpha.39",
|
||||
"@storybook/mdx1-csf": "0.0.1-canary.1.8dbf866.0",
|
||||
"@storybook/node-logger": "6.5.0-alpha.39",
|
||||
"@storybook/postinstall": "6.5.0-alpha.39",
|
||||
"@storybook/preview-web": "6.5.0-alpha.39",
|
||||
@ -108,6 +106,7 @@
|
||||
"@emotion/styled": "^10.0.27",
|
||||
"@storybook/angular": "6.5.0-alpha.39",
|
||||
"@storybook/html": "6.5.0-alpha.39",
|
||||
"@storybook/mdx2-csf": "0.0.1-canary.1.838a6ca.0",
|
||||
"@storybook/react": "6.5.0-alpha.39",
|
||||
"@storybook/vue": "6.5.0-alpha.39",
|
||||
"@storybook/web-components": "6.5.0-alpha.39",
|
||||
@ -137,6 +136,7 @@
|
||||
"peerDependencies": {
|
||||
"@storybook/angular": "6.5.0-alpha.39",
|
||||
"@storybook/html": "6.5.0-alpha.39",
|
||||
"@storybook/mdx2-csf": "0.0.1-canary.1.838a6ca.0",
|
||||
"@storybook/react": "6.5.0-alpha.39",
|
||||
"@storybook/vue": "6.5.0-alpha.39",
|
||||
"@storybook/vue3": "6.5.0-alpha.39",
|
||||
@ -157,6 +157,9 @@
|
||||
"@storybook/html": {
|
||||
"optional": true
|
||||
},
|
||||
"@storybook/mdx2-csf": {
|
||||
"optional": true
|
||||
},
|
||||
"@storybook/react": {
|
||||
"optional": true
|
||||
},
|
||||
|
@ -1,11 +1,12 @@
|
||||
import path from 'path';
|
||||
import remarkSlug from 'remark-slug';
|
||||
import remarkExternalLinks from 'remark-external-links';
|
||||
import global from 'global';
|
||||
|
||||
// @ts-ignore
|
||||
import { createCompiler } from '@storybook/csf-mdx1';
|
||||
import type { BuilderConfig, Options } from '@storybook/core-common';
|
||||
|
||||
const { log } = console;
|
||||
|
||||
// for frameworks that are not working with react, we need to configure
|
||||
// the jsx to transpile mdx, for now there will be a flag for that
|
||||
// for more complex solutions we can find alone that we need to add '@babel/plugin-transform-react-jsx'
|
||||
@ -34,9 +35,9 @@ function createBabelOptions({ babelOptions, mdxBabelOptions, configureJSX }: Bab
|
||||
export async function webpack(
|
||||
webpackConfig: any = {},
|
||||
options: Options &
|
||||
BabelParams & { sourceLoaderOptions: any; transcludeMarkdown: boolean } & Parameters<
|
||||
BabelParams & { sourceLoaderOptions: any; transcludeMarkdown: boolean } /* & Parameters<
|
||||
typeof createCompiler
|
||||
>[0]
|
||||
>[0] */
|
||||
) {
|
||||
const { builder = 'webpack4' } = await options.presets.apply<{
|
||||
builder: BuilderConfig;
|
||||
@ -65,6 +66,13 @@ export async function webpack(
|
||||
remarkPlugins: [remarkSlug, remarkExternalLinks],
|
||||
};
|
||||
|
||||
const mdxVersion = global.FEATURES?.previewMdx2 ? 'MDX2' : 'MDX1';
|
||||
log(`Addon-docs: using ${mdxVersion}`);
|
||||
|
||||
const mdxLoader = global.FEATURES?.previewMdx2
|
||||
? require.resolve('@storybook/mdx2-csf/loader')
|
||||
: require.resolve('@storybook/mdx1-csf/loader');
|
||||
|
||||
// set `sourceLoaderOptions` to `null` to disable for manual configuration
|
||||
const sourceLoader = sourceLoaderOptions
|
||||
? [
|
||||
@ -89,7 +97,7 @@ export async function webpack(
|
||||
options: createBabelOptions({ babelOptions, mdxBabelOptions, configureJSX }),
|
||||
},
|
||||
{
|
||||
loader: require.resolve('@mdx-js/loader'),
|
||||
loader: mdxLoader,
|
||||
options: mdxLoaderOptions,
|
||||
},
|
||||
],
|
||||
@ -123,11 +131,11 @@ export async function webpack(
|
||||
options: createBabelOptions({ babelOptions, mdxBabelOptions, configureJSX }),
|
||||
},
|
||||
{
|
||||
loader: require.resolve('@mdx-js/loader'),
|
||||
options: {
|
||||
compilers: [createCompiler(options)],
|
||||
...mdxLoaderOptions,
|
||||
},
|
||||
loader: mdxLoader,
|
||||
// options: {
|
||||
// compilers: [createCompiler(options)],
|
||||
// ...mdxLoaderOptions,
|
||||
// },
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -140,7 +148,7 @@ export async function webpack(
|
||||
options: createBabelOptions({ babelOptions, mdxBabelOptions, configureJSX }),
|
||||
},
|
||||
{
|
||||
loader: require.resolve('@mdx-js/loader'),
|
||||
loader: mdxLoader,
|
||||
options: mdxLoaderOptions,
|
||||
},
|
||||
],
|
||||
|
@ -32,6 +32,7 @@ const config: StorybookConfig = {
|
||||
buildStoriesJson: true,
|
||||
babelModeV7: true,
|
||||
warnOnLegacyHierarchySeparator: false,
|
||||
previewMdx2: true,
|
||||
},
|
||||
framework: '@storybook/react',
|
||||
};
|
||||
|
@ -6,4 +6,4 @@ import { Meta, Canvas } from '@storybook/addon-docs';
|
||||
|
||||
## [Link](http://https://storybook.js.org/) in heading
|
||||
|
||||
This file is a documentation-only MDX file, i.e. it doesn't contain any `<Story>` definitions.
|
||||
This file is a documentation-only MDX {1+1} file, i.e. it doesn't contain any `<Story>` definitions.
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Meta, Story } from '@storybook/addon-docs';
|
||||
import { Button } from '../button';
|
||||
|
||||
<Meta title="Docs/Button" component={Button} />
|
||||
<Meta title="Docs/ButtonMdx" component={Button} />
|
||||
|
||||
# Button
|
||||
# Button MDX
|
||||
|
||||
<Story name="Basic">
|
||||
<Button label="Click me" />
|
||||
|
@ -372,6 +372,11 @@ export interface StorybookConfig {
|
||||
* Will be removed in 7.0.
|
||||
*/
|
||||
warnOnLegacyHierarchySeparator?: boolean;
|
||||
|
||||
/**
|
||||
* Preview MDX2 support, will become default in 7.0
|
||||
*/
|
||||
previewMdx2?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -65,6 +65,7 @@
|
||||
"detect-port": "^1.3.0",
|
||||
"express": "^4.17.1",
|
||||
"fs-extra": "^9.0.1",
|
||||
"global": "^4.4.0",
|
||||
"globby": "^11.0.2",
|
||||
"ip": "^1.1.5",
|
||||
"lodash": "^4.17.21",
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
} from '@storybook/core-common';
|
||||
import dedent from 'ts-dedent';
|
||||
import prompts from 'prompts';
|
||||
import global from 'global';
|
||||
|
||||
import path from 'path';
|
||||
import { storybookDevServer } from './dev-server';
|
||||
@ -75,6 +76,7 @@ export async function buildDevStandalone(options: CLIOptions & LoadOptions & Bui
|
||||
});
|
||||
|
||||
const features = await presets.apply<StorybookConfig['features']>('features');
|
||||
global.FEATURES = features;
|
||||
|
||||
const fullOptions: Options = {
|
||||
...options,
|
||||
|
@ -3,6 +3,7 @@ import cpy from 'cpy';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import dedent from 'ts-dedent';
|
||||
import global from 'global';
|
||||
|
||||
import { logger } from '@storybook/node-logger';
|
||||
|
||||
@ -92,6 +93,8 @@ export async function buildStaticStandalone(options: CLIOptions & LoadOptions &
|
||||
}
|
||||
|
||||
const features = await presets.apply<StorybookConfig['features']>('features');
|
||||
global.FEATURES = features;
|
||||
|
||||
if (features?.buildStoriesJson || features?.storyStoreV7) {
|
||||
const directories = {
|
||||
configDir: options.configDir,
|
||||
|
@ -1,3 +0,0 @@
|
||||
# Storybook MDX1
|
||||
|
||||
This package contains an MDXv1-compatible compiler that transforms MDX documents with embedded stories into [Component Story Format (CSF)](https://storybook.js.org/docs/react/api/csf).
|
@ -1,64 +0,0 @@
|
||||
{
|
||||
"name": "@storybook/csf-mdx1",
|
||||
"version": "6.5.0-alpha.39",
|
||||
"description": "",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
],
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/main/lib/csf-mdx1",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybookjs/storybook/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/storybookjs/storybook.git",
|
||||
"directory": "lib/csf-mdx1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/storybook"
|
||||
},
|
||||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"main": "dist/cjs/index.js",
|
||||
"module": "dist/esm/index.js",
|
||||
"types": "dist/ts3.9/index.d.ts",
|
||||
"typesVersions": {
|
||||
"<3.8": {
|
||||
"dist/ts3.9/*": [
|
||||
"dist/ts3.4/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"README.md",
|
||||
"*.js",
|
||||
"*.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/generator": "^7.12.11",
|
||||
"@babel/parser": "^7.12.11",
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@babel/types": "^7.12.11",
|
||||
"@mdx-js/mdx": "^1.6.22",
|
||||
"core-js": "^3.8.2",
|
||||
"js-string-escape": "^1.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"prettier": ">=2.2.1 <=2.3.0",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"ts-dedent": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-string-escape": "^1.0.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "9b5d9b1f46a31a9881fe71a153a9b810e8203a93",
|
||||
"sbmodern": "dist/modern/index.js"
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
import mdx from '@mdx-js/mdx';
|
||||
import { createCompiler, MdxOptions } from './sb-mdx-plugin';
|
||||
|
||||
export const compile = async (code: string, options?: MdxOptions) =>
|
||||
mdx(code, { compilers: [createCompiler(options)] });
|
||||
|
||||
export * from './sb-mdx-plugin';
|
@ -1,878 +0,0 @@
|
||||
import dedent from 'ts-dedent';
|
||||
import path from 'path';
|
||||
import mdx from '@mdx-js/mdx';
|
||||
import prettier from 'prettier';
|
||||
import { createCompiler, wrapperJs } from './sb-mdx-plugin';
|
||||
|
||||
expect.addSnapshotSerializer({
|
||||
print: (val: any) => val,
|
||||
test: (val) => true,
|
||||
});
|
||||
|
||||
function clean(content) {
|
||||
const code = mdx
|
||||
.sync(content, {
|
||||
// filepath: filePath,
|
||||
compilers: [createCompiler({})],
|
||||
})
|
||||
.split('MDXContent.isMDXComponent = true;')[1]
|
||||
.split(wrapperJs)[0];
|
||||
|
||||
return prettier
|
||||
.format(code, {
|
||||
parser: 'babel',
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
bracketSpacing: true,
|
||||
trailingComma: 'es5',
|
||||
singleQuote: true,
|
||||
})
|
||||
.trim();
|
||||
}
|
||||
|
||||
const fixturesDir = path.join(__dirname, '..', '..', '__testfixtures__', 'mdx');
|
||||
|
||||
const snap = (prefix) => path.join(fixturesDir, `${prefix}.output.snapshot`);
|
||||
|
||||
describe('docs-mdx-compiler-plugin', () => {
|
||||
it('component-args.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Button" args={{ a: 1, b: 2 }} argTypes={{ a: { name: 'A' }, b: { name: 'B' } }} />
|
||||
|
||||
# Args
|
||||
|
||||
<Story name="component notes">
|
||||
<Button>Component notes</Button>
|
||||
</Story>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const componentNotes = () => <Button>Component notes</Button>;
|
||||
componentNotes.storyName = 'component notes';
|
||||
componentNotes.parameters = { storySource: { source: '<Button>Component notes</Button>' } };
|
||||
|
||||
const componentMeta = {
|
||||
title: 'Button',
|
||||
args: {
|
||||
a: 1,
|
||||
b: 2,
|
||||
},
|
||||
argTypes: {
|
||||
a: {
|
||||
name: 'A',
|
||||
},
|
||||
b: {
|
||||
name: 'B',
|
||||
},
|
||||
},
|
||||
includeStories: ['componentNotes'],
|
||||
};
|
||||
|
||||
const mdxStoryNameToKey = { 'component notes': 'componentNotes' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('component-id.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Button" component={Button} id="button-id" />
|
||||
|
||||
<Story name="component notes">
|
||||
<Button>Component notes</Button>
|
||||
</Story>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const componentNotes = () => <Button>Component notes</Button>;
|
||||
componentNotes.storyName = 'component notes';
|
||||
componentNotes.parameters = { storySource: { source: '<Button>Component notes</Button>' } };
|
||||
|
||||
const componentMeta = {
|
||||
title: 'Button',
|
||||
id: 'button-id',
|
||||
component: Button,
|
||||
includeStories: ['componentNotes'],
|
||||
};
|
||||
|
||||
const mdxStoryNameToKey = { 'component notes': 'componentNotes' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('csf-imports.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Story, Meta, Canvas } from '@storybook/addon-docs';
|
||||
import { Welcome, Button } from '@storybook/angular/demo';
|
||||
import * as MyStories from './My.stories';
|
||||
import { Other } from './Other.stories';
|
||||
|
||||
<Meta title="MDX/CSF imports" />
|
||||
|
||||
# Stories from CSF imports
|
||||
|
||||
<Story story={MyStories.Basic} />
|
||||
|
||||
<Canvas>
|
||||
<Story story={Other} />
|
||||
</Canvas>
|
||||
|
||||
<Story name="renamed" story={MyStories.Foo} />
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const _Basic_ = MyStories.Basic;
|
||||
|
||||
export const _Other_ = Other;
|
||||
|
||||
export const _Foo_ = MyStories.Foo;
|
||||
_Foo_.storyName = 'renamed';
|
||||
|
||||
const componentMeta = { title: 'MDX/CSF imports', includeStories: ['_Basic_', '_Other_', '_Foo_'] };
|
||||
|
||||
const mdxStoryNameToKey = { _Basic_: '_Basic_', _Other_: '_Other_', renamed: '_Foo_' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('decorators.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta
|
||||
title="Button"
|
||||
decorators={[(storyFn) => <div style={{ backgroundColor: 'yellow' }}>{storyFn()}</div>]}
|
||||
/>
|
||||
|
||||
# Decorated story
|
||||
|
||||
<Story name="one" decorators={[(storyFn) => <div className="local">{storyFn()}</div>]}>
|
||||
<Button>One</Button>
|
||||
</Story>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const one = () => <Button>One</Button>;
|
||||
one.storyName = 'one';
|
||||
one.parameters = { storySource: { source: '<Button>One</Button>' } };
|
||||
one.decorators = [(storyFn) => <div className="local">{storyFn()}</div>];
|
||||
|
||||
const componentMeta = {
|
||||
title: 'Button',
|
||||
decorators: [
|
||||
(storyFn) => (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'yellow',
|
||||
}}
|
||||
>
|
||||
{storyFn()}
|
||||
</div>
|
||||
),
|
||||
],
|
||||
includeStories: ['one'],
|
||||
};
|
||||
|
||||
const mdxStoryNameToKey = { one: 'one' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('docs-only.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="docs-only" />
|
||||
|
||||
# Documentation only
|
||||
|
||||
This is a documentation-only MDX file which cleans a dummy 'docsOnly: true' story.
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const __page = () => {
|
||||
throw new Error('Docs-only story');
|
||||
};
|
||||
|
||||
__page.parameters = { docsOnly: true };
|
||||
|
||||
const componentMeta = { title: 'docs-only', includeStories: ['__page'] };
|
||||
|
||||
const mdxStoryNameToKey = {};
|
||||
`);
|
||||
});
|
||||
|
||||
it('loaders.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Button" loaders={[async () => ({ foo: 1 })]} />
|
||||
|
||||
# Story with loader
|
||||
|
||||
<Story name="one" loaders={[async () => ({ bar: 2 })]}>
|
||||
<Button>One</Button>
|
||||
</Story>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const one = () => <Button>One</Button>;
|
||||
one.storyName = 'one';
|
||||
one.parameters = { storySource: { source: '<Button>One</Button>' } };
|
||||
one.loaders = [
|
||||
async () => ({
|
||||
bar: 2,
|
||||
}),
|
||||
];
|
||||
|
||||
const componentMeta = {
|
||||
title: 'Button',
|
||||
loaders: [
|
||||
async () => ({
|
||||
foo: 1,
|
||||
}),
|
||||
],
|
||||
includeStories: ['one'],
|
||||
};
|
||||
|
||||
const mdxStoryNameToKey = { one: 'one' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('meta-quotes-in-title.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Addons/Docs/what's in a title?" />
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const __page = () => {
|
||||
throw new Error('Docs-only story');
|
||||
};
|
||||
|
||||
__page.parameters = { docsOnly: true };
|
||||
|
||||
const componentMeta = { title: "Addons/Docs/what's in a title?", includeStories: ['__page'] };
|
||||
|
||||
const mdxStoryNameToKey = {};
|
||||
`);
|
||||
});
|
||||
|
||||
it('non-story-exports.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Button" />
|
||||
|
||||
# Story definition
|
||||
|
||||
<Story name="one">
|
||||
<Button>One</Button>
|
||||
</Story>
|
||||
|
||||
export const two = 2;
|
||||
|
||||
<Story name="hello story">
|
||||
<Button>Hello button</Button>
|
||||
</Story>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const one = () => <Button>One</Button>;
|
||||
one.storyName = 'one';
|
||||
one.parameters = { storySource: { source: '<Button>One</Button>' } };
|
||||
|
||||
export const helloStory = () => <Button>Hello button</Button>;
|
||||
helloStory.storyName = 'hello story';
|
||||
helloStory.parameters = { storySource: { source: '<Button>Hello button</Button>' } };
|
||||
|
||||
const componentMeta = { title: 'Button', includeStories: ['one', 'helloStory'] };
|
||||
|
||||
const mdxStoryNameToKey = { one: 'one', 'hello story': 'helloStory' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('parameters.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Button" component={Button} parameters={{ notes: 'component notes' }} />
|
||||
|
||||
<Story name="component notes">
|
||||
<Button>Component notes</Button>
|
||||
</Story>
|
||||
|
||||
<Story name="story notes" parameters={{ notes: 'story notes' }}>
|
||||
<Button>Story notes</Button>
|
||||
</Story>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const componentNotes = () => <Button>Component notes</Button>;
|
||||
componentNotes.storyName = 'component notes';
|
||||
componentNotes.parameters = { storySource: { source: '<Button>Component notes</Button>' } };
|
||||
|
||||
export const storyNotes = () => <Button>Story notes</Button>;
|
||||
storyNotes.storyName = 'story notes';
|
||||
storyNotes.parameters = {
|
||||
storySource: { source: '<Button>Story notes</Button>' },
|
||||
...{
|
||||
notes: 'story notes',
|
||||
},
|
||||
};
|
||||
|
||||
const componentMeta = {
|
||||
title: 'Button',
|
||||
parameters: {
|
||||
notes: 'component notes',
|
||||
},
|
||||
component: Button,
|
||||
includeStories: ['componentNotes', 'storyNotes'],
|
||||
};
|
||||
|
||||
const mdxStoryNameToKey = { 'component notes': 'componentNotes', 'story notes': 'storyNotes' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('previews.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Canvas, Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Button" component={Button} parameters={{ notes: 'component notes' }} />
|
||||
|
||||
# Canvas
|
||||
|
||||
Canvases can contain normal components, stories, and story references
|
||||
|
||||
<Canvas>
|
||||
<Button>Just a button</Button>
|
||||
<Story name="hello button">
|
||||
<Button>Hello button</Button>
|
||||
</Story>
|
||||
<Story name="two">
|
||||
<Button>Two</Button>
|
||||
</Story>
|
||||
<Story id="welcome--welcome" />
|
||||
</Canvas>
|
||||
|
||||
Canvas without a story
|
||||
|
||||
<Canvas>
|
||||
<Button>Just a button</Button>
|
||||
</Canvas>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const helloButton = () => <Button>Hello button</Button>;
|
||||
helloButton.storyName = 'hello button';
|
||||
helloButton.parameters = { storySource: { source: '<Button>Hello button</Button>' } };
|
||||
|
||||
export const two = () => <Button>Two</Button>;
|
||||
two.storyName = 'two';
|
||||
two.parameters = { storySource: { source: '<Button>Two</Button>' } };
|
||||
|
||||
const componentMeta = {
|
||||
title: 'Button',
|
||||
parameters: {
|
||||
notes: 'component notes',
|
||||
},
|
||||
component: Button,
|
||||
includeStories: ['helloButton', 'two'],
|
||||
};
|
||||
|
||||
const mdxStoryNameToKey = { 'hello button': 'helloButton', two: 'two' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('story-args.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Button" />
|
||||
|
||||
# Args
|
||||
|
||||
export const Template = (args) => <Button>Component notes</Button>;
|
||||
|
||||
<Story
|
||||
name="component notes"
|
||||
args={{ a: 1, b: 2 }}
|
||||
argTypes={{ a: { name: 'A' }, b: { name: 'B' } }}
|
||||
>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const componentNotes = Template.bind({});
|
||||
componentNotes.storyName = 'component notes';
|
||||
componentNotes.argTypes = {
|
||||
a: {
|
||||
name: 'A',
|
||||
},
|
||||
b: {
|
||||
name: 'B',
|
||||
},
|
||||
};
|
||||
componentNotes.args = {
|
||||
a: 1,
|
||||
b: 2,
|
||||
};
|
||||
componentNotes.parameters = { storySource: { source: 'args => <Button>Component notes</Button>' } };
|
||||
|
||||
const componentMeta = { title: 'Button', includeStories: ['componentNotes'] };
|
||||
|
||||
const mdxStoryNameToKey = { 'component notes': 'componentNotes' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('story-current.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Story } from '@storybook/addon-docs';
|
||||
|
||||
# Current story
|
||||
|
||||
<Story id="." />
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
const componentMeta = { includeStories: [] };
|
||||
|
||||
const mdxStoryNameToKey = {};
|
||||
`);
|
||||
});
|
||||
|
||||
it('story-def-text-only.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Text" />
|
||||
|
||||
# Story definition
|
||||
|
||||
<Story name="text">Plain text</Story>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const text = () => 'Plain text';
|
||||
text.storyName = 'text';
|
||||
text.parameters = { storySource: { source: "'Plain text'" } };
|
||||
|
||||
const componentMeta = { title: 'Text', includeStories: ['text'] };
|
||||
|
||||
const mdxStoryNameToKey = { text: 'text' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('story-definitions.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Button" />
|
||||
|
||||
# Story definition
|
||||
|
||||
<Story name="one">
|
||||
<Button>One</Button>
|
||||
</Story>
|
||||
|
||||
<Story name="hello story">
|
||||
<Button>Hello button</Button>
|
||||
</Story>
|
||||
|
||||
<Story name="w/punctuation">
|
||||
<Button>with punctuation</Button>
|
||||
</Story>
|
||||
|
||||
<Story name="1 fine day">
|
||||
<Button>starts with number</Button>
|
||||
</Story>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const one = () => <Button>One</Button>;
|
||||
one.storyName = 'one';
|
||||
one.parameters = { storySource: { source: '<Button>One</Button>' } };
|
||||
|
||||
export const helloStory = () => <Button>Hello button</Button>;
|
||||
helloStory.storyName = 'hello story';
|
||||
helloStory.parameters = { storySource: { source: '<Button>Hello button</Button>' } };
|
||||
|
||||
export const wPunctuation = () => <Button>with punctuation</Button>;
|
||||
wPunctuation.storyName = 'w/punctuation';
|
||||
wPunctuation.parameters = { storySource: { source: '<Button>with punctuation</Button>' } };
|
||||
|
||||
export const _1FineDay = () => <Button>starts with number</Button>;
|
||||
_1FineDay.storyName = '1 fine day';
|
||||
_1FineDay.parameters = { storySource: { source: '<Button>starts with number</Button>' } };
|
||||
|
||||
const componentMeta = {
|
||||
title: 'Button',
|
||||
includeStories: ['one', 'helloStory', 'wPunctuation', '_1FineDay'],
|
||||
};
|
||||
|
||||
const mdxStoryNameToKey = {
|
||||
one: 'one',
|
||||
'hello story': 'helloStory',
|
||||
'w/punctuation': 'wPunctuation',
|
||||
'1 fine day': '_1FineDay',
|
||||
};
|
||||
`);
|
||||
});
|
||||
|
||||
it('story-function-var.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Meta, Story } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="story-function-var" />
|
||||
|
||||
export const basicFn = () => <Button />;
|
||||
|
||||
# Button
|
||||
|
||||
I can define a story with the function defined in CSF:
|
||||
|
||||
<Story name="basic">{basicFn}</Story>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const basic = assertIsFn(basicFn);
|
||||
basic.storyName = 'basic';
|
||||
basic.parameters = { storySource: { source: 'basicFn' } };
|
||||
|
||||
const componentMeta = { title: 'story-function-var', includeStories: ['basic'] };
|
||||
|
||||
const mdxStoryNameToKey = { basic: 'basic' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('story-function.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
<Story name="function" height="100px">
|
||||
{() => {
|
||||
const btn = document.createElement('button');
|
||||
btn.innerHTML = 'Hello Button';
|
||||
btn.addEventListener('click', action('Click'));
|
||||
return btn;
|
||||
}}
|
||||
</Story>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const functionStory = () => {
|
||||
const btn = document.createElement('button');
|
||||
btn.innerHTML = 'Hello Button';
|
||||
btn.addEventListener('click', action('Click'));
|
||||
return btn;
|
||||
};
|
||||
functionStory.storyName = 'function';
|
||||
functionStory.parameters = {
|
||||
storySource: {
|
||||
source:
|
||||
"() => {\\n const btn = document.createElement('button');\\n btn.innerHTML = 'Hello Button';\\n btn.addEventListener('click', action('Click'));\\n return btn;\\n}",
|
||||
},
|
||||
};
|
||||
|
||||
const componentMeta = { includeStories: ['functionStory'] };
|
||||
|
||||
const mdxStoryNameToKey = { function: 'functionStory' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('story-multiple-children.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Multiple" />
|
||||
|
||||
# Multiple children
|
||||
|
||||
<Story name="multiple children">
|
||||
<p>Hello Child #1</p>
|
||||
<p>Hello Child #2</p>
|
||||
</Story>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const multipleChildren = () => (
|
||||
<>
|
||||
<p>Hello Child #1</p>
|
||||
<p>Hello Child #2</p>
|
||||
</>
|
||||
);
|
||||
multipleChildren.storyName = 'multiple children';
|
||||
multipleChildren.parameters = {
|
||||
storySource: { source: '<p>Hello Child #1</p>\\n<p>Hello Child #2</p>' },
|
||||
};
|
||||
|
||||
const componentMeta = { title: 'Multiple', includeStories: ['multipleChildren'] };
|
||||
|
||||
const mdxStoryNameToKey = { 'multiple children': 'multipleChildren' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('story-object.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
import { Welcome, Button } from '@storybook/angular/demo';
|
||||
import { linkTo } from '@storybook/addon-links';
|
||||
|
||||
<Meta title="MDX|Welcome" />
|
||||
|
||||
# Story object
|
||||
|
||||
<Story name="to storybook" height="300px">
|
||||
{{
|
||||
template: '<storybook-welcome-component (showApp)="showApp()"></storybook-welcome-component>',
|
||||
props: {
|
||||
showApp: linkTo('Button'),
|
||||
},
|
||||
moduleMetadata: {
|
||||
declarations: [Welcome],
|
||||
},
|
||||
}}
|
||||
</Story>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const toStorybook = () => ({
|
||||
template: '<storybook-welcome-component (showApp)="showApp()"></storybook-welcome-component>',
|
||||
props: {
|
||||
showApp: linkTo('Button'),
|
||||
},
|
||||
moduleMetadata: {
|
||||
declarations: [Welcome],
|
||||
},
|
||||
});
|
||||
toStorybook.storyName = 'to storybook';
|
||||
toStorybook.parameters = {
|
||||
storySource: {
|
||||
source:
|
||||
"{\\n template: '<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>',\\n props: {\\n showApp: linkTo('Button')\\n },\\n moduleMetadata: {\\n declarations: [Welcome]\\n }\\n}",
|
||||
},
|
||||
};
|
||||
|
||||
const componentMeta = { title: 'MDX|Welcome', includeStories: ['toStorybook'] };
|
||||
|
||||
const mdxStoryNameToKey = { 'to storybook': 'toStorybook' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('story-references.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Story } from '@storybook/addon-docs';
|
||||
|
||||
# Story reference
|
||||
|
||||
<Story id="welcome--welcome" />
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
const componentMeta = { includeStories: [] };
|
||||
|
||||
const mdxStoryNameToKey = {};
|
||||
`);
|
||||
});
|
||||
|
||||
it('title-template-string.mdx', () => {
|
||||
expect(
|
||||
clean(
|
||||
[
|
||||
"import { Meta, Story } from '@storybook/addon-docs';",
|
||||
"import { titleFunction } from '../title-generators';",
|
||||
'',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
"<Meta title={`${titleFunction('template')}`} />",
|
||||
].join('\n')
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const __page = () => {
|
||||
throw new Error('Docs-only story');
|
||||
};
|
||||
|
||||
__page.parameters = { docsOnly: true };
|
||||
|
||||
const componentMeta = { title: \`\${titleFunction('template')}\`, includeStories: ['__page'] };
|
||||
|
||||
const mdxStoryNameToKey = {};
|
||||
`);
|
||||
});
|
||||
|
||||
it('vanilla.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
|
||||
# Hello MDX
|
||||
|
||||
This is some random content.
|
||||
|
||||
<Button>Hello button</Button>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
const componentMeta = { includeStories: [] };
|
||||
|
||||
const mdxStoryNameToKey = {};
|
||||
`);
|
||||
});
|
||||
|
||||
it('errors on missing story props', async () => {
|
||||
await expect(async () =>
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Button" />
|
||||
|
||||
# Bad story
|
||||
|
||||
<Story>
|
||||
<Button>One</Button>
|
||||
</Story>
|
||||
`)
|
||||
).rejects.toThrow('Expected a Story name, id, or story attribute');
|
||||
});
|
||||
|
||||
describe('csf3', () => {
|
||||
it('auto-title-docs-only.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta />
|
||||
|
||||
# Auto-title Docs Only
|
||||
|
||||
Spme **markdown** here!
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
const componentMeta = { includeStories: [] };
|
||||
|
||||
const mdxStoryNameToKey = {};
|
||||
`);
|
||||
});
|
||||
|
||||
it('auto-title.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta component={Button} />
|
||||
|
||||
<Story name="Basic">
|
||||
<Button>Basic</Button>
|
||||
</Story>
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const basic = () => <Button>Basic</Button>;
|
||||
basic.storyName = 'Basic';
|
||||
basic.parameters = { storySource: { source: '<Button>Basic</Button>' } };
|
||||
|
||||
const componentMeta = { component: Button, includeStories: ['basic'] };
|
||||
|
||||
const mdxStoryNameToKey = { Basic: 'basic' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('default-render.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Button" component={Button} />
|
||||
|
||||
<Story name="Basic" />
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const basic = {};
|
||||
basic.storyName = 'Basic';
|
||||
basic.parameters = { storySource: { source: '{}' } };
|
||||
|
||||
const componentMeta = { title: 'Button', component: Button, includeStories: ['basic'] };
|
||||
|
||||
const mdxStoryNameToKey = { Basic: 'basic' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('component-render.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Button" component={Button} render={(args) => <Button {...args} />} />
|
||||
|
||||
<Story name="Basic" />
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const basic = {};
|
||||
basic.storyName = 'Basic';
|
||||
basic.parameters = { storySource: { source: '{}' } };
|
||||
|
||||
const componentMeta = {
|
||||
title: 'Button',
|
||||
component: Button,
|
||||
render: (args) => <Button {...args} />,
|
||||
includeStories: ['basic'],
|
||||
};
|
||||
|
||||
const mdxStoryNameToKey = { Basic: 'basic' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('story-render.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Button" component={Button} />
|
||||
|
||||
<Story name="Basic" render={(args) => <Button {...args} />} />
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const basic = {};
|
||||
basic.storyName = 'Basic';
|
||||
basic.parameters = { storySource: { source: '{}' } };
|
||||
basic.render = (args) => <Button {...args} />;
|
||||
|
||||
const componentMeta = { title: 'Button', component: Button, includeStories: ['basic'] };
|
||||
|
||||
const mdxStoryNameToKey = { Basic: 'basic' };
|
||||
`);
|
||||
});
|
||||
|
||||
it('story-play.mdx', () => {
|
||||
expect(
|
||||
clean(dedent`
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import { Story, Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Button" component={Button} />
|
||||
|
||||
<Story name="Basic" play={() => console.log('play')} />
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
export const basic = {};
|
||||
basic.storyName = 'Basic';
|
||||
basic.parameters = { storySource: { source: '{}' } };
|
||||
basic.play = () => console.log('play');
|
||||
|
||||
const componentMeta = { title: 'Button', component: Button, includeStories: ['basic'] };
|
||||
|
||||
const mdxStoryNameToKey = { Basic: 'basic' };
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,521 +0,0 @@
|
||||
import { toJSX } from '@mdx-js/mdx/mdx-hast-to-jsx';
|
||||
import { parse, parseExpression } from '@babel/parser';
|
||||
import * as t from '@babel/types';
|
||||
import generate from '@babel/generator';
|
||||
import camelCase from 'lodash/camelCase';
|
||||
import jsStringEscape from 'js-string-escape';
|
||||
|
||||
// Defined in MDX2.0
|
||||
export interface MdxOptions {
|
||||
filepath?: string;
|
||||
skipExport?: boolean;
|
||||
wrapExport?: string;
|
||||
remarkPlugins?: any[];
|
||||
rehypePlugins?: any[];
|
||||
}
|
||||
|
||||
interface CompilerOptions {
|
||||
filepath?: string;
|
||||
}
|
||||
|
||||
interface Element {
|
||||
type: string;
|
||||
children: Element[];
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface Context {
|
||||
counter: number;
|
||||
namedExports: Record<string, any>;
|
||||
storyNameToKey: Record<string, string>;
|
||||
root: Element;
|
||||
}
|
||||
|
||||
type MetaExport = Record<string, any>;
|
||||
|
||||
// Generate the MDX as is, but append named exports for every
|
||||
// story in the contents
|
||||
|
||||
const STORY_REGEX = /^<Story[\s>]/;
|
||||
const CANVAS_REGEX = /^<(Preview|Canvas)[\s>]/;
|
||||
const META_REGEX = /^<Meta[\s>]/;
|
||||
const RESERVED =
|
||||
/^(?:do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|await|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$/;
|
||||
|
||||
function getAttr(elt: t.JSXOpeningElement, what: string): t.JSXAttribute['value'] | undefined {
|
||||
const attr = (elt.attributes as t.JSXAttribute[]).find((n) => n.name.name === what);
|
||||
return attr?.value;
|
||||
}
|
||||
|
||||
const isReserved = (name: string) => RESERVED.exec(name);
|
||||
const startsWithNumber = (name: string) => /^\d/.exec(name);
|
||||
|
||||
const sanitizeName = (name: string) => {
|
||||
let key = camelCase(name);
|
||||
if (startsWithNumber(key)) {
|
||||
key = `_${key}`;
|
||||
} else if (isReserved(key)) {
|
||||
key = `${key}Story`;
|
||||
}
|
||||
return key;
|
||||
};
|
||||
|
||||
const getStoryKey = (name: string, counter: number) =>
|
||||
name ? sanitizeName(name) : `story${counter}`;
|
||||
|
||||
function genAttribute(key: string, element: t.JSXOpeningElement) {
|
||||
const value = getAttr(element, key);
|
||||
if (t.isJSXExpressionContainer(value)) {
|
||||
const { code } = generate(value.expression, {});
|
||||
return code;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function genImportStory(
|
||||
ast: t.JSXElement,
|
||||
storyDef: t.JSXExpressionContainer,
|
||||
storyName: string,
|
||||
context: Context
|
||||
) {
|
||||
const { code: story } = generate(storyDef.expression, {});
|
||||
|
||||
const storyKey = `_${story.split('.').pop()}_`;
|
||||
|
||||
const statements = [`export const ${storyKey} = ${story};`];
|
||||
if (storyName) {
|
||||
context.storyNameToKey[storyName] = storyKey;
|
||||
statements.push(`${storyKey}.storyName = '${storyName}';`);
|
||||
} else {
|
||||
context.storyNameToKey[storyKey] = storyKey;
|
||||
ast.openingElement.attributes.push(
|
||||
t.jsxAttribute(t.jsxIdentifier('name'), t.stringLiteral(storyKey))
|
||||
);
|
||||
}
|
||||
return {
|
||||
[storyKey]: statements.join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
function getBodyPart(bodyNode: t.Node, context: Context) {
|
||||
const body = t.isJSXExpressionContainer(bodyNode) ? bodyNode.expression : bodyNode;
|
||||
let sourceBody = body;
|
||||
if (
|
||||
t.isCallExpression(body) &&
|
||||
t.isMemberExpression(body.callee) &&
|
||||
t.isIdentifier(body.callee.object) &&
|
||||
t.isIdentifier(body.callee.property) &&
|
||||
body.callee.property.name === 'bind' &&
|
||||
(body.arguments.length === 0 ||
|
||||
(body.arguments.length === 1 &&
|
||||
t.isObjectExpression(body.arguments[0]) &&
|
||||
body.arguments[0].properties.length === 0))
|
||||
) {
|
||||
const bound = body.callee.object.name;
|
||||
const namedExport = context.namedExports[bound];
|
||||
if (namedExport) {
|
||||
sourceBody = namedExport;
|
||||
}
|
||||
}
|
||||
|
||||
const { code: storyCode } = generate(body, {});
|
||||
const { code: sourceCode } = generate(sourceBody, {});
|
||||
return { storyCode, sourceCode, body };
|
||||
}
|
||||
|
||||
const idOrNull = (attr: t.JSXAttribute['value']) => (t.isStringLiteral(attr) ? attr.value : null);
|
||||
const expressionOrNull = (attr: t.JSXAttribute['value']) =>
|
||||
t.isJSXExpressionContainer(attr) ? attr.expression : null;
|
||||
|
||||
function genStoryExport(ast: t.JSXElement, context: Context) {
|
||||
const storyName = idOrNull(getAttr(ast.openingElement, 'name'));
|
||||
const storyId = idOrNull(getAttr(ast.openingElement, 'id'));
|
||||
const storyRef = getAttr(ast.openingElement, 'story') as t.JSXExpressionContainer;
|
||||
|
||||
if (!storyId && !storyName && !storyRef) {
|
||||
throw new Error('Expected a Story name, id, or story attribute');
|
||||
}
|
||||
|
||||
// We don't generate exports for story references or the smart "current story"
|
||||
if (storyId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (storyRef) {
|
||||
return genImportStory(ast, storyRef, storyName, context);
|
||||
}
|
||||
|
||||
const statements = [];
|
||||
const storyKey = getStoryKey(storyName, context.counter);
|
||||
|
||||
const bodyNodes = ast.children.filter((n) => !t.isJSXText(n));
|
||||
let storyCode = null;
|
||||
let sourceCode = null;
|
||||
let storyVal = null;
|
||||
if (!bodyNodes.length) {
|
||||
if (ast.children.length > 0) {
|
||||
// plain text node
|
||||
const { code } = generate(ast.children[0], {});
|
||||
storyCode = `'${code}'`;
|
||||
sourceCode = storyCode;
|
||||
storyVal = `() => (
|
||||
${storyCode}
|
||||
)`;
|
||||
} else {
|
||||
sourceCode = '{}';
|
||||
storyVal = '{}';
|
||||
}
|
||||
} else {
|
||||
const bodyParts = bodyNodes.map((bodyNode) => getBodyPart(bodyNode, context));
|
||||
// if we have more than two children
|
||||
// 1. Add line breaks
|
||||
// 2. Enclose in <> ... </>
|
||||
storyCode = bodyParts.map(({ storyCode: code }) => code).join('\n');
|
||||
sourceCode = bodyParts.map(({ sourceCode: code }) => code).join('\n');
|
||||
const storyReactCode = bodyParts.length > 1 ? `<>\n${storyCode}\n</>` : storyCode;
|
||||
// keep track if an identifier or function call
|
||||
// avoid breaking change for 5.3
|
||||
const BIND_REGEX = /\.bind\(.*\)/;
|
||||
if (bodyParts.length === 1) {
|
||||
if (BIND_REGEX.test(bodyParts[0].storyCode)) {
|
||||
storyVal = bodyParts[0].storyCode;
|
||||
} else if (t.isIdentifier(bodyParts[0].body)) {
|
||||
storyVal = `assertIsFn(${storyCode})`;
|
||||
} else if (t.isArrowFunctionExpression(bodyParts[0].body)) {
|
||||
storyVal = `(${storyCode})`;
|
||||
} else {
|
||||
storyVal = `() => (
|
||||
${storyReactCode}
|
||||
)`;
|
||||
}
|
||||
} else {
|
||||
storyVal = `() => (
|
||||
${storyReactCode}
|
||||
)`;
|
||||
}
|
||||
}
|
||||
|
||||
statements.push(`export const ${storyKey} = ${storyVal};`);
|
||||
|
||||
// always preserve the name, since CSF exports can get modified by displayName
|
||||
statements.push(`${storyKey}.storyName = '${storyName}';`);
|
||||
|
||||
const argTypes = genAttribute('argTypes', ast.openingElement);
|
||||
if (argTypes) statements.push(`${storyKey}.argTypes = ${argTypes};`);
|
||||
|
||||
const args = genAttribute('args', ast.openingElement);
|
||||
if (args) statements.push(`${storyKey}.args = ${args};`);
|
||||
|
||||
const parameters = expressionOrNull(getAttr(ast.openingElement, 'parameters'));
|
||||
const source = jsStringEscape(sourceCode);
|
||||
const sourceParam = `storySource: { source: '${source}' }`;
|
||||
if (parameters) {
|
||||
const { code: params } = generate(parameters, {});
|
||||
statements.push(`${storyKey}.parameters = { ${sourceParam}, ...${params} };`);
|
||||
} else {
|
||||
statements.push(`${storyKey}.parameters = { ${sourceParam} };`);
|
||||
}
|
||||
|
||||
const decorators = expressionOrNull(getAttr(ast.openingElement, 'decorators'));
|
||||
if (decorators) {
|
||||
const { code: decos } = generate(decorators, {});
|
||||
statements.push(`${storyKey}.decorators = ${decos};`);
|
||||
}
|
||||
|
||||
const loaders = expressionOrNull(getAttr(ast.openingElement, 'loaders'));
|
||||
if (loaders) {
|
||||
const { code: loaderCode } = generate(loaders, {});
|
||||
statements.push(`${storyKey}.loaders = ${loaderCode};`);
|
||||
}
|
||||
|
||||
const play = expressionOrNull(getAttr(ast.openingElement, 'play'));
|
||||
if (play) {
|
||||
const { code: playCode } = generate(play, {});
|
||||
statements.push(`${storyKey}.play = ${playCode};`);
|
||||
}
|
||||
|
||||
const render = expressionOrNull(getAttr(ast.openingElement, 'render'));
|
||||
if (render) {
|
||||
const { code: renderCode } = generate(render, {});
|
||||
statements.push(`${storyKey}.render = ${renderCode};`);
|
||||
}
|
||||
|
||||
context.storyNameToKey[storyName] = storyKey;
|
||||
|
||||
return {
|
||||
[storyKey]: statements.join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
function genCanvasExports(ast: t.JSXElement, context: Context) {
|
||||
const canvasExports = {};
|
||||
for (let i = 0; i < ast.children.length; i += 1) {
|
||||
const child = ast.children[i];
|
||||
if (
|
||||
t.isJSXElement(child) &&
|
||||
t.isJSXIdentifier(child.openingElement.name) &&
|
||||
child.openingElement.name.name === 'Story'
|
||||
) {
|
||||
const storyExport = genStoryExport(child, context);
|
||||
const { code } = generate(child, {});
|
||||
// @ts-ignore
|
||||
child.value = code;
|
||||
if (storyExport) {
|
||||
Object.assign(canvasExports, storyExport);
|
||||
context.counter += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return canvasExports;
|
||||
}
|
||||
|
||||
function genMeta(ast: t.JSXElement, options: CompilerOptions) {
|
||||
const titleAttr = getAttr(ast.openingElement, 'title');
|
||||
const idAttr = getAttr(ast.openingElement, 'id');
|
||||
let title = null;
|
||||
if (titleAttr) {
|
||||
if (t.isStringLiteral(titleAttr)) {
|
||||
title = "'".concat(jsStringEscape(titleAttr.value), "'");
|
||||
} else if (t.isJSXExpressionContainer(titleAttr)) {
|
||||
try {
|
||||
// generate code, so the expression is evaluated by the CSF compiler
|
||||
const { code } = generate(titleAttr.expression, {});
|
||||
// remove the curly brackets at start and end of code
|
||||
title = code.replace(/^\{(.+)\}$/, '$1');
|
||||
} catch (e) {
|
||||
// eat exception if title parsing didn't go well
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Invalid title:', options.filepath);
|
||||
title = undefined;
|
||||
}
|
||||
} else {
|
||||
console.warn(`Unknown title attr: ${titleAttr.type}`);
|
||||
}
|
||||
}
|
||||
const id = t.isStringLiteral(idAttr) ? `'${idAttr.value}'` : null;
|
||||
const parameters = genAttribute('parameters', ast.openingElement);
|
||||
const decorators = genAttribute('decorators', ast.openingElement);
|
||||
const loaders = genAttribute('loaders', ast.openingElement);
|
||||
const component = genAttribute('component', ast.openingElement);
|
||||
const subcomponents = genAttribute('subcomponents', ast.openingElement);
|
||||
const args = genAttribute('args', ast.openingElement);
|
||||
const argTypes = genAttribute('argTypes', ast.openingElement);
|
||||
const render = genAttribute('render', ast.openingElement);
|
||||
|
||||
return {
|
||||
title,
|
||||
id,
|
||||
parameters,
|
||||
decorators,
|
||||
loaders,
|
||||
component,
|
||||
subcomponents,
|
||||
args,
|
||||
argTypes,
|
||||
render,
|
||||
};
|
||||
}
|
||||
|
||||
function getExports(node: Element, context: Context, options: CompilerOptions) {
|
||||
const { value, type } = node;
|
||||
if (type === 'jsx') {
|
||||
if (STORY_REGEX.exec(value)) {
|
||||
// Single story
|
||||
const ast = parseExpression(value, {
|
||||
plugins: ['jsx'],
|
||||
}) as unknown as t.JSXElement;
|
||||
const storyExport = genStoryExport(ast, context);
|
||||
const { code } = generate(ast, {});
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
node.value = code;
|
||||
return storyExport && { stories: storyExport };
|
||||
}
|
||||
if (CANVAS_REGEX.exec(value)) {
|
||||
// Canvas/Preview, possibly containing multiple stories
|
||||
const ast = parseExpression(value, { plugins: ['jsx'] }) as t.JSXElement;
|
||||
|
||||
const canvasExports = genCanvasExports(ast, context);
|
||||
|
||||
// We're overwriting the Canvas tag here with a version that
|
||||
// has the `name` attribute (e.g. `<Story name="..." story={...} />`)
|
||||
// even if the user didn't provide one. We need the name attribute when
|
||||
// we render the node at runtime.
|
||||
const { code } = generate(ast, {});
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
node.value = code;
|
||||
return { stories: canvasExports };
|
||||
}
|
||||
if (META_REGEX.exec(value)) {
|
||||
const ast = parseExpression(value, { plugins: ['jsx'] }) as t.JSXElement;
|
||||
return { meta: genMeta(ast, options) };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// insert `mdxStoryNameToKey` and `mdxComponentMeta` into the context so that we
|
||||
// can reconstruct the Story ID dynamically from the `name` at render time
|
||||
export const wrapperJs = `
|
||||
componentMeta.parameters = componentMeta.parameters || {};
|
||||
componentMeta.parameters.docs = {
|
||||
...(componentMeta.parameters.docs || {}),
|
||||
page: () => <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentAnnotations={componentMeta}><MDXContent /></AddContext>,
|
||||
};
|
||||
`.trim();
|
||||
|
||||
// Use this rather than JSON.stringify because `Meta`'s attributes
|
||||
// are already valid code strings, so we want to insert them raw
|
||||
// rather than add an extra set of quotes
|
||||
function stringifyMeta(meta: object) {
|
||||
let result = '{ ';
|
||||
Object.entries(meta).forEach(([key, val]) => {
|
||||
if (val) {
|
||||
result += `${key}: ${val}, `;
|
||||
}
|
||||
});
|
||||
result += ' }';
|
||||
return result;
|
||||
}
|
||||
|
||||
const hasStoryChild = (node: t.JSXElement): boolean => {
|
||||
if (
|
||||
node.openingElement &&
|
||||
t.isJSXIdentifier(node.openingElement.name) &&
|
||||
node.openingElement.name.name === 'Story'
|
||||
) {
|
||||
return !!node;
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
return !!node.children.find((child: t.JSXElement) => hasStoryChild(child));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const getMdxSource = (children: t.Node[]) =>
|
||||
encodeURI(children.map((el) => generate(el).code).join('\n'));
|
||||
|
||||
// Parse out the named exports from a node, where the key
|
||||
// is the variable name and the value is the AST of the
|
||||
// variable declaration initializer
|
||||
const getNamedExports = (node: Element) => {
|
||||
const namedExports: Record<string, t.Expression> = {};
|
||||
const ast = parse(node.value, {
|
||||
sourceType: 'module',
|
||||
plugins: ['jsx'],
|
||||
// FIXME!!! presets: ['env]
|
||||
});
|
||||
if (t.isFile(ast) && t.isProgram(ast.program) && ast.program.body.length === 1) {
|
||||
const exported = ast.program.body[0];
|
||||
if (
|
||||
t.isExportNamedDeclaration(exported) &&
|
||||
t.isVariableDeclaration(exported.declaration) &&
|
||||
exported.declaration.declarations.length === 1
|
||||
) {
|
||||
const declaration = exported.declaration.declarations[0];
|
||||
if (t.isVariableDeclarator(declaration) && t.isIdentifier(declaration.id)) {
|
||||
const { name } = declaration.id;
|
||||
namedExports[name] = declaration.init;
|
||||
}
|
||||
}
|
||||
}
|
||||
return namedExports;
|
||||
};
|
||||
|
||||
function extractExports(root: Element, options: CompilerOptions) {
|
||||
const namedExports = {};
|
||||
root.children.forEach((child) => {
|
||||
if (child.type === 'jsx') {
|
||||
try {
|
||||
const ast = parseExpression(child.value, { plugins: ['jsx'] }) as t.JSXElement;
|
||||
if (
|
||||
t.isJSXOpeningElement(ast.openingElement) &&
|
||||
['Preview', 'Canvas'].includes((ast.openingElement.name as t.JSXIdentifier).name) &&
|
||||
!hasStoryChild(ast)
|
||||
) {
|
||||
const canvasAst = ast.openingElement;
|
||||
canvasAst.attributes.push(
|
||||
t.jsxAttribute(
|
||||
t.jsxIdentifier('mdxSource'),
|
||||
t.stringLiteral(getMdxSource(ast.children))
|
||||
)
|
||||
);
|
||||
}
|
||||
const { code } = generate(ast, {});
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
child.value = code;
|
||||
} catch {
|
||||
/** catch erroneous child.value string where the babel parseExpression makes exception
|
||||
* https://github.com/mdx-js/mdx/issues/767
|
||||
* eg <button>
|
||||
* <div>hello world</div>
|
||||
*
|
||||
* </button>
|
||||
* generates error
|
||||
* 1. child.value =`<button>\n <div>hello world</div`
|
||||
* 2. child.value =`\n`
|
||||
* 3. child.value =`</button>`
|
||||
*
|
||||
*/
|
||||
}
|
||||
} else if (child.type === 'export') {
|
||||
Object.assign(namedExports, getNamedExports(child));
|
||||
}
|
||||
});
|
||||
// we're overriding default export
|
||||
const storyExports = [];
|
||||
const includeStories = [];
|
||||
let metaExport: MetaExport | null = null;
|
||||
const context: Context = {
|
||||
counter: 0,
|
||||
storyNameToKey: {},
|
||||
root,
|
||||
namedExports,
|
||||
};
|
||||
root.children.forEach((n) => {
|
||||
const exports = getExports(n, context, options);
|
||||
if (exports) {
|
||||
const { stories, meta } = exports;
|
||||
if (stories) {
|
||||
Object.entries(stories).forEach(([key, story]) => {
|
||||
includeStories.push(key);
|
||||
storyExports.push(story);
|
||||
});
|
||||
}
|
||||
if (meta) {
|
||||
if (metaExport) {
|
||||
throw new Error('Meta can only be declared once');
|
||||
}
|
||||
metaExport = meta;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (metaExport) {
|
||||
if (!storyExports.length) {
|
||||
storyExports.push('export const __page = () => { throw new Error("Docs-only story"); };');
|
||||
storyExports.push('__page.parameters = { docsOnly: true };');
|
||||
includeStories.push('__page');
|
||||
}
|
||||
} else {
|
||||
metaExport = {};
|
||||
}
|
||||
metaExport.includeStories = JSON.stringify(includeStories);
|
||||
|
||||
const defaultJsx = toJSX(root, {}, { ...options, skipExport: true });
|
||||
const fullJsx = [
|
||||
'import { assertIsFn, AddContext } from "@storybook/addon-docs";',
|
||||
defaultJsx,
|
||||
...storyExports,
|
||||
`const componentMeta = ${stringifyMeta(metaExport)};`,
|
||||
`const mdxStoryNameToKey = ${JSON.stringify(context.storyNameToKey)};`,
|
||||
wrapperJs,
|
||||
'export default componentMeta;',
|
||||
].join('\n\n');
|
||||
|
||||
return fullJsx;
|
||||
}
|
||||
|
||||
export function createCompiler(mdxOptions: MdxOptions) {
|
||||
return function compiler(options: CompilerOptions = {}) {
|
||||
this.Compiler = (root: Element) => extractExports(root, options);
|
||||
};
|
||||
}
|
4
lib/csf-mdx1/src/typings.d.ts
vendored
4
lib/csf-mdx1/src/typings.d.ts
vendored
@ -1,4 +0,0 @@
|
||||
declare module '@mdx-js/react';
|
||||
declare module '@mdx-js/mdx';
|
||||
declare module '@mdx-js/mdx/mdx-hast-to-jsx';
|
||||
declare module 'js-string-escape';
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/tests/**/*",
|
||||
"src/**/__tests__/**/*",
|
||||
"src/**/*.stories.*",
|
||||
"src/**/*.mockdata.*",
|
||||
"src/**/__testfixtures__/**"
|
||||
]
|
||||
}
|
20
lib/csf-tools/index.cjs
Normal file
20
lib/csf-tools/index.cjs
Normal file
@ -0,0 +1,20 @@
|
||||
const global = require('global');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
const lib = require('./dist/cjs/index');
|
||||
|
||||
const readCsfOrMdx = async (fileName, options) => {
|
||||
let code = (await fs.readFile(fileName, 'utf-8')).toString();
|
||||
if (fileName.endsWith('.mdx')) {
|
||||
const { compile } = global.FEATURES?.previewMdx2
|
||||
? await import('@storybook/mdx2-csf')
|
||||
: await import('@storybook/mdx1-csf');
|
||||
code = await compile(code);
|
||||
}
|
||||
return lib.loadCsf(code, { ...options, fileName });
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
readCsfOrMdx,
|
||||
...lib,
|
||||
};
|
4
lib/csf-tools/index.d.ts
vendored
Normal file
4
lib/csf-tools/index.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import { CsfFile, CsfOptions } from './dist/ts3.9/index.d';
|
||||
|
||||
export declare const readCsfOrMdx: (fileName: string, options: CsfOptions) => Promise<CsfFile>;
|
||||
export * from './dist/ts3.9/index.d';
|
17
lib/csf-tools/index.mjs
Normal file
17
lib/csf-tools/index.mjs
Normal file
@ -0,0 +1,17 @@
|
||||
import global from 'global';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { loadCsf } from './dist/esm/index';
|
||||
|
||||
export const readCsfOrMdx = async (fileName, options) => {
|
||||
let code = (await fs.readFile(fileName, 'utf-8')).toString();
|
||||
if (fileName.endsWith('.mdx')) {
|
||||
const { compile } = global.FEATURES?.previewMdx2
|
||||
? await import('@storybook/mdx2-csf')
|
||||
: await import('@storybook/mdx1-csf');
|
||||
code = await compile(code);
|
||||
}
|
||||
return loadCsf(code, { ...options, fileName });
|
||||
};
|
||||
|
||||
export * from './dist/esm/index';
|
3
lib/csf-tools/mdx.d.ts
vendored
3
lib/csf-tools/mdx.d.ts
vendored
@ -1 +1,2 @@
|
||||
export * from '@storybook/csf-mdx1';
|
||||
declare module '@mdx-js/loader';
|
||||
export * from '@storybook/mdx1-csf';
|
||||
|
@ -1 +1 @@
|
||||
module.exports = require('@storybook/csf-mdx1');
|
||||
module.exports = require('@storybook/mdx1-csf');
|
||||
|
@ -20,9 +20,9 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"main": "dist/cjs/index.js",
|
||||
"module": "dist/esm/index.js",
|
||||
"types": "dist/ts3.9/index.d.ts",
|
||||
"main": "index.cjs",
|
||||
"module": "index.mjs",
|
||||
"types": "index.d.ts",
|
||||
"typesVersions": {
|
||||
"<3.8": {
|
||||
"dist/ts3.9/*": [
|
||||
@ -48,16 +48,26 @@
|
||||
"@babel/traverse": "^7.12.11",
|
||||
"@babel/types": "^7.12.11",
|
||||
"@storybook/csf": "0.0.2--canary.87bc651.0",
|
||||
"@storybook/csf-mdx1": "6.5.0-alpha.39",
|
||||
"@storybook/mdx1-csf": "0.0.1-canary.1.8dbf866.0",
|
||||
"core-js": "^3.8.2",
|
||||
"fs-extra": "^9.0.1",
|
||||
"global": "^4.4.0",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"ts-dedent": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/mdx2-csf": "0.0.1-canary.1.838a6ca.0",
|
||||
"@types/fs-extra": "^9.0.6",
|
||||
"js-yaml": "^3.14.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@storybook/mdx2-csf": "0.0.1-canary.1.838a6ca.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@storybook/mdx2-csf": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
@ -1,16 +1,3 @@
|
||||
import fs from 'fs-extra';
|
||||
import { compile } from '@storybook/csf-mdx1';
|
||||
|
||||
import { loadCsf, CsfOptions } from './CsfFile';
|
||||
|
||||
export const readCsfOrMdx = async (fileName: string, options: CsfOptions) => {
|
||||
let code = (await fs.readFile(fileName, 'utf-8')).toString();
|
||||
if (fileName.endsWith('.mdx')) {
|
||||
code = await compile(code);
|
||||
}
|
||||
return loadCsf(code, { ...options, fileName });
|
||||
};
|
||||
|
||||
export * from './CsfFile';
|
||||
export * from './ConfigFile';
|
||||
export * from './getStorySortParameter';
|
||||
|
3
nx.json
3
nx.json
@ -235,9 +235,6 @@
|
||||
"@storybook/theming": {
|
||||
"implicitDependencies": []
|
||||
},
|
||||
"@storybook/csf-mdx1": {
|
||||
"implicitDependencies": []
|
||||
},
|
||||
"@storybook/csf-tools": {
|
||||
"implicitDependencies": []
|
||||
},
|
||||
|
@ -163,7 +163,6 @@
|
||||
"@storybook/components": "workspace:*",
|
||||
"@storybook/core": "workspace:*",
|
||||
"@storybook/core-events": "workspace:*",
|
||||
"@storybook/csf-mdx1": "workspace:*",
|
||||
"@storybook/csf-tools": "workspace:*",
|
||||
"@storybook/ember": "workspace:*",
|
||||
"@storybook/eslint-config-storybook": "^2.4.0",
|
||||
|
@ -289,10 +289,6 @@
|
||||
"root": "lib/theming",
|
||||
"type": "library"
|
||||
},
|
||||
"@storybook/csf-mdx1": {
|
||||
"root": "lib/csf-mdx1",
|
||||
"type": "library"
|
||||
},
|
||||
"@storybook/csf-tools": {
|
||||
"root": "lib/csf-tools",
|
||||
"type": "library"
|
||||
|
Loading…
x
Reference in New Issue
Block a user