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: Refactor preview and deprecate story store #24926

Merged
merged 26 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
91c5882
Move preview init parameters to constructor
tmeasday Nov 21, 2023
0cf9b38
Re-enable tests
tmeasday Nov 21, 2023
bea1072
Move initialization promise to Preview and simplify store
tmeasday Nov 21, 2023
166599a
Deprecate `fromId`/`raw`
tmeasday Nov 22, 2023
9d813a0
Refactored Preview + Store errors to new format
tmeasday Nov 22, 2023
1722ea4
Fix error tests
tmeasday Nov 22, 2023
f7e9586
Update code/lib/core-events/src/errors/preview-errors.ts
tmeasday Nov 22, 2023
96f837b
Remove `Preview.initialize`, use `Preview.ready()` instead
tmeasday Nov 23, 2023
f69f96e
fix
ndelangen Nov 24, 2023
7386fb7
fixes
ndelangen Nov 24, 2023
709222e
fixes
ndelangen Nov 24, 2023
24ec131
Ensure we reject `.ready()` if initialization fails
tmeasday Dec 20, 2023
cbf2095
Fix error message and loaders behavior in PreviewWeb.test.ts
tmeasday Dec 20, 2023
7e3c376
fixing tests
ndelangen Jan 5, 2024
3e7e6b4
Create a proxy `storyStore` object on the preview.
tmeasday Jan 11, 2024
bc1e4cd
Update a11y addon to not use story store
tmeasday Jan 11, 2024
67ea6d1
Fix some tests
tmeasday Jan 13, 2024
f73756e
Remove random log
tmeasday Jan 13, 2024
bf01aa7
Fix a11y tests
tmeasday Jan 13, 2024
11108d9
Fix ExternalPreview
tmeasday Jan 14, 2024
e324ed5
Fix unexpected formatting issues
tmeasday Jan 14, 2024
bee8765
Drop unused vars
tmeasday Jan 14, 2024
4821d82
Allow react complaining about errors caught by a boundary
tmeasday Jan 14, 2024
a21ac32
Add deprecation note + pass through `loadStory`
tmeasday Jan 14, 2024
d75b635
Fix typing problems in PreviewWeb test
tmeasday Jan 15, 2024
4084f37
Clean up error message a tiny bit!
tmeasday Jan 15, 2024
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
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
17 changes: 10 additions & 7 deletions code/addons/docs/src/preview.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import type { PreparedStory } from '@storybook/types';
import { global } from '@storybook/global';

const excludeTags = Object.entries(global.TAGS_OPTIONS ?? {}).reduce((acc, entry) => {
const [tag, option] = entry;
if ((option as any).excludeFromDocsStories) {
acc[tag] = true;
}
return acc;
}, {} as Record<string, boolean>);
const excludeTags = Object.entries(global.TAGS_OPTIONS ?? {}).reduce(
(acc, entry) => {
const [tag, option] = entry;
if ((option as any).excludeFromDocsStories) {
acc[tag] = true;
}
return acc;
},
{} as Record<string, boolean>
);

export const parameters: any = {
docs: {
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
11 changes: 7 additions & 4 deletions code/lib/cli/src/upgrade.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ vi.mock('@storybook/telemetry');
vi.mock('./versions', async (importOriginal) => {
const originalVersions = ((await importOriginal()) as { default: typeof versions }).default;
return {
default: Object.keys(originalVersions).reduce((acc, key) => {
acc[key] = '8.0.0';
return acc;
}, {} as Record<string, string>),
default: Object.keys(originalVersions).reduce(
(acc, key) => {
acc[key] = '8.0.0';
return acc;
},
{} as Record<string, string>
),
};
});

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`;
}
}
Loading