Skip to content

Commit

Permalink
Merge pull request #25086 from storybookjs/shilman/enable-embedded-st…
Browse files Browse the repository at this point in the history
…orybook

Core: Maintenance changes for NextJS embedding
  • Loading branch information
shilman committed Dec 11, 2023
2 parents f091782 + 10e81f9 commit 3817d1c
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 88 deletions.
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);
}

0 comments on commit 3817d1c

Please sign in to comment.