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

Addon-docs: Skip dynamic source rendering when not needed #11640

Merged
merged 3 commits into from
Jul 22, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 1 addition & 23 deletions addons/docs/src/blocks/Source.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,10 @@ import { logger } from '@storybook/client-logger';
import { DocsContext, DocsContextProps } from './DocsContext';
import { SourceContext, SourceContextProps } from './SourceContainer';
import { CURRENT_SELECTION } from './types';
import { SourceType } from '../shared';

import { enhanceSource } from './enhanceSource';

enum SourceType {
/**
* AUTO is the default
*
* Use the CODE logic if:
* - the user has set a custom source snippet in `docs.source.code` story parameter
* - the story is not an args-based story
*
* Use the DYNAMIC rendered snippet if the story is an args story
*/
AUTO = 'auto',

/**
* Render the code extracted by source-loader
*/
CODE = 'code',

/**
* Render dynamically-rendered source snippet from the story's virtual DOM (currently React only)
*/
DYNAMIC = 'dynamic',
}

interface CommonProps {
language?: string;
dark?: boolean;
Expand Down
44 changes: 43 additions & 1 deletion addons/docs/src/frameworks/react/jsxDecorator.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
import React from 'react';
import range from 'lodash/range';
import { renderJsx } from './jsxDecorator';
import addons, { StoryContext } from '@storybook/addons';
import { renderJsx, jsxDecorator } from './jsxDecorator';
import { SNIPPET_RENDERED } from '../../shared';

jest.mock('@storybook/addons');
const mockedAddons = addons as jest.Mocked<typeof addons>;

expect.addSnapshotSerializer({
print: (val: any) => val,
Expand Down Expand Up @@ -88,3 +93,40 @@ describe('renderJsx', () => {
`);
});
});

// @ts-ignore
const makeContext = (name: string, parameters: any, args: any): StoryContext => ({
id: `jsx-test--${name}`,
kind: 'js-text',
name,
parameters,
args,
});

describe('jsxDecorator', () => {
let mockChannel: { on: jest.Mock; emit?: jest.Mock };
beforeEach(() => {
mockedAddons.getChannel.mockReset();

mockChannel = { on: jest.fn(), emit: jest.fn() };
mockedAddons.getChannel.mockReturnValue(mockChannel as any);
});

it('should render dynamically for args stories', () => {
const storyFn = (args: any) => <div>args story</div>;
const context = makeContext('args', { __isArgsStory: true }, {});
jsxDecorator(storyFn, context);
expect(mockChannel.emit).toHaveBeenCalledWith(
SNIPPET_RENDERED,
'jsx-test--args',
'<div>\n args story\n</div>'
);
});

it('should skip dynamic rendering for no-args stories', () => {
const storyFn = () => <div>classic story</div>;
const context = makeContext('classic', {}, {});
jsxDecorator(storyFn, context);
expect(mockChannel.emit).not.toHaveBeenCalled();
});
});
31 changes: 24 additions & 7 deletions addons/docs/src/frameworks/react/jsxDecorator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ import reactElementToJSXString, { Options } from 'react-element-to-jsx-string';
import { addons, StoryContext } from '@storybook/addons';
import { logger } from '@storybook/client-logger';

import { SNIPPET_RENDERED } from '../../shared';

type VueComponent = {
/** The template for the Vue component */
template?: string;
};
import { SourceType, SNIPPET_RENDERED } from '../../shared';

interface JSXOptions {
/** How many wrappers to skip when rendering the jsx */
Expand Down Expand Up @@ -100,8 +95,30 @@ const defaultOpts = {
enableBeautify: true,
};

export const skipJsxRender = (context: StoryContext) => {
const { parameters } = context;
const sourceParams = parameters.docs?.source;

// always render if the user forces it
if (sourceParams?.type === SourceType.DYNAMIC) {
return false;
}

// never render if the user is forcing the block to render code, or
// if the user provides code, or if it's not an args story.

// eslint-disable-next-line no-underscore-dangle
return !parameters.__isArgsStory || sourceParams?.code || sourceParams?.type === SourceType.CODE;
};

export const jsxDecorator = (storyFn: any, context: StoryContext) => {
const story: ReturnType<typeof storyFn> & VueComponent = storyFn();
const story = storyFn();

// We only need to render JSX if the source block is actually going to
// consume it. Otherwise it's just slowing us down.
if (skipJsxRender(context)) {
return story;
}

const channel = addons.getChannel();

Expand Down
23 changes: 23 additions & 0 deletions addons/docs/src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,26 @@ export const PANEL_ID = `${ADDON_ID}/panel`;
export const PARAM_KEY = `docs`;

export const SNIPPET_RENDERED = `${ADDON_ID}/snippet-rendered`;

export enum SourceType {
/**
* AUTO is the default
*
* Use the CODE logic if:
* - the user has set a custom source snippet in `docs.source.code` story parameter
* - the story is not an args-based story
*
* Use the DYNAMIC rendered snippet if the story is an args story
*/
AUTO = 'auto',

/**
* Render the code extracted by source-loader
*/
CODE = 'code',

/**
* Render dynamically-rendered source snippet from the story's virtual DOM (currently React only)
*/
DYNAMIC = 'dynamic',
}