Merge pull request #19712 from storybookjs/tom/sb-882-use-mdx-tag-instead-of-matching-on

Core: Update index generation to use tags to detect MDX stories
This commit is contained in:
Michael Shilman 2022-11-07 09:30:47 +08:00 committed by GitHub
commit 01ee7b5f5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 141 additions and 68 deletions

View File

@ -2,11 +2,11 @@ const path = require('path');
const { ScriptTransformer } = require('@jest/transform');
const { dedent } = require('ts-dedent');
const { compileAsync } = require('@storybook/mdx2-csf');
const { compile } = require('@storybook/mdx2-csf');
module.exports = {
async processAsync(src, filename, config, { instrument }) {
const code = await compileAsync(src, { skipCsf: false });
const code = await compile(src, { skipCsf: false });
const result = dedent`
/* @jsx mdx */
import React from 'react'

View File

@ -160,10 +160,9 @@ export async function webpack(
return result;
}
export const storyIndexers = async (indexers: CoreCommon_StoryIndexer[] | null) => {
export const storyIndexers = (indexers: CoreCommon_StoryIndexer[] | null) => {
const mdxIndexer = async (fileName: string, opts: CoreCommon_IndexerOptions) => {
let code = (await fs.readFile(fileName, 'utf-8')).toString();
// @ts-expect-error (Converted from ts-ignore)
const { compile } = await import('@storybook/mdx2-csf');
code = await compile(code, {});
return loadCsf(code, { ...opts, fileName }).parse();
@ -172,7 +171,6 @@ export const storyIndexers = async (indexers: CoreCommon_StoryIndexer[] | null)
{
test: /(stories|story)\.mdx$/,
indexer: mdxIndexer,
addDocsTemplate: true,
},
...(indexers || []),
];

View File

@ -1,4 +1,4 @@
import { Meta, Story, Canvas } from '@storybook/addon-docs';
import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import globalThis from 'global';
import * as Csf from './csf-in-mdx.stories.js';

View File

@ -44,7 +44,6 @@ export function mdxPlugin(): Plugin {
async transform(src, id, options) {
if (!filter(id)) return undefined;
// @ts-expect-error typescript doesn't think compile exists, but it does.
const { compile } = await import('@storybook/mdx2-csf');
const mdxCode = String(await compile(src, { skipCsf: !isStorybookMdx(id) }));

View File

@ -195,8 +195,7 @@ export class StoryStoreFacade<TFramework extends AnyFramework> {
docsOptions.docsPage === 'automatic' ||
(docsOptions.docsPage && componentTags.includes('docsPage'));
if (docsOptions.enabled && storyExports.length) {
// We will use tags soon and this crappy filename test will go away
if (fileName.match(/\.mdx$/) || docsPageOptedIn) {
if (componentTags.includes('mdx') || docsPageOptedIn) {
const name = docsOptions.defaultName;
const docsId = toId(componentId || title, name);
this.entries[docsId] = {

View File

@ -961,7 +961,7 @@ describe('start', () => {
'test',
makeRequireContext({
'./Introduction.stories.mdx': {
default: { title: 'Introduction' },
default: { title: 'Introduction', tags: ['mdx'] },
_Page: { name: 'Page', parameters: { docsOnly: true } },
},
})
@ -979,6 +979,7 @@ describe('start', () => {
"standalone": false,
"storiesImports": Array [],
"tags": Array [
"mdx",
"docs",
],
"title": "Introduction",

View File

@ -33,7 +33,8 @@ jest.mock('@storybook/docs-mdx', async () => ({
const name = content.match(/name=['"](.*)['"]/)?.[1];
const ofMatch = content.match(/of=\{(.*)\}/)?.[1];
const isTemplate = content.match(/isTemplate/);
return { title, name, imports, of: ofMatch && imports.length && imports[0], isTemplate };
const tags = ['mdx'];
return { title, name, tags, imports, of: ofMatch && imports.length && imports[0], isTemplate };
},
}));
@ -50,12 +51,20 @@ const csfIndexer = async (fileName: string, opts: any) => {
return loadCsf(code, { ...opts, fileName }).parse();
};
const storiesMdxIndexer = async (fileName: string, opts: any) => {
let code = (await fs.readFile(fileName, 'utf-8')).toString();
const { compile } = await import('@storybook/mdx2-csf');
code = await compile(code, {});
return loadCsf(code, { ...opts, fileName }).parse();
};
const options = {
configDir: path.join(__dirname, '__mockdata__'),
workingDir: path.join(__dirname, '__mockdata__'),
storyIndexers: [
{ test: /\.stories\..*$/, indexer: csfIndexer as any as CoreCommon_StoryIndexer['indexer'] },
],
{ test: /\.stories\.mdx$/, indexer: storiesMdxIndexer },
{ test: /\.stories\.(js|ts)x?$/, indexer: csfIndexer },
] as CoreCommon_StoryIndexer[],
storiesV2Compatibility: false,
storyStoreV7: true,
docs: { enabled: true, defaultName: 'docs', docsPage: false },
@ -232,47 +241,43 @@ describe('StoryIndexGenerator', () => {
});
});
describe('addDocsTemplate indexer', () => {
const templateIndexer = { ...options.storyIndexers[0], addDocsTemplate: true };
describe('mdx tagged components', () => {
it('adds docs entry with docs enabled', async () => {
const specifier: CoreCommon_NormalizedStoriesSpecifier = normalizeStoriesEntry(
'./src/A.stories.js',
'./src/nested/Page.stories.mdx',
options
);
const generator = new StoryIndexGenerator([specifier], {
...options,
storyIndexers: [templateIndexer],
});
await generator.initialize();
expect(await generator.getIndex()).toMatchInlineSnapshot(`
Object {
"entries": Object {
"a--docs": Object {
"id": "a--docs",
"importPath": "./src/A.stories.js",
"page--docs": Object {
"id": "page--docs",
"importPath": "./src/nested/Page.stories.mdx",
"name": "docs",
"standalone": false,
"storiesImports": Array [],
"tags": Array [
"component-tag",
"docsPage",
"mdx",
"docs",
],
"title": "A",
"title": "Page",
"type": "docs",
},
"a--story-one": Object {
"id": "a--story-one",
"importPath": "./src/A.stories.js",
"name": "Story One",
"page--story-one": Object {
"id": "page--story-one",
"importPath": "./src/nested/Page.stories.mdx",
"name": "StoryOne",
"tags": Array [
"story-tag",
"mdx",
"story",
],
"title": "A",
"title": "Page",
"type": "story",
},
},
@ -288,7 +293,6 @@ describe('StoryIndexGenerator', () => {
const generator = new StoryIndexGenerator([specifier], {
...options,
storyIndexers: [templateIndexer],
docs: { enabled: false },
});
await generator.initialize();
@ -500,6 +504,7 @@ describe('StoryIndexGenerator', () => {
"./src/A.stories.js",
],
"tags": Array [
"mdx",
"docs",
],
"title": "A",
@ -613,6 +618,7 @@ describe('StoryIndexGenerator', () => {
"./src/A.stories.js",
],
"tags": Array [
"mdx",
"docs",
],
"title": "A",
@ -627,6 +633,7 @@ describe('StoryIndexGenerator', () => {
"./src/A.stories.js",
],
"tags": Array [
"mdx",
"docs",
],
"title": "A",
@ -650,6 +657,7 @@ describe('StoryIndexGenerator', () => {
"standalone": true,
"storiesImports": Array [],
"tags": Array [
"mdx",
"docs",
],
"title": "docs2/Yabbadabbadooo",
@ -662,6 +670,7 @@ describe('StoryIndexGenerator', () => {
"standalone": true,
"storiesImports": Array [],
"tags": Array [
"mdx",
"docs",
],
"title": "NoTitle",
@ -752,6 +761,7 @@ describe('StoryIndexGenerator', () => {
"./src/A.stories.js",
],
"tags": Array [
"mdx",
"docs",
],
"title": "A",
@ -766,6 +776,7 @@ describe('StoryIndexGenerator', () => {
"./src/A.stories.js",
],
"tags": Array [
"mdx",
"docs",
],
"title": "A",
@ -789,6 +800,7 @@ describe('StoryIndexGenerator', () => {
"standalone": true,
"storiesImports": Array [],
"tags": Array [
"mdx",
"docs",
],
"title": "docs2/Yabbadabbadooo",
@ -801,6 +813,7 @@ describe('StoryIndexGenerator', () => {
"standalone": true,
"storiesImports": Array [],
"tags": Array [
"mdx",
"docs",
],
"title": "NoTitle",

View File

@ -227,9 +227,10 @@ export class StoryIndexGenerator {
const { docsPage } = this.options.docs;
const docsPageOptedIn =
docsPage === 'automatic' || (docsPage && componentTags.includes('docsPage'));
// We always add a template for *.stories.mdx, but only if docs page is enabled for
// regular CSF files
if (storyIndexer.addDocsTemplate || docsPageOptedIn) {
// We need a docs entry attached to the CSF file if either:
// a) it is a stories.mdx transpiled to CSF, OR
// b) we have docs page enabled for this file
if (componentTags.includes('mdx') || docsPageOptedIn) {
const name = this.options.docs.defaultName;
const id = toId(csf.meta.title, name);
entries.unshift({

View File

@ -1,5 +1,5 @@
import { Meta, Story } from '@storybook/addon-docs';
<Meta component={{}} />;
<Meta component={{}} />
<Story name="StoryOne" />

View File

@ -1,10 +1,12 @@
/// <reference types="@types/jest" />;
import fs from 'fs-extra';
import type { Router, Request, Response } from 'express';
import Watchpack from 'watchpack';
import path from 'path';
import debounce from 'lodash/debounce';
import { STORY_INDEX_INVALIDATED } from '@storybook/core-events';
import type { StoryIndex } from '@storybook/store';
import type { Store_StoryIndex, CoreCommon_StoryIndexer } from '@storybook/types';
import { loadCsf } from '@storybook/csf-tools';
import { normalizeStoriesEntry } from '@storybook/core-common';
@ -51,12 +53,22 @@ const csfIndexer = async (fileName: string, opts: any) => {
return loadCsf(code, { ...opts, fileName }).parse();
};
const storiesMdxIndexer = async (fileName: string, opts: any) => {
let code = (await fs.readFile(fileName, 'utf-8')).toString();
const { compile } = await import('@storybook/mdx2-csf');
code = await compile(code, {});
return loadCsf(code, { ...opts, fileName }).parse();
};
const getInitializedStoryIndexGenerator = async (
overrides: any = {},
inputNormalizedStories = normalizedStories
) => {
const generator = new StoryIndexGenerator(inputNormalizedStories, {
storyIndexers: [{ test: /\.stories\..*$/, indexer: csfIndexer }],
storyIndexers: [
{ test: /\.stories\.mdx$/, indexer: storiesMdxIndexer },
{ test: /\.stories\.(js|ts)x?$/, indexer: csfIndexer },
] as CoreCommon_StoryIndexer[],
configDir: workingDir,
workingDir,
storiesV2Compatibility: false,
@ -220,6 +232,30 @@ describe('useStoriesJson', () => {
"title": "nested/Button",
"type": "story",
},
"nested-page--docs": Object {
"id": "nested-page--docs",
"importPath": "./src/nested/Page.stories.mdx",
"name": "docs",
"standalone": false,
"storiesImports": Array [],
"tags": Array [
"mdx",
"docs",
],
"title": "nested/Page",
"type": "docs",
},
"nested-page--story-one": Object {
"id": "nested-page--story-one",
"importPath": "./src/nested/Page.stories.mdx",
"name": "StoryOne",
"tags": Array [
"mdx",
"story",
],
"title": "nested/Page",
"type": "story",
},
"second-nested-g--story-one": Object {
"id": "second-nested-g--story-one",
"importPath": "./src/second-nested/G.stories.ts",
@ -413,6 +449,42 @@ describe('useStoriesJson', () => {
],
"title": "nested/Button",
},
"nested-page--docs": Object {
"id": "nested-page--docs",
"importPath": "./src/nested/Page.stories.mdx",
"kind": "nested/Page",
"name": "docs",
"parameters": Object {
"__id": "nested-page--docs",
"docsOnly": true,
"fileName": "./src/nested/Page.stories.mdx",
},
"standalone": false,
"storiesImports": Array [],
"story": "docs",
"tags": Array [
"mdx",
"docs",
],
"title": "nested/Page",
},
"nested-page--story-one": Object {
"id": "nested-page--story-one",
"importPath": "./src/nested/Page.stories.mdx",
"kind": "nested/Page",
"name": "StoryOne",
"parameters": Object {
"__id": "nested-page--story-one",
"docsOnly": false,
"fileName": "./src/nested/Page.stories.mdx",
},
"story": "StoryOne",
"tags": Array [
"mdx",
"story",
],
"title": "nested/Page",
},
"second-nested-g--story-one": Object {
"id": "second-nested-g--story-one",
"importPath": "./src/second-nested/G.stories.ts",
@ -540,6 +612,23 @@ describe('useStoriesJson', () => {
],
"title": "nested/Button",
},
"nested-page--story-one": Object {
"id": "nested-page--story-one",
"importPath": "./src/nested/Page.stories.mdx",
"kind": "nested/Page",
"name": "StoryOne",
"parameters": Object {
"__id": "nested-page--story-one",
"docsOnly": false,
"fileName": "./src/nested/Page.stories.mdx",
},
"story": "StoryOne",
"tags": Array [
"mdx",
"story",
],
"title": "nested/Page",
},
"second-nested-g--story-one": Object {
"id": "second-nested-g--story-one",
"importPath": "./src/second-nested/G.stories.ts",
@ -852,7 +941,7 @@ describe('useStoriesJson', () => {
describe('convertToIndexV3', () => {
it('converts v7 index.json to v6 stories.json', () => {
const indexJson: StoryIndex = {
const indexJson: Store_StoryIndex = {
v: 4,
entries: {
'a--docs': {

View File

@ -220,7 +220,6 @@ export interface CoreCommon_StoryIndex {
export interface CoreCommon_StoryIndexer {
test: RegExp;
indexer: (fileName: string, options: CoreCommon_IndexerOptions) => Promise<CoreCommon_StoryIndex>;
addDocsTemplate?: boolean;
}
/**

View File

@ -2159,17 +2159,6 @@ __metadata:
languageName: node
linkType: hard
"@babel/types@npm:^7.14.8":
version: 7.20.0
resolution: "@babel/types@npm:7.20.0"
dependencies:
"@babel/helper-string-parser": ^7.19.4
"@babel/helper-validator-identifier": ^7.19.1
to-fast-properties: ^2.0.0
checksum: 8b9c960eb013142eaf6294d77b75e469b7e97461bd7ad939e625ed74865fbf5a1c20b7989ec3357d0f4ffd93dd79d6daead08c0c06647815d8bbe94dae445f5c
languageName: node
linkType: hard
"@base2/pretty-print-object@npm:1.0.1":
version: 1.0.1
resolution: "@base2/pretty-print-object@npm:1.0.1"
@ -6935,19 +6924,11 @@ __metadata:
linkType: soft
"@storybook/mdx2-csf@npm:next":
version: 0.1.0-next.3
resolution: "@storybook/mdx2-csf@npm:0.1.0-next.3"
version: 0.1.0-next.4
resolution: "@storybook/mdx2-csf@npm:0.1.0-next.4"
dependencies:
"@babel/generator": ^7.12.11
"@babel/parser": ^7.12.11
"@babel/types": ^7.14.8
"@mdx-js/mdx": ^2.0.0
estree-to-babel: ^4.9.0
hast-util-to-estree: ^2.0.2
js-string-escape: ^1.0.1
loader-utils: ^2.0.0
lodash: ^4.17.21
checksum: b8cf49e6549507b0e176191ce58f2fb9ab152383e54c73d665414bd5013dc0b90d3cddc814e396563e6af7164b762f75a6a488091f2b46aa33b3fcbd89a12e70
checksum: 206f3fea03db7aad37873653e7c98cdda28c27253eef26df5e5cff7943770c404af839a250c6e9035604670362810847e7103a5834af0632cfd16576dd386c1f
languageName: node
linkType: hard
@ -22895,13 +22876,6 @@ __metadata:
languageName: node
linkType: hard
"js-string-escape@npm:^1.0.1":
version: 1.0.1
resolution: "js-string-escape@npm:1.0.1"
checksum: 2c33b9ff1ba6b84681c51ca0997e7d5a1639813c95d5b61cb7ad47e55cc28fa4a0b1935c3d218710d8e6bcee5d0cd8c44755231e3a4e45fc604534d9595a3628
languageName: node
linkType: hard
"js-stringify@npm:^1.0.2":
version: 1.0.2
resolution: "js-stringify@npm:1.0.2"