diff --git a/code/builders/builder-manager/src/utils/template.ts b/code/builders/builder-manager/src/utils/template.ts index 64fb626869b3..0d7b67a1dff3 100644 --- a/code/builders/builder-manager/src/utils/template.ts +++ b/code/builders/builder-manager/src/utils/template.ts @@ -34,7 +34,7 @@ export const renderHTML = async ( refs: Promise>, logLevel: Promise, docsOptions: Promise, - { versionCheck, previewUrl, configType }: Options + { versionCheck, previewUrl, configType, ignorePreview }: Options ) => { const titleRef = await title; const templateRef = await template; @@ -54,5 +54,6 @@ export const renderHTML = async ( PREVIEW_URL: JSON.stringify(previewUrl, null, 2), // global preview URL }, head: (await customHead) || '', + ignorePreview, }); }; diff --git a/code/builders/builder-manager/templates/template.ejs b/code/builders/builder-manager/templates/template.ejs index 7961f4ae4f7c..af42859a0791 100644 --- a/code/builders/builder-manager/templates/template.ejs +++ b/code/builders/builder-manager/templates/template.ejs @@ -66,6 +66,8 @@ import './sb-manager/runtime.js'; + <% if (!ignorePreview) { %> + <% } %> diff --git a/code/lib/cli/src/generate.ts b/code/lib/cli/src/generate.ts index bc396ec428b7..2594863f0e90 100644 --- a/code/lib/cli/src/generate.ts +++ b/code/lib/cli/src/generate.ts @@ -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' diff --git a/code/lib/core-server/src/build-dev.ts b/code/lib/core-server/src/build-dev.ts index 186a1a6fc47b..5e8a0a955c2a 100644 --- a/code/lib/core-server/src/build-dev.ts +++ b/code/lib/core-server/src/build-dev.ts @@ -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() }), diff --git a/code/lib/core-server/src/utils/server-address.ts b/code/lib/core-server/src/utils/server-address.ts index 5e4ae1b18b72..bfcb2ba969d9 100644 --- a/code/lib/core-server/src/utils/server-address.ts +++ b/code/lib/core-server/src/utils/server-address.ts @@ -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`; diff --git a/code/lib/preview-api/src/index.ts b/code/lib/preview-api/src/index.ts index db1f05d29870..d9b2be0b3bf5 100644 --- a/code/lib/preview-api/src/index.ts +++ b/code/lib/preview-api/src/index.ts @@ -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'; diff --git a/code/lib/preview-api/src/modules/preview-web/index.ts b/code/lib/preview-api/src/modules/preview-web/index.ts index 0fb2f4da2a65..9ee4e4ce3d7a 100644 --- a/code/lib/preview-api/src/modules/preview-web/index.ts +++ b/code/lib/preview-api/src/modules/preview-web/index.ts @@ -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'; diff --git a/code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts b/code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts index c9a3731f4bd6..da92ad8784d7 100644 --- a/code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts +++ b/code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts @@ -11,6 +11,7 @@ import type { StoryContext, Parameters, ComposedStoryFn, + StrictArgTypes, } from '@storybook/types'; import { HooksContext } from '../../../addons'; @@ -89,6 +90,7 @@ export function composeStory, play: story.playFunction as ComposedStoryPlayFn>, parameters: story.parameters as Parameters, + argTypes: story.argTypes as StrictArgTypes, id: story.id, } ); diff --git a/code/lib/types/src/modules/composedStory.ts b/code/lib/types/src/modules/composedStory.ts index f02a30187a38..a8902426aba4 100644 --- a/code/lib/types/src/modules/composedStory.ts +++ b/code/lib/types/src/modules/composedStory.ts @@ -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, @@ -60,6 +60,7 @@ export type ComposedStoryFn< id: StoryId; storyName: string; parameters: Parameters; + argTypes: StrictArgTypes; }; /** * Based on a module of stories, it returns all stories within it, filtering non-stories diff --git a/code/lib/types/src/modules/core-common.ts b/code/lib/types/src/modules/core-common.ts index 4eb9119b74c4..53dcecdcc684 100644 --- a/code/lib/types/src/modules/core-common.ts +++ b/code/lib/types/src/modules/core-common.ts @@ -170,6 +170,7 @@ export interface CLIOptions { enableCrashReports?: boolean; host?: string; initialPath?: string; + exactPort?: boolean; /** * @deprecated Use 'staticDirs' Storybook Configuration option instead */ diff --git a/code/renderers/react/src/entry-preview.ts b/code/renderers/react/src/entry-preview.ts index fe96bc623da7..c5cabfb30420 100644 --- a/code/renderers/react/src/entry-preview.ts +++ b/code/renderers/react/src/entry-preview.ts @@ -1,2 +1,3 @@ export const parameters: {} = { renderer: 'react' }; -export { render, renderToCanvas } from './render'; +export { render } from './render'; +export { renderToCanvas } from './renderToCanvas'; diff --git a/code/renderers/react/src/render.tsx b/code/renderers/react/src/render.tsx index 275fa704ffca..06bf38aba8e3 100644 --- a/code/renderers/react/src/render.tsx +++ b/code/renderers/react/src/render.tsx @@ -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 = (args, context) => { const { id, component: Component } = context; @@ -19,73 +14,3 @@ export const render: ArgsStoryFn = (args, context) => { return ; }; - -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, - canvasElement: ReactRenderer['canvasElement'] -) { - const Story = unboundStoryFn as FC>; - - const content = ( - - - - ); - - // For React 15, StrictMode & Fragment doesn't exists. - const element = Wrapper ? {content} : 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); -} diff --git a/code/renderers/react/src/renderToCanvas.tsx b/code/renderers/react/src/renderToCanvas.tsx new file mode 100644 index 000000000000..d8821a3458e4 --- /dev/null +++ b/code/renderers/react/src/renderToCanvas.tsx @@ -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, + canvasElement: ReactRenderer['canvasElement'] +) { + const Story = unboundStoryFn as FC>; + + const content = ( + + + + ); + + // For React 15, StrictMode & Fragment doesn't exists. + const element = Wrapper ? {content} : 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); +}