mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-19 05:02:40 +08:00
Created react-dom-shim
package and use in react+docs
This commit is contained in:
parent
627a0f1f3b
commit
8c767d2831
@ -113,6 +113,7 @@
|
||||
"@storybook/node-logger": "7.0.0-beta.53",
|
||||
"@storybook/postinstall": "7.0.0-beta.53",
|
||||
"@storybook/preview-api": "7.0.0-beta.53",
|
||||
"@storybook/react-dom-shim": "7.0.0-beta.53",
|
||||
"@storybook/theming": "7.0.0-beta.53",
|
||||
"@storybook/types": "7.0.0-beta.53",
|
||||
"fs-extra": "^11.1.0",
|
||||
@ -141,7 +142,6 @@
|
||||
"bundler": {
|
||||
"entries": [
|
||||
"./src/index.ts",
|
||||
"./src/preset.ts",
|
||||
"./src/preview.ts",
|
||||
"./src/blocks.ts",
|
||||
"./src/shims/mdx-react-shim.ts"
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { renderElement, unmountElement } from '@storybook/react-dom-shim';
|
||||
import type { Renderer, Parameters, DocsContextProps, DocsRenderFunction } from '@storybook/types';
|
||||
import { Docs, CodeOrSourceMdx, AnchorMdx, HeadersMdx } from '@storybook/blocks';
|
||||
|
||||
@ -27,19 +27,20 @@ export class DocsRenderer<TRenderer extends Renderer> {
|
||||
...docsParameter?.components,
|
||||
};
|
||||
|
||||
import('@mdx-js/react').then(({ MDXProvider }) => {
|
||||
ReactDOM.render(
|
||||
<MDXProvider components={components}>
|
||||
<Docs context={context} docsParameter={docsParameter} />
|
||||
</MDXProvider>,
|
||||
element,
|
||||
callback
|
||||
);
|
||||
});
|
||||
import('@mdx-js/react')
|
||||
.then(({ MDXProvider }) =>
|
||||
renderElement(
|
||||
<MDXProvider components={components}>
|
||||
<Docs context={context} docsParameter={docsParameter} />
|
||||
</MDXProvider>,
|
||||
element
|
||||
)
|
||||
)
|
||||
.then(callback);
|
||||
};
|
||||
|
||||
this.unmount = (element: HTMLElement) => {
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
unmountElement(element);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,15 @@ import fs from 'fs-extra';
|
||||
import remarkSlug from 'remark-slug';
|
||||
import remarkExternalLinks from 'remark-external-links';
|
||||
import { dedent } from 'ts-dedent';
|
||||
import { dirname } from 'path';
|
||||
|
||||
import type { IndexerOptions, StoryIndexer, DocsOptions, Options } from '@storybook/types';
|
||||
import type {
|
||||
IndexerOptions,
|
||||
StoryIndexer,
|
||||
DocsOptions,
|
||||
Options,
|
||||
StorybookConfig,
|
||||
} from '@storybook/types';
|
||||
import type { CsfPluginOptions } from '@storybook/csf-plugin';
|
||||
import type { JSXOptions, CompileOptions } from '@storybook/mdx2-csf';
|
||||
import { global } from '@storybook/global';
|
||||
@ -153,6 +160,10 @@ const docs = (docsOptions: DocsOptions) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const addons: StorybookConfig['addons'] = [
|
||||
dirname(require.resolve('@storybook/react-dom-shim/package.json')),
|
||||
];
|
||||
|
||||
/*
|
||||
* This is a workaround for https://github.com/Swatinem/rollup-plugin-dts/issues/162
|
||||
* something down the dependency chain is using typescript namespaces, which are not supported by rollup-plugin-dts
|
||||
|
@ -63,6 +63,7 @@ export default {
|
||||
'@storybook/preview-api': '7.0.0-beta.53',
|
||||
'@storybook/preview-web': '7.0.0-beta.53',
|
||||
'@storybook/react': '7.0.0-beta.53',
|
||||
'@storybook/react-dom-shim': '7.0.0-beta.53',
|
||||
'@storybook/react-vite': '7.0.0-beta.53',
|
||||
'@storybook/react-webpack5': '7.0.0-beta.53',
|
||||
'@storybook/router': '7.0.0-beta.53',
|
||||
|
@ -254,6 +254,7 @@ export async function loadPreset(
|
||||
${input} is not a valid preset
|
||||
`);
|
||||
} catch (e: any) {
|
||||
console.log(e);
|
||||
const warning =
|
||||
level > 0
|
||||
? ` Failed to load preset: ${JSON.stringify(input)} on level ${level}`
|
||||
|
3
code/lib/react-dom-shim/README.md
Normal file
3
code/lib/react-dom-shim/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# React Dom Shim
|
||||
|
||||
A shim for `react-dom` that provides a single API that will work whether the user is on `react-dom@17` or `react-dom@18`, as well as webpack/vite config necessary to make that work.
|
74
code/lib/react-dom-shim/package.json
Normal file
74
code/lib/react-dom-shim/package.json
Normal file
@ -0,0 +1,74 @@
|
||||
{
|
||||
"name": "@storybook/react-dom-shim",
|
||||
"version": "7.0.0-beta.53",
|
||||
"description": "",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
],
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/main/lib/react-dom-shim",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybookjs/storybook/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/storybookjs/storybook.git",
|
||||
"directory": "lib/react-dom-shim"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/storybook"
|
||||
},
|
||||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"node": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"import": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./react-18": {
|
||||
"node": "./dist/react-18.js",
|
||||
"require": "./dist/react-18.js",
|
||||
"import": "./dist/react-18.mjs",
|
||||
"types": "./dist/react-18.d.ts"
|
||||
},
|
||||
"./dist/preset": {
|
||||
"require": "./dist/preset.js",
|
||||
"import": "./dist/preset.mjs",
|
||||
"types": "./dist/preset.d.ts"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"README.md",
|
||||
"*.js",
|
||||
"*.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"check": "../../../scripts/node_modules/.bin/tsc --noEmit",
|
||||
"prep": "../../../scripts/prepare/bundle.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "~4.9.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"bundler": {
|
||||
"entries": [
|
||||
"./src/preset.ts",
|
||||
"./src/index.tsx",
|
||||
"./src/react-18.tsx"
|
||||
]
|
||||
},
|
||||
"gitHead": "b1da06450dc3e4124a935785a2041b18204533ae"
|
||||
}
|
1
code/lib/react-dom-shim/preset.js
vendored
Normal file
1
code/lib/react-dom-shim/preset.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/preset');
|
6
code/lib/react-dom-shim/project.json
Normal file
6
code/lib/react-dom-shim/project.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/react-dom-shim",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"implicitDependencies": [],
|
||||
"type": "library"
|
||||
}
|
12
code/lib/react-dom-shim/src/index.tsx
Normal file
12
code/lib/react-dom-shim/src/index.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import type { ReactElement } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
export const renderElement = async (node: ReactElement, el: Element) => {
|
||||
return new Promise((resolve) => {
|
||||
ReactDOM.render(node, el, () => resolve(null));
|
||||
});
|
||||
};
|
||||
|
||||
export const unmountElement = (el: Element) => {
|
||||
ReactDOM.unmountComponentAtNode(el);
|
||||
};
|
26
code/lib/react-dom-shim/src/preset.ts
Normal file
26
code/lib/react-dom-shim/src/preset.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import type { Options, StorybookConfig } from '@storybook/types';
|
||||
import { version } from 'react-dom/package.json';
|
||||
|
||||
// @ts-expect-error can't use webpack-inclusive config type
|
||||
export const webpackFinal: StorybookConfig['webpackFinal'] = async (
|
||||
config: any,
|
||||
options: Options
|
||||
) => {
|
||||
const { legacyRootApi } = await options.presets.apply<{ legacyRootApi?: boolean }>(
|
||||
'frameworkOptions'
|
||||
);
|
||||
|
||||
const isReact18 = version.startsWith('18') || version.startsWith('0.0.0');
|
||||
const useReact17 = legacyRootApi ?? !isReact18;
|
||||
if (useReact17) return config;
|
||||
|
||||
return {
|
||||
...config,
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
alias: {
|
||||
'@storybook/react-dom-shim': '@storybook/react-dom-shim/react-18',
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
52
code/lib/react-dom-shim/src/react-18.tsx
Normal file
52
code/lib/react-dom-shim/src/react-18.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import type { FC, ReactElement } from 'react';
|
||||
import type { Root as ReactRoot } from 'react-dom/client';
|
||||
import React, { useLayoutEffect, useRef } from 'react';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import ReactDOM from 'react-dom/client';
|
||||
|
||||
// A map of all rendered React 18 nodes
|
||||
const nodes = new Map<Element, ReactRoot>();
|
||||
|
||||
const WithCallback: FC<{ callback: () => void; children: ReactElement }> = ({
|
||||
callback,
|
||||
children,
|
||||
}) => {
|
||||
// See https://github.com/reactwg/react-18/discussions/5#discussioncomment-2276079
|
||||
const once = useRef<() => void>();
|
||||
useLayoutEffect(() => {
|
||||
if (once.current === callback) return;
|
||||
once.current = callback;
|
||||
callback();
|
||||
}, [callback]);
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export const renderElement = async (node: ReactElement, el: Element) => {
|
||||
// Create Root Element conditionally for new React 18 Root Api
|
||||
const root = await getReactRoot(el);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
root.render(<WithCallback callback={() => resolve(null)}>{node}</WithCallback>);
|
||||
});
|
||||
};
|
||||
|
||||
export const unmountElement = (el: Element, shouldUseNewRootApi?: boolean) => {
|
||||
const root = nodes.get(el);
|
||||
|
||||
if (root) {
|
||||
root.unmount();
|
||||
nodes.delete(el);
|
||||
}
|
||||
};
|
||||
|
||||
const getReactRoot = async (el: Element): Promise<ReactRoot | null> => {
|
||||
let root = nodes.get(el);
|
||||
|
||||
if (!root) {
|
||||
root = ReactDOM.createRoot(el);
|
||||
nodes.set(el, root);
|
||||
}
|
||||
|
||||
return root;
|
||||
};
|
8
code/lib/react-dom-shim/tsconfig.json
Normal file
8
code/lib/react-dom-shim/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"types": ["node", "jest"]
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
@ -42,11 +42,6 @@
|
||||
"import": "./dist/framework-preset-react-docs.mjs",
|
||||
"types": "./dist/framework-preset-react-docs.d.ts"
|
||||
},
|
||||
"./dist/framework-preset-react-dom-hack": {
|
||||
"require": "./dist/framework-preset-react-dom-hack.js",
|
||||
"import": "./dist/framework-preset-react-dom-hack.mjs",
|
||||
"types": "./dist/framework-preset-react-dom-hack.d.ts"
|
||||
},
|
||||
"./dist/framework-preset-react": {
|
||||
"require": "./dist/framework-preset-react.js",
|
||||
"import": "./dist/framework-preset-react.mjs",
|
||||
@ -112,7 +107,6 @@
|
||||
"./src/index.ts",
|
||||
"./src/framework-preset-cra.ts",
|
||||
"./src/framework-preset-react-docs.ts",
|
||||
"./src/framework-preset-react-dom-hack.ts",
|
||||
"./src/framework-preset-react.ts"
|
||||
],
|
||||
"platform": "node"
|
||||
|
@ -1,24 +0,0 @@
|
||||
import { readJSON } from 'fs-extra';
|
||||
import { IgnorePlugin } from 'webpack';
|
||||
import type { StorybookConfig } from '@storybook/core-webpack';
|
||||
|
||||
// this is a hack to allow importing react-dom/client even when it's not available
|
||||
// this should be removed once we drop support for react-dom < 18
|
||||
|
||||
export const webpackFinal: StorybookConfig['webpackFinal'] = async (config) => {
|
||||
const reactDomPkg = await readJSON(require.resolve('react-dom/package.json'));
|
||||
|
||||
return {
|
||||
...config,
|
||||
plugins: [
|
||||
...(config.plugins || []),
|
||||
reactDomPkg?.version?.startsWith('18') || reactDomPkg?.version?.startsWith('0.0.0')
|
||||
? null
|
||||
: new IgnorePlugin({
|
||||
resourceRegExp: /react-dom\/client$/,
|
||||
contextRegExp:
|
||||
/(renderers\/react|renderers\\react|@storybook\/react|@storybook\\react)/, // TODO this needs to work for both in our MONOREPO and in the user's NODE_MODULES
|
||||
}),
|
||||
].filter(Boolean),
|
||||
};
|
||||
};
|
@ -4,7 +4,6 @@ export * from './types';
|
||||
|
||||
export const addons: StorybookConfig['addons'] = [
|
||||
require.resolve('@storybook/preset-react-webpack/dist/framework-preset-react'),
|
||||
require.resolve('@storybook/preset-react-webpack/dist/framework-preset-react-dom-hack'),
|
||||
require.resolve('@storybook/preset-react-webpack/dist/framework-preset-cra'),
|
||||
require.resolve('@storybook/preset-react-webpack/dist/framework-preset-react-docs'),
|
||||
];
|
||||
|
@ -31,6 +31,11 @@
|
||||
"import": "./dist/config.mjs",
|
||||
"types": "./dist/config.d.ts"
|
||||
},
|
||||
"./dist/preset": {
|
||||
"require": "./dist/preset.js",
|
||||
"import": "./dist/preset.mjs",
|
||||
"types": "./dist/preset.d.ts"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
@ -53,6 +58,7 @@
|
||||
"@storybook/docs-tools": "7.0.0-beta.53",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/preview-api": "7.0.0-beta.53",
|
||||
"@storybook/react-dom-shim": "7.0.0-beta.53",
|
||||
"@storybook/types": "7.0.0-beta.53",
|
||||
"@types/escodegen": "^0.0.6",
|
||||
"@types/estree": "^0.0.51",
|
||||
@ -95,7 +101,8 @@
|
||||
"bundler": {
|
||||
"entries": [
|
||||
"./src/index.ts",
|
||||
"./src/config.ts"
|
||||
"./src/config.ts",
|
||||
"./src/preset.ts"
|
||||
],
|
||||
"platform": "browser"
|
||||
},
|
||||
|
1
code/renderers/react/preset.js
Normal file
1
code/renderers/react/preset.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/preset');
|
6
code/renderers/react/src/preset.ts
Normal file
6
code/renderers/react/src/preset.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type { StorybookConfig } from '@storybook/types';
|
||||
|
||||
export const addons: StorybookConfig['addons'] = [
|
||||
// Can't use path in this file due to be compiled for browser, this is a workaround
|
||||
require.resolve('@storybook/react-dom-shim/package.json').replace('package.json', ''),
|
||||
];
|
@ -1,25 +1,11 @@
|
||||
import { global } from '@storybook/global';
|
||||
|
||||
import type { FC, ReactElement } from 'react';
|
||||
import React, {
|
||||
Component as ReactComponent,
|
||||
StrictMode,
|
||||
Fragment,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import ReactDOM, { version as reactDomVersion } from 'react-dom';
|
||||
import type { Root as ReactRoot } from 'react-dom/client';
|
||||
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, ArgsStoryFn } from '@storybook/types';
|
||||
|
||||
import type { ReactRenderer, StoryContext } from './types';
|
||||
|
||||
const { FRAMEWORK_OPTIONS } = global;
|
||||
|
||||
// A map of all rendered React 18 nodes
|
||||
const nodes = new Map<Element, ReactRoot>();
|
||||
|
||||
export const render: ArgsStoryFn<ReactRenderer> = (args, context) => {
|
||||
const { id, component: Component } = context;
|
||||
if (!Component) {
|
||||
@ -31,68 +17,6 @@ export const render: ArgsStoryFn<ReactRenderer> = (args, context) => {
|
||||
return <Component {...args} />;
|
||||
};
|
||||
|
||||
const WithCallback: FC<{ callback: () => void; children: ReactElement }> = ({
|
||||
callback,
|
||||
children,
|
||||
}) => {
|
||||
// See https://github.com/reactwg/react-18/discussions/5#discussioncomment-2276079
|
||||
const once = useRef<() => void>();
|
||||
useLayoutEffect(() => {
|
||||
if (once.current === callback) return;
|
||||
once.current = callback;
|
||||
callback();
|
||||
}, [callback]);
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
const renderElement = async (node: ReactElement, el: Element) => {
|
||||
// Create Root Element conditionally for new React 18 Root Api
|
||||
const root = await getReactRoot(el);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
if (root) {
|
||||
root.render(<WithCallback callback={() => resolve(null)}>{node}</WithCallback>);
|
||||
} else {
|
||||
ReactDOM.render(node, el, () => resolve(null));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const canUseNewReactRootApi =
|
||||
reactDomVersion && (reactDomVersion.startsWith('18') || reactDomVersion.startsWith('0.0.0'));
|
||||
|
||||
const shouldUseNewRootApi = FRAMEWORK_OPTIONS?.legacyRootApi !== true;
|
||||
|
||||
const isUsingNewReactRootApi = shouldUseNewRootApi && canUseNewReactRootApi;
|
||||
|
||||
const unmountElement = (el: Element) => {
|
||||
const root = nodes.get(el);
|
||||
if (root && isUsingNewReactRootApi) {
|
||||
root.unmount();
|
||||
nodes.delete(el);
|
||||
} else {
|
||||
ReactDOM.unmountComponentAtNode(el);
|
||||
}
|
||||
};
|
||||
|
||||
const getReactRoot = async (el: Element): Promise<ReactRoot | null> => {
|
||||
if (!isUsingNewReactRootApi) {
|
||||
return null;
|
||||
}
|
||||
let root = nodes.get(el);
|
||||
|
||||
if (!root) {
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
const reactDomClient = (await import('react-dom/client')).default;
|
||||
root = reactDomClient.createRoot(el);
|
||||
|
||||
nodes.set(el, root);
|
||||
}
|
||||
|
||||
return root;
|
||||
};
|
||||
|
||||
class ErrorBoundary extends ReactComponent<{
|
||||
showException: (err: Error) => void;
|
||||
showMain: () => void;
|
||||
|
@ -6388,6 +6388,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/react-dom-shim@7.0.0-beta.53, @storybook/react-dom-shim@workspace:lib/react-dom-shim":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@storybook/react-dom-shim@workspace:lib/react-dom-shim"
|
||||
dependencies:
|
||||
typescript: ~4.9.3
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@storybook/react-vite@workspace:*, @storybook/react-vite@workspace:frameworks/react-vite":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@storybook/react-vite@workspace:frameworks/react-vite"
|
||||
@ -6442,6 +6453,7 @@ __metadata:
|
||||
"@storybook/docs-tools": 7.0.0-beta.53
|
||||
"@storybook/global": ^5.0.0
|
||||
"@storybook/preview-api": 7.0.0-beta.53
|
||||
"@storybook/react-dom-shim": 7.0.0-beta.53
|
||||
"@storybook/types": 7.0.0-beta.53
|
||||
"@types/escodegen": ^0.0.6
|
||||
"@types/estree": ^0.0.51
|
||||
|
Loading…
x
Reference in New Issue
Block a user