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

Fix: vite devmode with storyStoreV6 by ensuring singleton via global #20207

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion code/lib/builder-vite/src/codegen-iframe-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export async function generateIframeScriptCode(options: ExtendedOptions) {
import { configure } from '${rendererName}';

import { logger } from '@storybook/client-logger';
import * as clientApi from "@storybook/preview-api";
import * as previewApi from "@storybook/preview-api";
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
${filesToImport(configEntries, 'config')}

import * as preview from '${virtualPreviewFile}';
Expand Down
46 changes: 18 additions & 28 deletions code/lib/preview-api/src/modules/client-api/ClientApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// <reference types="webpack-env" />
/* eslint-disable no-underscore-dangle */

import { dedent } from 'ts-dedent';
import global from 'global';
Expand Down Expand Up @@ -29,10 +29,6 @@ import { combineParameters, composeStepRunners, normalizeInputTypes } from '../.

import { StoryStoreFacade } from './StoryStoreFacade';

// ClientApi (and StoreStore) are really singletons. However they are not created until the
// relevant framework instanciates them via `start.js`. The good news is this happens right away.
let singleton: ClientApi<Renderer>;

const warningAlternatives = {
addDecorator: `Instead, use \`export const decorators = [];\` in your \`preview.js\`.`,
addParameters: `Instead, use \`export const parameters = {};\` in your \`preview.js\`.`,
Expand All @@ -55,61 +51,61 @@ const checkMethod = (method: keyof typeof warningAlternatives) => {
);
}

if (!singleton) {
if (!global.__STORYBOOK_CLIENT_API__) {
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(`Singleton client API not yet initialized, cannot call \`${method}\`.`);
}
};

export const addDecorator = (decorator: DecoratorFunction<Renderer>) => {
checkMethod('addDecorator');
singleton.addDecorator(decorator);
global.__STORYBOOK_CLIENT_API__?.addDecorator(decorator);
};

export const addParameters = (parameters: Parameters) => {
checkMethod('addParameters');
singleton.addParameters(parameters);
global.__STORYBOOK_CLIENT_API__?.addParameters(parameters);
};

export const addLoader = (loader: LoaderFunction<Renderer>) => {
checkMethod('addLoader');
singleton.addLoader(loader);
global.__STORYBOOK_CLIENT_API__?.addLoader(loader);
};

export const addArgs = (args: Args) => {
checkMethod('addArgs');
singleton.addArgs(args);
global.__STORYBOOK_CLIENT_API__?.addArgs(args);
};

export const addArgTypes = (argTypes: ArgTypes) => {
checkMethod('addArgTypes');
singleton.addArgTypes(argTypes);
global.__STORYBOOK_CLIENT_API__?.addArgTypes(argTypes);
};

export const addArgsEnhancer = (enhancer: ArgsEnhancer<Renderer>) => {
checkMethod('addArgsEnhancer');
singleton.addArgsEnhancer(enhancer);
global.__STORYBOOK_CLIENT_API__?.addArgsEnhancer(enhancer);
};

export const addArgTypesEnhancer = (enhancer: ArgTypesEnhancer<Renderer>) => {
checkMethod('addArgTypesEnhancer');
singleton.addArgTypesEnhancer(enhancer);
global.__STORYBOOK_CLIENT_API__?.addArgTypesEnhancer(enhancer);
};

export const addStepRunner = (stepRunner: StepRunner) => {
checkMethod('addStepRunner');
singleton.addStepRunner(stepRunner);
global.__STORYBOOK_CLIENT_API__?.addStepRunner(stepRunner);
};

export const getGlobalRender = () => {
checkMethod('getGlobalRender');
return singleton.facade.projectAnnotations.render;
return global.__STORYBOOK_CLIENT_API__?.facade.projectAnnotations.render;
};

export const setGlobalRender = (
render: typeof singleton['facade']['projectAnnotations']['render']
) => {
export const setGlobalRender = (render: StoryStoreFacade<any>['projectAnnotations']['render']) => {
checkMethod('setGlobalRender');
singleton.facade.projectAnnotations.render = render;
if (global.__STORYBOOK_CLIENT_API__) {
global.__STORYBOOK_CLIENT_API__.facade.projectAnnotations.render = render;
}
};

const invalidStoryTypes = new Set(['string', 'number', 'boolean', 'symbol']);
Expand All @@ -132,8 +128,6 @@ export class ClientApi<TRenderer extends Renderer> {
this.addons = {};

this.storyStore = storyStore;

singleton = this as any;
}

importFn(path: Path) {
Expand Down Expand Up @@ -213,7 +207,6 @@ export class ClientApi<TRenderer extends Renderer> {
_addedExports = {} as Record<Path, ModuleExports>;

_loadAddedExports() {
// eslint-disable-next-line no-underscore-dangle
Object.entries(this._addedExports).forEach(([fileName, fileExports]) =>
this.facade.addStoriesFromExports(fileName, fileExports)
);
Expand Down Expand Up @@ -247,7 +240,7 @@ export class ClientApi<TRenderer extends Renderer> {
let i = 1;
// Deal with `storiesOf()` being called twice in the same file.
// On HMR, we clear _addedExports[fileName] below.
// eslint-disable-next-line no-underscore-dangle

while (this._addedExports[fileName]) {
i += 1;
fileName = `${baseFilename}-${i}`;
Expand All @@ -259,7 +252,7 @@ export class ClientApi<TRenderer extends Renderer> {
m.hot.accept();
m.hot.dispose(() => {
this.facade.clearFilenameExports(fileName);
// eslint-disable-next-line no-underscore-dangle

delete this._addedExports[fileName];

// We need to update the importFn as soon as the module re-evaluates
Expand All @@ -268,7 +261,6 @@ export class ClientApi<TRenderer extends Renderer> {
// debounce it somehow for initial startup. Instead, we'll take advantage of
// the fact that the evaluation of the module happens immediately in the same tick
setTimeout(() => {
// eslint-disable-next-line no-underscore-dangle
this._loadAddedExports();
this.onImportFnChanged?.({ importFn: this.importFn.bind(this) });
}, 0);
Expand Down Expand Up @@ -301,7 +293,7 @@ export class ClientApi<TRenderer extends Renderer> {
parameters: {},
};
// We map these back to a simple default export, even though we have type guarantees at this point
// eslint-disable-next-line no-underscore-dangle

this._addedExports[fileName] = { default: meta };

let counter = 0;
Expand All @@ -320,10 +312,8 @@ export class ClientApi<TRenderer extends Renderer> {

const { decorators, loaders, component, args, argTypes, ...storyParameters } = parameters;

// eslint-disable-next-line no-underscore-dangle
const storyId = parameters.__id || toId(kind, storyName);

// eslint-disable-next-line no-underscore-dangle
const csfExports = this._addedExports[fileName];
// Whack a _ on the front incase it is "default"
csfExports[`story${counter}`] = {
Expand Down