Skip to content

Commit

Permalink
Merge pull request #24926 from storybookjs/tom/simplify-preview
Browse files Browse the repository at this point in the history
Core: Refactor preview and deprecate story store
  • Loading branch information
shilman authored Jan 15, 2024
2 parents 2334b1d + 4084f37 commit c356639
Show file tree
Hide file tree
Showing 21 changed files with 638 additions and 583 deletions.
12 changes: 12 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
- [Canvas Doc block properties](#canvas-doc-block-properties)
- [`Primary` Doc block properties](#primary-doc-block-properties)
- [`createChannel` from `@storybook/postmessage` and `@storybook/channel-websocket`](#createchannel-from-storybookpostmessage-and--storybookchannel-websocket)
- [StoryStore and methods deprecated](#storystore-and-methods-deprecated)
- [From version 7.5.0 to 7.6.0](#from-version-750-to-760)
- [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated)
- [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated)
Expand Down Expand Up @@ -978,6 +979,17 @@ The `createChannel` APIs from both `@storybook/channel-websocket` and `@storyboo

Additionally, the `PostmsgTransport` type is now removed in favor of `PostMessageTransport`.


#### StoryStore and methods deprecated

The StoryStore (`__STORYBOOK_STORY_STORE__` and `__STORYBOOK_PREVIEW__.storyStore`) are deprecated, and will no longer be accessible in Storybook 9.0.

In particular, the following methods on the `StoryStore` are deprecated and will be removed in 9.0:
- `store.fromId()` - please use `preview.loadStory({ storyId })` instead.
- `store.raw()` - please use `preview.extract()` instead.

Note that both these methods require initialization, so you should await `preview.ready()`.

## From version 7.5.0 to 7.6.0

#### CommonJS with Vite is deprecated
Expand Down
27 changes: 7 additions & 20 deletions code/addons/a11y/src/a11yRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,29 @@ import { addons } from '@storybook/preview-api';
import { EVENTS } from './constants';
import type { A11yParameters } from './params';

const { document, window: globalWindow } = global;
const { document } = global;

const channel = addons.getChannel();
// Holds axe core running state
let active = false;
// Holds latest story we requested a run
let activeStoryId: string | undefined;

const defaultParameters = { config: {}, options: {} };

/**
* Handle A11yContext events.
* Because the event are sent without manual check, we split calls
*/
const handleRequest = async (storyId: string) => {
const { manual } = await getParams(storyId);
if (!manual) {
await run(storyId);
const handleRequest = async (storyId: string, input: A11yParameters = defaultParameters) => {
if (!input?.manual) {
await run(storyId, input);
}
};

const run = async (storyId: string) => {
const run = async (storyId: string, input: A11yParameters = defaultParameters) => {
activeStoryId = storyId;
try {
const input = await getParams(storyId);

if (!active) {
active = true;
channel.emit(EVENTS.RUNNING);
Expand Down Expand Up @@ -69,17 +68,5 @@ const run = async (storyId: string) => {
}
};

/** Returns story parameters or default ones. */
const getParams = async (storyId: string): Promise<A11yParameters> => {
const { parameters } =
(await globalWindow.__STORYBOOK_STORY_STORE__.loadStory({ storyId })) || {};
return (
parameters.a11y || {
config: {},
options: {},
}
);
};

channel.on(EVENTS.REQUEST, handleRequest);
channel.on(EVENTS.MANUAL, run);
10 changes: 8 additions & 2 deletions code/addons/a11y/src/components/A11YPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { ActionBar, ScrollArea } from '@storybook/components';
import { SyncIcon, CheckIcon } from '@storybook/icons';

import type { AxeResults } from 'axe-core';
import { useChannel, useParameter, useStorybookState } from '@storybook/manager-api';
import {
useChannel,
useParameter,
useStorybookApi,
useStorybookState,
} from '@storybook/manager-api';

import { Report } from './Report';

Expand Down Expand Up @@ -59,6 +64,7 @@ export const A11YPanel: React.FC = () => {
const [error, setError] = React.useState<unknown>(undefined);
const { setResults, results } = useA11yContext();
const { storyId } = useStorybookState();
const api = useStorybookApi();

React.useEffect(() => {
setStatus(manual ? 'manual' : 'initial');
Expand Down Expand Up @@ -92,7 +98,7 @@ export const A11YPanel: React.FC = () => {

const handleManual = useCallback(() => {
setStatus('running');
emit(EVENTS.MANUAL, storyId);
emit(EVENTS.MANUAL, storyId, api.getParameters(storyId, 'a11y'));
}, [storyId]);

const manualActionItems = useMemo(
Expand Down
6 changes: 4 additions & 2 deletions code/addons/a11y/src/components/A11yContext.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ describe('A11YPanel', () => {
});

const getCurrentStoryData = vi.fn();
const getParameters = vi.fn();
beforeEach(() => {
mockedApi.useChannel.mockReset();
mockedApi.useStorybookApi.mockReset();
Expand All @@ -65,7 +66,8 @@ describe('A11YPanel', () => {
mockedApi.useAddonState.mockImplementation((_, defaultState) => React.useState(defaultState));
mockedApi.useChannel.mockReturnValue(vi.fn());
getCurrentStoryData.mockReset().mockReturnValue({ id: storyId, type: 'story' });
mockedApi.useStorybookApi.mockReturnValue({ getCurrentStoryData } as any);
getParameters.mockReturnValue({});
mockedApi.useStorybookApi.mockReturnValue({ getCurrentStoryData, getParameters } as any);
});

it('should render children', () => {
Expand Down Expand Up @@ -94,7 +96,7 @@ describe('A11YPanel', () => {
mockedApi.useChannel.mockReturnValue(emit);
const { rerender } = render(<A11yContextProvider active={false} />);
rerender(<A11yContextProvider active />);
expect(emit).toHaveBeenLastCalledWith(EVENTS.REQUEST, storyId);
expect(emit).toHaveBeenLastCalledWith(EVENTS.REQUEST, storyId, {});
});

it('should emit highlight with no values when inactive', () => {
Expand Down
2 changes: 1 addition & 1 deletion code/addons/a11y/src/components/A11yContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const A11yContextProvider: React.FC<React.PropsWithChildren<A11yContextPr
);
}, []);
const handleRun = (renderedStoryId: string) => {
emit(EVENTS.REQUEST, renderedStoryId);
emit(EVENTS.REQUEST, renderedStoryId, api.getParameters(renderedStoryId, 'a11y'));
};
const handleClearHighlights = React.useCallback(() => setHighlighted([]), []);
const handleSetTab = React.useCallback((index: number) => {
Expand Down
4 changes: 0 additions & 4 deletions code/addons/links/src/react/components/link.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ vi.mock('@storybook/global', () => ({
search: 'search',
},
},
window: global,
__STORYBOOK_STORY_STORE__: {
fromId: vi.fn(() => ({})),
},
},
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,9 @@ export async function generateModernIframeScriptCode(options: Options, projectRo
${getPreviewAnnotationsFunction}
window.__STORYBOOK_PREVIEW__ = window.__STORYBOOK_PREVIEW__ || new PreviewWeb();
window.__STORYBOOK_PREVIEW__ = window.__STORYBOOK_PREVIEW__ || new PreviewWeb(importFn, getProjectAnnotations);
window.__STORYBOOK_STORY_STORE__ = window.__STORYBOOK_STORY_STORE__ || window.__STORYBOOK_PREVIEW__.storyStore;
window.__STORYBOOK_PREVIEW__.initialize({ importFn, getProjectAnnotations });
${generateHMRHandler(frameworkName)};
`.trim();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@ if (global.CONFIG_TYPE === 'DEVELOPMENT'){
window.__STORYBOOK_SERVER_CHANNEL__ = channel;
}

const preview = new PreviewWeb();
const preview = new PreviewWeb(importFn, getProjectAnnotations);

window.__STORYBOOK_PREVIEW__ = preview;
window.__STORYBOOK_STORY_STORE__ = preview.storyStore;
window.__STORYBOOK_ADDONS_CHANNEL__ = channel;

preview.initialize({ importFn, getProjectAnnotations });

if (import.meta.webpackHot) {
import.meta.webpackHot.accept('./{{storiesFilename}}', () => {
// importFn has changed so we need to patch the new one in
Expand Down
161 changes: 161 additions & 0 deletions code/lib/core-events/src/errors/preview-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,164 @@ export class ImplicitActionsDuringRendering extends StorybookError {
`;
}
}

export class CalledExtractOnStoreError extends StorybookError {
readonly category = Category.PREVIEW_API;

readonly code = 3;

template() {
return dedent`
Cannot call \`storyStore.extract()\` without calling \`storyStore.cacheAllCsfFiles()\` first.
You probably meant to call \`await preview.extract()\` which does the above for you.`;
}
}

export class MissingRenderToCanvasError extends StorybookError {
readonly category = Category.PREVIEW_API;

readonly code = 4;

readonly documentation =
'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#mainjs-framework-field';

template() {
return dedent`
Expected your framework's preset to export a \`renderToCanvas\` field.
Perhaps it needs to be upgraded for Storybook 6.4?`;
}
}

export class CalledPreviewMethodBeforeInitializationError extends StorybookError {
readonly category = Category.PREVIEW_API;

readonly code = 5;

constructor(public data: { methodName: string }) {
super();
}

template() {
return dedent`
Called \`Preview.${this.data.methodName}()\` before initialization.
The preview needs to load the story index before most methods can be called. If you want
to call \`${this.data.methodName}\`, try \`await preview.initializationPromise;\` first.
If you didn't call the above code, then likely it was called by an addon that needs to
do the above.`;
}
}

export class StoryIndexFetchError extends StorybookError {
readonly category = Category.PREVIEW_API;

readonly code = 6;

constructor(public data: { text: string }) {
super();
}

template() {
return dedent`
Error fetching \`/index.json\`:
${this.data.text}
If you are in development, this likely indicates a problem with your Storybook process,
check the terminal for errors.
If you are in a deployed Storybook, there may have been an issue deploying the full Storybook
build.`;
}
}

export class MdxFileWithNoCsfReferencesError extends StorybookError {
readonly category = Category.PREVIEW_API;

readonly code = 7;

constructor(public data: { storyId: string }) {
super();
}

template() {
return dedent`
Tried to render docs entry ${this.data.storyId} but it is a MDX file that has no CSF
references, or autodocs for a CSF file that some doesn't refer to itself.
This likely is an internal error in Storybook's indexing, or you've attached the
\`attached-mdx\` tag to an MDX file that is not attached.`;
}
}

export class EmptyIndexError extends StorybookError {
readonly category = Category.PREVIEW_API;

readonly code = 8;

template() {
return dedent`
Couldn't find any stories in your Storybook.
- Please check your stories field of your main.js config: does it match correctly?
- Also check the browser console and terminal for error messages.`;
}
}

export class NoStoryMatchError extends StorybookError {
readonly category = Category.PREVIEW_API;

readonly code = 9;

constructor(public data: { storySpecifier: string }) {
super();
}

template() {
return dedent`
Couldn't find story matching '${this.data.storySpecifier}'.
- Are you sure a story with that id exists?
- Please check your stories field of your main.js config.
- Also check the browser console and terminal for error messages.`;
}
}

export class MissingStoryFromCsfFileError extends StorybookError {
readonly category = Category.PREVIEW_API;

readonly code = 10;

constructor(public data: { storyId: string }) {
super();
}

template() {
return dedent`
Couldn't find story matching id '${this.data.storyId}' after importing a CSF file.
The file was indexed as if the story was there, but then after importing the file in the browser
we didn't find the story. Possible reasons:
- You are using a custom story indexer that is misbehaving.
- You have a custom file loader that is removing or renaming exports.
Please check your browser console and terminal for errors that may explain the issue.`;
}
}

export class StoryStoreAccessedBeforeInitializationError extends StorybookError {
readonly category = Category.PREVIEW_API;

readonly code = 11;

template() {
return dedent`
Cannot access the Story Store until the index is ready.
It is not recommended to use methods directly on the Story Store anyway, in Storybook 9 we will
remove access to the store entirely`;
}
}
1 change: 1 addition & 0 deletions code/lib/preview-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"lodash": "^4.17.21",
"memoizerific": "^1.11.3",
"qs": "^6.10.0",
"tiny-invariant": "^1.3.1",
"ts-dedent": "^2.0.0",
"util-deprecate": "^1.0.2"
},
Expand Down
Loading

0 comments on commit c356639

Please sign in to comment.