Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion code/builders/builder-manager/src/utils/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const renderHTML = async (
refs: Promise<Record<string, Ref>>,
logLevel: Promise<string>,
docsOptions: Promise<DocsOptions>,
{ versionCheck, previewUrl, configType }: Options
{ versionCheck, previewUrl, configType, ignorePreview }: Options
) => {
const titleRef = await title;
const templateRef = await template;
Expand All @@ -54,5 +54,6 @@ export const renderHTML = async (
PREVIEW_URL: JSON.stringify(previewUrl, null, 2), // global preview URL
},
head: (await customHead) || '',
ignorePreview,
});
};
2 changes: 2 additions & 0 deletions code/builders/builder-manager/templates/template.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
import './sb-manager/runtime.js';
</script>

<% if (!ignorePreview) { %>
<link href="./sb-preview/runtime.js" rel="prefetch" as="script" />
<% } %>
</body>
</html>
1 change: 1 addition & 0 deletions code/lib/cli/src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ command('dev')
)
.option('--force-build-preview', 'Build the preview iframe even if you are using --preview-url')
.option('--docs', 'Build a documentation-only site using addon-docs')
.option('--exact-port', 'Exit early if the desired port is not available')
.option(
'--initial-path [path]',
'URL path to be appended when visiting Storybook for the first time'
Expand Down
2 changes: 1 addition & 1 deletion code/lib/core-server/src/build-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export async function buildDevStandalone(
);
// updateInfo are cached, so this is typically pretty fast
const [port, versionCheck] = await Promise.all([
getServerPort(options.port),
getServerPort(options.port, { exactPort: options.exactPort }),
versionUpdates
? updateCheck(packageJson.version)
: Promise.resolve({ success: false, cached: false, data: {}, time: Date.now() }),
Expand Down
21 changes: 16 additions & 5 deletions code/lib/core-server/src/utils/server-address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,22 @@ export function getServerAddresses(
};
}

export const getServerPort = (port?: number) =>
detectFreePort(port).catch((error) => {
logger.error(error);
process.exit(-1);
});
interface PortOptions {
exactPort?: boolean;
}

export const getServerPort = (port?: number, { exactPort }: PortOptions = {}) =>
detectFreePort(port)
.then((freePort) => {
if (freePort !== port && exactPort) {
process.exit(-1);
}
return freePort;
})
.catch((error) => {
logger.error(error);
process.exit(-1);
});

export const getServerChannelUrl = (port: number, { https }: { https?: boolean }) => {
return `${https ? 'wss' : 'ws'}://localhost:${port}/storybook-server-channel`;
Expand Down
3 changes: 2 additions & 1 deletion code/lib/preview-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,6 @@ export type { PropDescriptor } from './store';
*/
export { ClientApi } from './client-api';
export { StoryStore } from './store';
export { Preview, PreviewWeb } from './preview-web';
export { Preview, PreviewWeb, PreviewWithSelection, UrlStore, WebView } from './preview-web';
export type { SelectionStore, View } from './preview-web';
export { start } from './core-client';
5 changes: 5 additions & 0 deletions code/lib/preview-api/src/modules/preview-web/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ export { Preview } from './Preview';
export { PreviewWeb } from './PreviewWeb';
export { PreviewWithSelection } from './PreviewWithSelection';

export type { SelectionStore } from './SelectionStore';
export { UrlStore } from './UrlStore';
export type { View } from './View';
export { WebView } from './WebView';

export { simulatePageLoad, simulateDOMContentLoaded } from './simulate-pageload';

export { DocsContext } from './docs-context/DocsContext';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
StoryContext,
Parameters,
ComposedStoryFn,
StrictArgTypes,
} from '@storybook/types';

import { HooksContext } from '../../../addons';
Expand Down Expand Up @@ -89,6 +90,7 @@ export function composeStory<TRenderer extends Renderer = Renderer, TArgs extend
args: story.initialArgs as Partial<TArgs>,
play: story.playFunction as ComposedStoryPlayFn<TRenderer, Partial<TArgs>>,
parameters: story.parameters as Parameters,
argTypes: story.argTypes as StrictArgTypes<TArgs>,
id: story.id,
}
);
Expand Down
3 changes: 2 additions & 1 deletion code/lib/types/src/modules/composedStory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/naming-convention */

import type { Renderer, StoryId } from '@storybook/csf';
import type { Renderer, StoryId, StrictArgTypes } from '@storybook/csf';

import type {
AnnotatedStoryFn,
Expand Down Expand Up @@ -60,6 +60,7 @@ export type ComposedStoryFn<
id: StoryId;
storyName: string;
parameters: Parameters;
argTypes: StrictArgTypes<TArgs>;
};
/**
* Based on a module of stories, it returns all stories within it, filtering non-stories
Expand Down
1 change: 1 addition & 0 deletions code/lib/types/src/modules/core-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export interface CLIOptions {
enableCrashReports?: boolean;
host?: string;
initialPath?: string;
exactPort?: boolean;
/**
* @deprecated Use 'staticDirs' Storybook Configuration option instead
*/
Expand Down
3 changes: 2 additions & 1 deletion code/renderers/react/src/entry-preview.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const parameters: {} = { renderer: 'react' };
export { render, renderToCanvas } from './render';
export { render } from './render';
export { renderToCanvas } from './renderToCanvas';
81 changes: 3 additions & 78 deletions code/renderers/react/src/render.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { global } from '@storybook/global';
import type { FC } from 'react';
import React, { Component as ReactComponent, StrictMode, Fragment } from 'react';
import { renderElement, unmountElement } from '@storybook/react-dom-shim';
import React from 'react';

import type { RenderContext, ArgsStoryFn } from '@storybook/types';
import type { ArgsStoryFn } from '@storybook/types';

import type { ReactRenderer, StoryContext } from './types';

const { FRAMEWORK_OPTIONS } = global;
import type { ReactRenderer } from './types';

export const render: ArgsStoryFn<ReactRenderer> = (args, context) => {
const { id, component: Component } = context;
Expand All @@ -19,73 +14,3 @@ export const render: ArgsStoryFn<ReactRenderer> = (args, context) => {

return <Component {...args} />;
};

class ErrorBoundary extends ReactComponent<{
showException: (err: Error) => void;
showMain: () => void;
children?: React.ReactNode;
}> {
state = { hasError: false };

static getDerivedStateFromError() {
return { hasError: true };
}

componentDidMount() {
const { hasError } = this.state;
const { showMain } = this.props;
if (!hasError) {
showMain();
}
}

componentDidCatch(err: Error) {
const { showException } = this.props;
// message partially duplicates stack, strip it
showException(err);
}

render() {
const { hasError } = this.state;
const { children } = this.props;

return hasError ? null : children;
}
}

const Wrapper = FRAMEWORK_OPTIONS?.strictMode ? StrictMode : Fragment;

export async function renderToCanvas(
{
storyContext,
unboundStoryFn,
showMain,
showException,
forceRemount,
}: RenderContext<ReactRenderer>,
canvasElement: ReactRenderer['canvasElement']
) {
const Story = unboundStoryFn as FC<StoryContext<ReactRenderer>>;

const content = (
<ErrorBoundary showMain={showMain} showException={showException}>
<Story {...storyContext} />
</ErrorBoundary>
);

// For React 15, StrictMode & Fragment doesn't exists.
const element = Wrapper ? <Wrapper>{content}</Wrapper> : content;

// In most cases, we need to unmount the existing set of components in the DOM node.
// Otherwise, React may not recreate instances for every story run.
// This could leads to issues like below:
// https://github.com/storybookjs/react-storybook/issues/81
// (This is not the case when we change args or globals to the story however)
if (forceRemount) {
unmountElement(canvasElement);
}

await renderElement(element, canvasElement);

return () => unmountElement(canvasElement);
}
80 changes: 80 additions & 0 deletions code/renderers/react/src/renderToCanvas.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { global } from '@storybook/global';
import type { FC } from 'react';
import React, { Component as ReactComponent, StrictMode, Fragment } from 'react';
import { renderElement, unmountElement } from '@storybook/react-dom-shim';

import type { RenderContext } from '@storybook/types';

import type { ReactRenderer, StoryContext } from './types';

const { FRAMEWORK_OPTIONS } = global;

class ErrorBoundary extends ReactComponent<{
showException: (err: Error) => void;
showMain: () => void;
children?: React.ReactNode;
}> {
state = { hasError: false };

static getDerivedStateFromError() {
return { hasError: true };
}

componentDidMount() {
const { hasError } = this.state;
const { showMain } = this.props;
if (!hasError) {
showMain();
}
}

componentDidCatch(err: Error) {
const { showException } = this.props;
// message partially duplicates stack, strip it
showException(err);
}

render() {
const { hasError } = this.state;
const { children } = this.props;

return hasError ? null : children;
}
}

const Wrapper = FRAMEWORK_OPTIONS?.strictMode ? StrictMode : Fragment;

export async function renderToCanvas(
{
storyContext,
unboundStoryFn,
showMain,
showException,
forceRemount,
}: RenderContext<ReactRenderer>,
canvasElement: ReactRenderer['canvasElement']
) {
const Story = unboundStoryFn as FC<StoryContext<ReactRenderer>>;

const content = (
<ErrorBoundary showMain={showMain} showException={showException}>
<Story {...storyContext} />
</ErrorBoundary>
);

// For React 15, StrictMode & Fragment doesn't exists.
const element = Wrapper ? <Wrapper>{content}</Wrapper> : content;

// In most cases, we need to unmount the existing set of components in the DOM node.
// Otherwise, React may not recreate instances for every story run.
// This could leads to issues like below:
// https://github.com/storybookjs/react-storybook/issues/81
// (This is not the case when we change args or globals to the story however)
if (forceRemount) {
unmountElement(canvasElement);
}

await renderElement(element, canvasElement);

return () => unmountElement(canvasElement);
}