From f5ebdde9e1c7d43e846fae926625ef1ff1cabc34 Mon Sep 17 00:00:00 2001 From: Tom Coleman Date: Mon, 20 Jun 2022 16:34:40 +1000 Subject: [PATCH 1/4] Switch `` to expect full exports --- .../docs/src/blocks/ExternalDocsContainer.tsx | 7 ++- addons/docs/src/blocks/ExternalPreview.ts | 21 +++---- addons/docs/src/blocks/Meta.tsx | 4 +- addons/docs/src/blocks/Story.tsx | 9 +-- .../external-docs/components/AccountForm.mdx | 6 +- examples/external-docs/pages/AccountForm.mdx | 2 +- examples/external-docs/pages/index.mdx | 10 ++-- examples/react-ts/src/docs2/MetaOf.mdx | 6 +- .../src/utils/StoryIndexGenerator.ts | 4 +- lib/preview-web/src/DocsRender.ts | 55 ++++++++++++------- lib/preview-web/src/types.ts | 6 +- 11 files changed, 74 insertions(+), 56 deletions(-) diff --git a/addons/docs/src/blocks/ExternalDocsContainer.tsx b/addons/docs/src/blocks/ExternalDocsContainer.tsx index c4b0580b8879..b260af12af36 100644 --- a/addons/docs/src/blocks/ExternalDocsContainer.tsx +++ b/addons/docs/src/blocks/ExternalDocsContainer.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { ThemeProvider, themes, ensure } from '@storybook/theming'; import { DocsContextProps } from '@storybook/preview-web'; -import { ModuleExport, Story } from '@storybook/store'; +import { ModuleExport, ModuleExports, Story } from '@storybook/store'; import { AnyFramework, StoryId } from '@storybook/csf'; import { DocsContext } from './DocsContext'; @@ -28,8 +28,8 @@ export const ExternalDocsContainer: React.FC<{ projectAnnotations: any }> = ({ title: 'External', name: 'Docs', - storyIdByModuleExport: (storyExport: ModuleExport) => { - return preview.storyIdByModuleExport(storyExport, pageMeta); + storyIdByModuleExport: (storyExport: ModuleExport, metaExport: ModuleExports) => { + return preview.storyIdByModuleExport(storyExport, metaExport || pageMeta); }, storyById: (id: StoryId) => { @@ -41,6 +41,7 @@ export const ExternalDocsContainer: React.FC<{ projectAnnotations: any }> = ({ }, componentStories: () => { + // TODO: could implement in a very similar way to in DocsRender. (TODO: How to share code?) throw new Error('not implemented'); }, diff --git a/addons/docs/src/blocks/ExternalPreview.ts b/addons/docs/src/blocks/ExternalPreview.ts index cd365a89e9ef..7b5874b0869d 100644 --- a/addons/docs/src/blocks/ExternalPreview.ts +++ b/addons/docs/src/blocks/ExternalPreview.ts @@ -3,7 +3,7 @@ import { Path, ModuleExports, StoryIndex, ModuleExport } from '@storybook/store' import { toId, AnyFramework, ComponentTitle, StoryId, ProjectAnnotations } from '@storybook/csf'; type StoryExport = ModuleExport; -type MetaExport = ModuleExport; +type MetaExport = ModuleExports; type ExportName = string; class ConstantMap { @@ -27,8 +27,6 @@ export class ExternalPreview extends Preview('title-'); - private exportNames = new ConstantMap('story-'); - public storyIds = new Map(); private storyIndex: StoryIndex = { v: 4, entries: {} }; @@ -46,19 +44,18 @@ export class ExternalPreview extends Preview moduleExport === storyExport + ); + if (!exportEntry) + throw new Error(`Didn't find \`of\` used in Story block in the provided CSF exports`); + const storyId = toId(title, exportEntry[0]); this.storyIds.set(storyExport, storyId); - // We need to be sure to create a new object each time here to bust caches - this.moduleExportsByImportPath[importPath] = { - ...this.moduleExportsByImportPath[importPath], - default: meta, - [exportName]: storyExport, - }; - this.storyIndex.entries[storyId] = { id: storyId, importPath, diff --git a/addons/docs/src/blocks/Meta.tsx b/addons/docs/src/blocks/Meta.tsx index 5ee54432b432..12f8f285fd80 100644 --- a/addons/docs/src/blocks/Meta.tsx +++ b/addons/docs/src/blocks/Meta.tsx @@ -1,12 +1,14 @@ import React, { FC, useContext } from 'react'; import global from 'global'; import { BaseAnnotations } from '@storybook/csf'; +import type { ModuleExports } from '@storybook/store'; + import { Anchor } from './Anchor'; import { DocsContext, DocsContextProps } from './DocsContext'; const { document } = global; -type MetaProps = BaseAnnotations & { of?: any }; +type MetaProps = BaseAnnotations & { of?: ModuleExports }; function getFirstStoryId(docsContext: DocsContextProps): string { const stories = docsContext.componentStories(); diff --git a/addons/docs/src/blocks/Story.tsx b/addons/docs/src/blocks/Story.tsx index a32bf40122e8..85414666eb84 100644 --- a/addons/docs/src/blocks/Story.tsx +++ b/addons/docs/src/blocks/Story.tsx @@ -11,7 +11,7 @@ import React, { import { MDXProvider } from '@mdx-js/react'; import { resetComponents, Story as PureStory, StorySkeleton } from '@storybook/components'; import { StoryId, toId, storyNameFromExport, StoryAnnotations, AnyFramework } from '@storybook/csf'; -import type { Story as StoryType } from '@storybook/store'; +import type { ModuleExport, ModuleExports, Story as StoryType } from '@storybook/store'; import { CURRENT_SELECTION } from './types'; import { DocsContext, DocsContextProps } from './DocsContext'; @@ -33,7 +33,8 @@ type StoryDefProps = { type StoryRefProps = { id?: string; - of?: any; + of?: ModuleExport; + meta?: ModuleExports; }; type StoryImportProps = { @@ -53,10 +54,10 @@ export const lookupStoryId = ( ); export const getStoryId = (props: StoryProps, context: DocsContextProps): StoryId => { - const { id, of } = props as StoryRefProps; + const { id, of, meta } = props as StoryRefProps; if (of) { - return context.storyIdByModuleExport(of); + return context.storyIdByModuleExport(of, meta); } const { name } = props as StoryDefProps; diff --git a/examples/external-docs/components/AccountForm.mdx b/examples/external-docs/components/AccountForm.mdx index 5cac7482c406..1048a938b871 100644 --- a/examples/external-docs/components/AccountForm.mdx +++ b/examples/external-docs/components/AccountForm.mdx @@ -1,8 +1,8 @@ import { Meta, Story } from '@storybook/addon-docs'; -import meta, { Standard } from './AccountForm.stories'; +import * as AccountFormStories from './AccountForm.stories'; ## Docs for Account form - + - + diff --git a/examples/external-docs/pages/AccountForm.mdx b/examples/external-docs/pages/AccountForm.mdx index d9110c81661b..d00ca2a3b55a 120000 --- a/examples/external-docs/pages/AccountForm.mdx +++ b/examples/external-docs/pages/AccountForm.mdx @@ -1 +1 @@ -../components/AccountForm.docs.mdx \ No newline at end of file +../components/AccountForm.mdx \ No newline at end of file diff --git a/examples/external-docs/pages/index.mdx b/examples/external-docs/pages/index.mdx index 67a89e6e4ac2..6e2b480410ab 100644 --- a/examples/external-docs/pages/index.mdx +++ b/examples/external-docs/pages/index.mdx @@ -1,19 +1,19 @@ import Callout from 'nextra-theme-docs/callout'; import { Title, Meta, Story, Canvas } from '@storybook/addon-docs'; -import meta, { Standard } from '../components/AccountForm.stories'; -import buttonMeta, { Basic } from '../components/button.stories'; +import * as AccountFormStories from '../components/AccountForm.stories'; +import * as ButtonStories from '../components/button.stories'; Embedded docs demo - + This is an example of an MDX file that embeds Doc Blocks and CSF stories. hahaha' }}> - + - + **MDX** (the library), at its core, transforms MDX (the syntax) to JSX. It receives an MDX string diff --git a/examples/react-ts/src/docs2/MetaOf.mdx b/examples/react-ts/src/docs2/MetaOf.mdx index 2f5a1c9d4277..6b62fd3f4d8d 100644 --- a/examples/react-ts/src/docs2/MetaOf.mdx +++ b/examples/react-ts/src/docs2/MetaOf.mdx @@ -1,10 +1,10 @@ import { Meta, Story } from '@storybook/addon-docs'; -import meta, { Basic } from '../button.stories'; +import * as ButtonStories from '../button.stories'; - + # Docs with of hello docs - + diff --git a/lib/core-server/src/utils/StoryIndexGenerator.ts b/lib/core-server/src/utils/StoryIndexGenerator.ts index cbe85dfea6e5..411df02c848d 100644 --- a/lib/core-server/src/utils/StoryIndexGenerator.ts +++ b/lib/core-server/src/utils/StoryIndexGenerator.ts @@ -115,7 +115,7 @@ export class StoryIndexGenerator { async ensureExtracted(): Promise { // First process all the story files. Then, in a second pass, // process the docs files. The reason for this is that the docs - // files may use the `` syntax, which requires + // files may use the `` syntax, which requires // that the story file that contains the meta be processed first. await this.updateExtracted(async (specifier, absolutePath) => this.isDocsMdx(absolutePath) ? false : this.extractStories(specifier, absolutePath) @@ -193,7 +193,7 @@ export class StoryIndexGenerator { // are invalidated. const dependencies = this.findDependencies(absoluteImports); - // Also, if `result.of` is set, it means that we're using the `` syntax, + // Also, if `result.of` is set, it means that we're using the `` syntax, // so find the `title` defined the file that `meta` points to. let ofTitle: string; if (result.of) { diff --git a/lib/preview-web/src/DocsRender.ts b/lib/preview-web/src/DocsRender.ts index ccf76ceaef21..e90bb037de11 100644 --- a/lib/preview-web/src/DocsRender.ts +++ b/lib/preview-web/src/DocsRender.ts @@ -1,5 +1,12 @@ import { AnyFramework, StoryId, ViewMode, StoryContextForLoaders } from '@storybook/csf'; -import { Story, StoryStore, CSFFile, ModuleExports, IndexEntry } from '@storybook/store'; +import { + Story, + StoryStore, + CSFFile, + ModuleExports, + IndexEntry, + ModuleExport, +} from '@storybook/store'; import { Channel } from '@storybook/addons'; import { DOCS_RENDERED } from '@storybook/core-events'; @@ -79,9 +86,6 @@ export class DocsRender implements Render), - - // These is intended for the external docs render only - setMeta: () => {}, }; if (this.legacy) { @@ -97,31 +101,44 @@ export class DocsRender implements Render this.store.storyFromCSFFile({ storyId, csfFile }), componentStories, + setMeta: () => {}, }; } + let metaCsfFile: ModuleExports; + const exportToStoryId = new Map(); + const storyIdToCSFFile = new Map>(); + // eslint-disable-next-line no-restricted-syntax + for (const csfFile of this.csfFiles) { + // eslint-disable-next-line no-restricted-syntax + for (const annotation of Object.values(csfFile.stories)) { + exportToStoryId.set(annotation.moduleExport, annotation.id); + storyIdToCSFFile.set(annotation.id, csfFile); + } + } + function storyById(storyId: StoryId) { + const csfFile = storyIdToCSFFile.get(storyId); + if (!csfFile) + throw new Error(`Called \`storyById\` for story that was never loaded: ${storyId}`); + return this.store.storyFromCSFFile({ storyId, csfFile }); + } + return { ...base, storyIdByModuleExport: (moduleExport) => { - // eslint-disable-next-line no-restricted-syntax - for (const csfFile of this.csfFiles) { - // eslint-disable-next-line no-restricted-syntax - for (const annotation of Object.values(csfFile.stories)) { - if (annotation.moduleExport === moduleExport) { - return annotation.id; - } - } - } + if (exportToStoryId.has(moduleExport)) return exportToStoryId.get(moduleExport); throw new Error(`No story found with that export: ${moduleExport}`); }, - storyById: () => { - throw new Error('`storyById` not available for modern docs files.'); - }, + storyById, componentStories: () => { - throw new Error( - 'You cannot render all the stories for a component in a (non-legacy) .mdx file' - ); + return Object.entries(metaCsfFile) + .map(([_, moduleExport]) => exportToStoryId.get(moduleExport)) + .filter(Boolean) + .map(storyById); + }, + setMeta(m: ModuleExports) { + metaCsfFile = m; }, }; } diff --git a/lib/preview-web/src/types.ts b/lib/preview-web/src/types.ts index 32f8d95b1dbd..2c94ca2d3236 100644 --- a/lib/preview-web/src/types.ts +++ b/lib/preview-web/src/types.ts @@ -6,7 +6,7 @@ import type { ComponentTitle, Parameters, } from '@storybook/csf'; -import type { Story } from '@storybook/store'; +import type { ModuleExport, ModuleExports, Story } from '@storybook/store'; import { PreviewWeb } from './PreviewWeb'; export interface DocsContextProps { @@ -16,7 +16,7 @@ export interface DocsContextProps StoryId; + storyIdByModuleExport: (storyExport: ModuleExport, metaExports?: ModuleExports) => StoryId; storyById: (id: StoryId) => Story; getStoryContext: (story: Story) => StoryContextForLoaders; @@ -36,7 +36,7 @@ export interface DocsContextProps void; + setMeta: (metaExport: ModuleExports) => void; } export type DocsRenderFunction = ( From 11568f79fc6fe4142f706ec52798044780f2f32d Mon Sep 17 00:00:00 2001 From: Tom Coleman Date: Mon, 20 Jun 2022 16:44:25 +1000 Subject: [PATCH 2/4] Use `` component in `MetaOf` --- addons/docs/src/blocks/DocsContainer.tsx | 24 ++++++++++++++---------- addons/docs/src/blocks/DocsRenderer.tsx | 9 +-------- examples/react-ts/src/docs2/MetaOf.mdx | 4 +++- lib/preview-web/src/DocsRender.ts | 4 ++-- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/addons/docs/src/blocks/DocsContainer.tsx b/addons/docs/src/blocks/DocsContainer.tsx index 66d904b1f5df..cb3738804daf 100644 --- a/addons/docs/src/blocks/DocsContainer.tsx +++ b/addons/docs/src/blocks/DocsContainer.tsx @@ -36,17 +36,21 @@ const warnOptionsTheme = deprecate( ); export const DocsContainer: FunctionComponent = ({ context, children }) => { - const { id: storyId, storyById } = context; - const { - parameters: { options = {}, docs = {} }, - } = storyById(storyId); - let themeVars = docs.theme; - if (!themeVars && options.theme) { - warnOptionsTheme(); - themeVars = options.theme; + const { id: storyId, type, storyById } = context; + const allComponents = { ...defaultComponents }; + let theme = ensureTheme(null); + if (type === 'legacy') { + const { + parameters: { options = {}, docs = {} }, + } = storyById(storyId); + let themeVars = docs.theme; + if (!themeVars && options.theme) { + warnOptionsTheme(); + themeVars = options.theme; + } + theme = ensureTheme(themeVars); + Object.assign(allComponents, docs.components); } - const theme = ensureTheme(themeVars); - const allComponents = { ...defaultComponents, ...docs.components }; useEffect(() => { let url; diff --git a/addons/docs/src/blocks/DocsRenderer.tsx b/addons/docs/src/blocks/DocsRenderer.tsx index 666c0630aeee..b467be250e82 100644 --- a/addons/docs/src/blocks/DocsRenderer.tsx +++ b/addons/docs/src/blocks/DocsRenderer.tsx @@ -33,15 +33,8 @@ async function renderDocsAsync( docsParameters: Parameters, element: HTMLElement ) { - // FIXME -- use DocsContainer, make it work for modern - const SimpleContainer = ({ children }: any) => ( - {children} - ); - const Container: ComponentType<{ context: DocsContextProps }> = - docsParameters.container || - (await docsParameters.getContainer?.()) || - (docsContext.type === 'legacy' ? DocsContainer : SimpleContainer); + docsParameters.container || (await docsParameters.getContainer?.()) || DocsContainer; const Page: ComponentType = docsParameters.page || (await docsParameters.getPage?.()) || DocsPage; diff --git a/examples/react-ts/src/docs2/MetaOf.mdx b/examples/react-ts/src/docs2/MetaOf.mdx index 6b62fd3f4d8d..f48a75b6bd58 100644 --- a/examples/react-ts/src/docs2/MetaOf.mdx +++ b/examples/react-ts/src/docs2/MetaOf.mdx @@ -1,4 +1,4 @@ -import { Meta, Story } from '@storybook/addon-docs'; +import { Meta, Story, Stories } from '@storybook/addon-docs'; import * as ButtonStories from '../button.stories'; @@ -8,3 +8,5 @@ import * as ButtonStories from '../button.stories'; hello docs + + diff --git a/lib/preview-web/src/DocsRender.ts b/lib/preview-web/src/DocsRender.ts index e90bb037de11..6e5ddee106d8 100644 --- a/lib/preview-web/src/DocsRender.ts +++ b/lib/preview-web/src/DocsRender.ts @@ -116,12 +116,12 @@ export class DocsRender implements Render { const csfFile = storyIdToCSFFile.get(storyId); if (!csfFile) throw new Error(`Called \`storyById\` for story that was never loaded: ${storyId}`); return this.store.storyFromCSFFile({ storyId, csfFile }); - } + }; return { ...base, From a6c6869265fc54d6cdfd534699e2f3a8336938d0 Mon Sep 17 00:00:00 2001 From: Tom Coleman Date: Mon, 20 Jun 2022 21:52:12 +1000 Subject: [PATCH 3/4] Fixes from CI --- addons/docs/src/blocks/DocsContainer.tsx | 1 + addons/docs/src/blocks/DocsRenderer.tsx | 2 +- .../docs/src/blocks/ExternalPreview.test.ts | 21 ++++++++++++------- addons/docs/src/blocks/ExternalPreview.ts | 2 +- .../stories/addon-docs/mdx.stories.js | 1 + 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/addons/docs/src/blocks/DocsContainer.tsx b/addons/docs/src/blocks/DocsContainer.tsx index cb3738804daf..758344c51f6f 100644 --- a/addons/docs/src/blocks/DocsContainer.tsx +++ b/addons/docs/src/blocks/DocsContainer.tsx @@ -39,6 +39,7 @@ export const DocsContainer: FunctionComponent = ({ context, const { id: storyId, type, storyById } = context; const allComponents = { ...defaultComponents }; let theme = ensureTheme(null); + console.log(context); if (type === 'legacy') { const { parameters: { options = {}, docs = {} }, diff --git a/addons/docs/src/blocks/DocsRenderer.tsx b/addons/docs/src/blocks/DocsRenderer.tsx index b467be250e82..23baac3d92f7 100644 --- a/addons/docs/src/blocks/DocsRenderer.tsx +++ b/addons/docs/src/blocks/DocsRenderer.tsx @@ -5,7 +5,7 @@ import { DocsRenderFunction } from '@storybook/preview-web'; import { DocsContainer } from './DocsContainer'; import { DocsPage } from './DocsPage'; -import { DocsContext, DocsContextProps } from './DocsContext'; +import { DocsContextProps } from './DocsContext'; export class DocsRenderer { public render: DocsRenderFunction; diff --git a/addons/docs/src/blocks/ExternalPreview.test.ts b/addons/docs/src/blocks/ExternalPreview.test.ts index 503241402f22..3dc820f1bcf9 100644 --- a/addons/docs/src/blocks/ExternalPreview.test.ts +++ b/addons/docs/src/blocks/ExternalPreview.test.ts @@ -1,3 +1,4 @@ +import { StoryId } from '@storybook/csf'; import { ExternalPreview } from './ExternalPreview'; const projectAnnotations = { render: jest.fn(), renderToDOM: jest.fn() }; @@ -18,7 +19,10 @@ describe('ExternalPreview', () => { it('handles csf files with titles', async () => { const preview = new ExternalPreview(projectAnnotations); - const storyId = preview.storyIdByModuleExport(csfFileWithTitle.one, csfFileWithTitle.default); + const storyId = preview.storyIdByModuleExport( + csfFileWithTitle.one, + csfFileWithTitle + ) as StoryId; const story = preview.storyById(storyId); expect(story).toMatchObject({ @@ -30,10 +34,13 @@ describe('ExternalPreview', () => { it('returns consistent story ids and objects', () => { const preview = new ExternalPreview(projectAnnotations); - const storyId = preview.storyIdByModuleExport(csfFileWithTitle.one, csfFileWithTitle.default); + const storyId = preview.storyIdByModuleExport( + csfFileWithTitle.one, + csfFileWithTitle + ) as StoryId; const story = preview.storyById(storyId); - expect(preview.storyIdByModuleExport(csfFileWithTitle.one, csfFileWithTitle.default)).toEqual( + expect(preview.storyIdByModuleExport(csfFileWithTitle.one, csfFileWithTitle)).toEqual( storyId ); expect(preview.storyById(storyId)).toBe(story); @@ -43,11 +50,11 @@ describe('ExternalPreview', () => { const preview = new ExternalPreview(projectAnnotations); preview.storyById( - preview.storyIdByModuleExport(csfFileWithTitle.one, csfFileWithTitle.default) + preview.storyIdByModuleExport(csfFileWithTitle.one, csfFileWithTitle) as StoryId ); const story = preview.storyById( - preview.storyIdByModuleExport(csfFileWithTitle.two, csfFileWithTitle.default) + preview.storyIdByModuleExport(csfFileWithTitle.two, csfFileWithTitle) as StoryId ); expect(story).toMatchObject({ title: 'Component', @@ -60,8 +67,8 @@ describe('ExternalPreview', () => { const storyId = preview.storyIdByModuleExport( csfFileWithoutTitle.one, - csfFileWithoutTitle.default - ); + csfFileWithoutTitle + ) as StoryId; const story = preview.storyById(storyId); expect(story).toMatchObject({ diff --git a/addons/docs/src/blocks/ExternalPreview.ts b/addons/docs/src/blocks/ExternalPreview.ts index 7b5874b0869d..87c39d165236 100644 --- a/addons/docs/src/blocks/ExternalPreview.ts +++ b/addons/docs/src/blocks/ExternalPreview.ts @@ -46,7 +46,7 @@ export class ExternalPreview extends Preview moduleExport === storyExport diff --git a/examples/official-storybook/stories/addon-docs/mdx.stories.js b/examples/official-storybook/stories/addon-docs/mdx.stories.js index c5c14efcd208..548ddd89f28a 100644 --- a/examples/official-storybook/stories/addon-docs/mdx.stories.js +++ b/examples/official-storybook/stories/addon-docs/mdx.stories.js @@ -34,6 +34,7 @@ DarkModeDocs.decorators = [ (storyFn) => ( [], storyById: () => ({ parameters: { docs: { theme: themes.dark } } }), }} From 3dae5e2eeb021bd8a39fa0d8d5235cd2863438bf Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Mon, 20 Jun 2022 20:22:21 +0800 Subject: [PATCH 4/4] Update addons/docs/src/blocks/DocsContainer.tsx --- addons/docs/src/blocks/DocsContainer.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/docs/src/blocks/DocsContainer.tsx b/addons/docs/src/blocks/DocsContainer.tsx index 758344c51f6f..cb3738804daf 100644 --- a/addons/docs/src/blocks/DocsContainer.tsx +++ b/addons/docs/src/blocks/DocsContainer.tsx @@ -39,7 +39,6 @@ export const DocsContainer: FunctionComponent = ({ context, const { id: storyId, type, storyById } = context; const allComponents = { ...defaultComponents }; let theme = ensureTheme(null); - console.log(context); if (type === 'legacy') { const { parameters: { options = {}, docs = {} },