Addon-docs: Add opt-in MDX2 support

This commit is contained in:
Michael Shilman 2022-02-22 16:56:51 +08:00
parent 99f9de1144
commit b749473a98
30 changed files with 1194 additions and 1597 deletions

View File

@ -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`.

View File

@ -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);

View File

@ -1 +1 @@
module.exports = require('@storybook/csf-mdx1').createCompiler;
module.exports = require('@storybook/mdx1-csf').createCompiler;

View File

@ -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
},

View File

@ -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,
},
],

View File

@ -32,6 +32,7 @@ const config: StorybookConfig = {
buildStoriesJson: true,
babelModeV7: true,
warnOnLegacyHierarchySeparator: false,
previewMdx2: true,
},
framework: '@storybook/react',
};

View File

@ -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.

View File

@ -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" />

View File

@ -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;
};
/**

View File

@ -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",

View File

@ -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,

View File

@ -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,

View File

@ -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).

View File

@ -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"
}

View File

@ -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';

View File

@ -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' };
`);
});
});
});

View File

@ -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);
};
}

View File

@ -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';

View File

@ -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
View 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
View 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
View 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';

View File

@ -1 +1,2 @@
export * from '@storybook/csf-mdx1';
declare module '@mdx-js/loader';
export * from '@storybook/mdx1-csf';

View File

@ -1 +1 @@
module.exports = require('@storybook/csf-mdx1');
module.exports = require('@storybook/mdx1-csf');

View File

@ -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"
},

View File

@ -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';

View File

@ -235,9 +235,6 @@
"@storybook/theming": {
"implicitDependencies": []
},
"@storybook/csf-mdx1": {
"implicitDependencies": []
},
"@storybook/csf-tools": {
"implicitDependencies": []
},

View File

@ -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",

View File

@ -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"

1124
yarn.lock

File diff suppressed because it is too large Load Diff