Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Core, Server, Addon-docs: Replace story indexers with new API #23660

Merged
merged 17 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 32 additions & 19 deletions code/addons/docs/src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import remarkSlug from 'remark-slug';
import remarkExternalLinks from 'remark-external-links';
import { dedent } from 'ts-dedent';

import type { DocsOptions, Options, StorybookConfig, StoryIndexer } from '@storybook/types';
import type { DocsOptions, Options, StorybookConfig } from '@storybook/types';
import type { CsfPluginOptions } from '@storybook/csf-plugin';
import type { JSXOptions, CompileOptions } from '@storybook/mdx2-csf';
import { global } from '@storybook/global';
Expand Down Expand Up @@ -129,23 +129,36 @@ async function webpack(
return result;
}

const storyIndexers: StorybookConfig['storyIndexers'] = (existingIndexers) => {
const mdxIndexer: StoryIndexer['indexer'] = async (fileName, opts) => {
let code = (await fs.readFile(fileName, 'utf-8')).toString();
const { compile } = global.FEATURES?.legacyMdx1
? await import('@storybook/mdx1-csf')
: await import('@storybook/mdx2-csf');
code = await compile(code, {});
return loadCsf(code, { ...opts, fileName }).parse();
};
return [
{
test: /(stories|story)\.mdx$/,
indexer: mdxIndexer,
const indexers: StorybookConfig['indexers'] = (existingIndexers) => [
{
test: /(stories|story)\.mdx$/,
index: async (fileName, opts) => {
let code = (await fs.readFile(fileName, 'utf-8')).toString();
const { compile } = global.FEATURES?.legacyMdx1
? await import('@storybook/mdx1-csf')
: await import('@storybook/mdx2-csf');
code = await compile(code, {});
const csf = loadCsf(code, { ...opts, fileName }).parse();

// eslint-disable-next-line no-underscore-dangle
return Object.entries(csf._stories).map(([exportName, story]) => {
const docsOnly = story.parameters?.docsOnly;
const tags = (story.tags ?? csf.meta.tags ?? []).concat(docsOnly ? 'docsOnly' : []);
JReinhold marked this conversation as resolved.
Show resolved Hide resolved
return {
type: 'story',
importPath: fileName,
exportName,
name: story.name,
title: csf.meta.title,
metaId: csf.meta.id,
tags,
__id: story.id,
};
});
},
...(existingIndexers || []),
];
};
},
...(existingIndexers || []),
];

const docs = (docsOptions: DocsOptions) => {
return {
Expand All @@ -164,9 +177,9 @@ export const addons: StorybookConfig['addons'] = [
* something down the dependency chain is using typescript namespaces, which are not supported by rollup-plugin-dts
*/
const webpackX = webpack as any;
const storyIndexersX = storyIndexers as any;
const indexersX = indexers as any;
const docsX = docs as any;

ensureReactPeerDeps();

export { webpackX as webpack, storyIndexersX as storyIndexers, docsX as docs };
export { webpackX as webpack, indexersX as indexers, docsX as docs };
2 changes: 1 addition & 1 deletion code/lib/cli/src/generators/SERVER/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Generator } from '../types';

const generator: Generator = async (packageManager, npmOptions, options) => {
await baseGenerator(packageManager, npmOptions, options, 'server', {
extensions: ['json'],
extensions: ['json', 'yaml', 'yml'],
});
};

Expand Down
35 changes: 21 additions & 14 deletions code/lib/core-server/src/presets/common-preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ import type {
Options,
PresetPropertyFn,
StorybookConfig,
StoryIndexer,
} from '@storybook/types';
import { loadCsf, printConfig, readConfig } from '@storybook/csf-tools';
import { printConfig, readConfig, readCsf } from '@storybook/csf-tools';
import { join } from 'path';
import { dedent } from 'ts-dedent';
import fetch from 'node-fetch';
Expand Down Expand Up @@ -194,19 +193,27 @@ export const features = async (
legacyDecoratorFileOrder: false,
});

export const storyIndexers: StorybookConfig['storyIndexers'] = async (existingIndexers) => {
const csfIndexer: StoryIndexer['indexer'] = async (fileName, opts) => {
const code = (await readFile(fileName, 'utf-8')).toString();
return loadCsf(code, { ...opts, fileName }).parse();
};
return [
{
test: /(stories|story)\.(m?js|ts)x?$/,
indexer: csfIndexer,
export const indexers: StorybookConfig['indexers'] = (existingIndexers) => [
{
test: /\.stories\.(m?js|ts)x?$/,
index: async (fileName, options) => {
const csf = (await readCsf(fileName, options)).parse();

// eslint-disable-next-line no-underscore-dangle
return Object.entries(csf._stories).map(([exportName, story]) => ({
type: 'story',
importPath: fileName,
exportName,
name: story.name,
title: csf.meta.title,
metaId: csf.meta.id,
tags: story.tags ?? csf.meta.tags,
__id: story.id,
}));
},
...(existingIndexers || []),
];
};
},
...(existingIndexers || []),
];

export const frameworkOptions = async (
_: never,
Expand Down
24 changes: 14 additions & 10 deletions code/lib/core-server/src/utils/StoryIndexGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,20 @@ const storiesMdxIndexer: Indexer = {
const csf = loadCsf(code, { ...opts, fileName }).parse();

// eslint-disable-next-line no-underscore-dangle
return Object.entries(csf._stories).map(([exportName, story]) => ({
type: 'story',
importPath: fileName,
exportName,
name: story.name,
title: csf.meta.title,
metaId: csf.meta.id,
tags: story.tags ?? csf.meta.tags,
__id: story.id,
}));
return Object.entries(csf._stories).map(([exportName, story]) => {
const docsOnly = story.parameters?.docsOnly;
const tags = (story.tags ?? csf.meta.tags ?? []).concat(docsOnly ? 'docsOnly' : []);
return {
type: 'story',
importPath: fileName,
exportName,
name: story.name,
title: csf.meta.title,
metaId: csf.meta.id,
tags,
__id: story.id,
};
});
JReinhold marked this conversation as resolved.
Show resolved Hide resolved
},
};

Expand Down
6 changes: 5 additions & 1 deletion code/lib/core-server/src/utils/StoryIndexGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,12 @@ export class StoryIndexGenerator {
});
}

const entriesWithoutDocsOnlyStories = entries.filter(
(entry) => !(entry.type === 'story' && entry.tags.includes('docsOnly'))
JReinhold marked this conversation as resolved.
Show resolved Hide resolved
);

return {
entries,
entries: entriesWithoutDocsOnlyStories,
dependents: [],
type: 'stories',
};
Expand Down
49 changes: 49 additions & 0 deletions code/lib/core-server/src/utils/__tests__/index-extraction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -591,4 +591,53 @@ describe('docs entries from story extraction', () => {
}
`);
});
it(`Only adds a docs entry and not a story entry when an input has the "docsOnly" tag`, async () => {
const relativePath = './src/nested/Page.stories.mdx';
const absolutePath = path.join(options.workingDir, relativePath);
const specifier: NormalizedStoriesSpecifier = normalizeStoriesEntry(relativePath, options);

const generator = new StoryIndexGenerator([specifier], {
...options,
docs: { defaultName: 'docs', autodocs: false },
indexers: [
{
test: /\.stories\.mdx?$/,
index: async (fileName) => [
{
exportName: '__page',
__id: 'page--page',
name: 'Page',
title: 'Page',
tags: [STORIES_MDX_TAG, 'docsOnly'],
importPath: fileName,
type: 'story',
},
],
},
],
});
const result = await generator.extractStories(specifier, absolutePath);

expect(result).toMatchInlineSnapshot(`
Object {
"dependents": Array [],
"entries": Array [
Object {
"id": "page--docs",
"importPath": "./src/nested/Page.stories.mdx",
"name": "docs",
"storiesImports": Array [],
"tags": Array [
"stories-mdx",
"docsOnly",
"docs",
],
"title": "Page",
"type": "docs",
},
],
"type": "stories",
}
`);
});
});
24 changes: 14 additions & 10 deletions code/lib/core-server/src/utils/stories-json.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,20 @@ const storiesMdxIndexer: Indexer = {
const csf = loadCsf(code, { ...opts, fileName }).parse();

// eslint-disable-next-line no-underscore-dangle
return Object.entries(csf._stories).map(([exportName, story]) => ({
exportName,
__id: story.id,
metaId: csf.meta.id,
name: story.name,
title: csf.meta.title,
importPath: fileName,
type: 'story',
tags: story.tags ?? csf.meta.tags,
}));
return Object.entries(csf._stories).map(([exportName, story]) => {
const docsOnly = story.parameters?.docsOnly;
const tags = (story.tags ?? csf.meta.tags ?? []).concat(docsOnly ? 'docsOnly' : []);
return {
type: 'story',
importPath: fileName,
exportName,
name: story.name,
title: csf.meta.title,
metaId: csf.meta.id,
tags,
__id: story.id,
};
});
},
};

Expand Down
58 changes: 26 additions & 32 deletions code/renderers/server/src/preset.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,30 @@
import fs from 'fs-extra';
import yaml from 'yaml';
import { toId } from '@storybook/csf';
import type { StaticMeta } from '@storybook/csf-tools';
import type { IndexedStory, StoryIndexer, StorybookConfig } from '@storybook/types';
import type { StorybookConfig, Tag, StoryName, ComponentTitle } from '@storybook/types';

export const storyIndexers: StorybookConfig['storyIndexers'] = (existingIndexers) => {
const serverIndexer: StoryIndexer['indexer'] = async (fileName) => {
const json = fileName.endsWith('.json')
? await fs.readJson(fileName, 'utf-8')
: yaml.parse((await fs.readFile(fileName, 'utf-8')).toString());
const meta: StaticMeta = {
title: json.title,
};
const stories: IndexedStory[] = json.stories.map((story: { name: string }) => {
const id = toId(meta.title, story.name);
const { name } = story;
const indexedStory: IndexedStory = {
id,
name,
};
return indexedStory;
});
return {
meta,
stories,
};
};
return [
{
test: /(stories|story)\.(json|ya?ml)$/,
indexer: serverIndexer,
},
...(existingIndexers || []),
];
type FileContent = {
title: ComponentTitle;
tags?: Tag[];
stories: { name: StoryName; tags?: Tag[] }[];
};

export const indexers: StorybookConfig['indexers'] = (existingIndexers) => [
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
{
test: /(stories|story)\.(json|ya?ml)$/,
index: async (fileName) => {
const content: FileContent = fileName.endsWith('.json')
? await fs.readJson(fileName, 'utf-8')
: yaml.parse((await fs.readFile(fileName, 'utf-8')).toString());

return content.stories.map((story) => ({
importPath: fileName,
exportName: story.name,
name: story.name,
title: content.title,
tags: content.tags ?? story.tags ?? [],
type: 'story',
}));
},
},
...(existingIndexers || []),
];
1 change: 1 addition & 0 deletions code/renderers/server/template/cli/button.stories.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"control": { "type": "select", "options": ["small", "medium", "large"] }
}
},
"tags": ["autodocs"],
"stories": [
{
"name": "Primary",
Expand Down
18 changes: 0 additions & 18 deletions code/renderers/server/template/cli/page.stories.json

This file was deleted.

10 changes: 10 additions & 0 deletions code/renderers/server/template/cli/page.stories.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
title: "Example/Page"
parameters:
server:
url: "https://storybook-server-demo.netlify.app/api"
id: "page"
stories:
- name: "LoggedIn"
args:
user: {}
- name: "LoggedOut"
17 changes: 14 additions & 3 deletions docs/snippets/common/storybook-main-csf-indexer.ts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,21 @@ import { readFileSync } from 'fs';
import { loadCsf } from '@storybook/csf-tools';

export default {
storyIndexers = (existingIndexers) => {
const indexer = async (fileName, opts) => {
indexers = (existingIndexers) => {
const index = async (fileName, opts) => {
const code = readFileSync(fileName, { encoding: 'utf-8' });
return loadCsf(code, { ...opts, fileName }).parse();
const csf = (await loadCsf(code, { ...opts, fileName })).parse();

return Object.entries(csf._stories).map(([exportName, story]) => ({
type: 'story',
importPath: fileName,
exportName,
name: story.name,
title: csf.meta.title,
metaId: csf.meta.id,
tags: story.tags ?? csf.meta.tags,
__id: story.id,
}));
};

return [
JReinhold marked this conversation as resolved.
Show resolved Hide resolved
Expand Down