Skip to content

Commit 52b6fdf

Browse files
authored
Support React 18 (#320)
An [exciting PR](storybookjs/storybook#17215) has been merged and released in `6.5.0-alpha.58 ` that adds storybook support for React 18. However, it currently breaks in the vite-builder test because of an `import()` of the new `react-dom/client`, which only exists in react 18. Due to vitejs/vite#6007, we have to do a little bit of trickery to stop vite from erroring out. The approach I'm taking here is: 1) Rewriting the import of `react-dom/client` to a dummy file if it can't be `require.resolve()`ed. This fixes the problem in production builds. 2) Additionally, adding `react-dom/client` to the list of `optimizeDeps.exclude` if the framework is react and the package can't be required. This is necessary to prevent vite from trying to pre-bundle, and throwing errors in dev. This PR also updates the examples to the new `6.5.0-alpha.58` version of storybook, which includes the PR mentioned above, so that it can be tested in our react examples, and it adds a new `react-18` example as well.
1 parent b6ef9de commit 52b6fdf

37 files changed

+1295
-473
lines changed

examples/react-18/.env

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
STORYBOOK_ENV_VAR=included
2+
VITE_ENV_VAR=included
3+
ENV_VAR=should_not_be_included

examples/react-18/.storybook/main.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module.exports = {
2+
framework: '@storybook/react',
3+
stories: ['../stories/**/*.stories.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'],
4+
addons: ['@storybook/addon-a11y', '@storybook/addon-links', '@storybook/addon-essentials'],
5+
core: {
6+
builder: '@storybook/builder-vite',
7+
},
8+
features: {
9+
storyStoreV7: true,
10+
},
11+
async viteFinal(config, { configType }) {
12+
// customize the Vite config here
13+
return config;
14+
},
15+
};
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const parameters = {
2+
actions: { argTypesRegex: '^on[A-Z].*' },
3+
controls: {
4+
matchers: {
5+
color: /(background|color)$/i,
6+
date: /Date$/,
7+
},
8+
},
9+
};

examples/react-18/package.json

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "example-react-18",
3+
"private": true,
4+
"version": "0.0.0",
5+
"description": "",
6+
"main": "index.js",
7+
"scripts": {
8+
"storybook": "start-storybook --port 6018",
9+
"build-storybook": "build-storybook",
10+
"preview-storybook": "http-server storybook-static --port 6018 --silent",
11+
"test": "wait-on tcp:6018 && test-storybook --url 'http://localhost:6018'",
12+
"test-ci": "run-p --race test preview-storybook"
13+
},
14+
"author": "",
15+
"license": "MIT",
16+
"dependencies": {
17+
"react": "^18.0.0",
18+
"react-dom": "^18.0.0"
19+
},
20+
"devDependencies": {
21+
"@storybook/addon-a11y": "^6.5.0-alpha.58",
22+
"@storybook/addon-docs": "^6.5.0-alpha.58",
23+
"@storybook/addon-essentials": "^6.5.0-alpha.58",
24+
"@storybook/builder-vite": "workspace:*",
25+
"@storybook/react": "^6.5.0-alpha.58",
26+
"@storybook/test-runner": "^0.0.4",
27+
"@vitejs/plugin-react": "^1.3.0",
28+
"http-server": "^14.1.0",
29+
"jest": "^27.5.1",
30+
"npm-run-all": "^4.1.5",
31+
"vite": "2.9.0",
32+
"wait-on": "^6.0.1"
33+
}
34+
}

examples/react-18/stories/Button.jsx

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import PropTypes from 'prop-types';
2+
import './button.css';
3+
4+
/**
5+
* Primary UI component for user interaction
6+
*/
7+
export const Button = ({ primary, backgroundColor, size, label, ...props }) => {
8+
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
9+
return (
10+
<button
11+
type="button"
12+
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
13+
style={backgroundColor && { backgroundColor }}
14+
{...props}
15+
>
16+
{label}
17+
</button>
18+
);
19+
};
20+
21+
Button.propTypes = {
22+
/**
23+
* Is this the principal call to action on the page?
24+
*/
25+
primary: PropTypes.bool,
26+
/**
27+
* What background color to use
28+
*/
29+
backgroundColor: PropTypes.string,
30+
/**
31+
* How large should the button be?
32+
*/
33+
size: PropTypes.oneOf(['small', 'medium', 'large']),
34+
/**
35+
* Button contents
36+
*/
37+
label: PropTypes.string.isRequired,
38+
/**
39+
* Optional click handler
40+
*/
41+
onClick: PropTypes.func,
42+
};
43+
44+
Button.defaultProps = {
45+
backgroundColor: null,
46+
primary: false,
47+
size: 'medium',
48+
onClick: undefined,
49+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Button } from './Button';
2+
3+
export default {
4+
// no title, to demonstrate autotitle
5+
component: Button,
6+
argTypes: {
7+
backgroundColor: { control: 'color' },
8+
},
9+
};
10+
11+
export const Primary = {
12+
args: {
13+
primary: true,
14+
label: 'Button',
15+
},
16+
};
17+
18+
export const Secondary = {
19+
args: {
20+
label: 'Button',
21+
},
22+
};
23+
24+
export const Large = {
25+
args: {
26+
size: 'large',
27+
label: 'Button',
28+
},
29+
};
30+
31+
export const Small = {
32+
args: {
33+
size: 'small',
34+
label: 'Button',
35+
},
36+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export function EnvironmentVariables() {
2+
return (
3+
<div>
4+
<h1>import . meta . env:</h1>
5+
<div>{JSON.stringify(import.meta.env, null, 2)}</div>
6+
<h1>import . meta . env . STORYBOOK:</h1>
7+
<div>{import.meta.env.STORYBOOK}</div>
8+
<h1>import . meta . env . STORYBOOK_ENV_VAR:</h1>
9+
<div>{import.meta.env.STORYBOOK_ENV_VAR}</div>
10+
<h1>import . meta . env . VITE_ENV_VAR:</h1>
11+
<div>{import.meta.env.VITE_ENV_VAR}</div>
12+
<h1>import . meta . env . ENV_VAR:</h1>
13+
<div>{import.meta.env.ENV_VAR}</div>
14+
</div>
15+
);
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { EnvironmentVariables } from './EnvironmentVariables';
2+
3+
export default {
4+
title: 'Environment Variables',
5+
component: EnvironmentVariables,
6+
};
7+
8+
export const Info = () => <EnvironmentVariables />;

examples/react-18/stories/Header.jsx

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import PropTypes from 'prop-types';
2+
3+
import { Button } from './Button';
4+
import './header.css';
5+
6+
export const Header = ({ user, onLogin, onLogout, onCreateAccount }) => (
7+
<header>
8+
<div className="wrapper">
9+
<div>
10+
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
11+
<g fill="none" fillRule="evenodd">
12+
<path d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z" fill="#FFF" />
13+
<path d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z" fill="#555AB9" />
14+
<path d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z" fill="#91BAF8" />
15+
</g>
16+
</svg>
17+
<h1>Acme</h1>
18+
</div>
19+
<div>
20+
{user ? (
21+
<Button size="small" onClick={onLogout} label="Log out" />
22+
) : (
23+
<>
24+
<Button size="small" onClick={onLogin} label="Log in" />
25+
<Button primary size="small" onClick={onCreateAccount} label="Sign up" />
26+
</>
27+
)}
28+
</div>
29+
</div>
30+
</header>
31+
);
32+
33+
Header.propTypes = {
34+
user: PropTypes.shape({}),
35+
onLogin: PropTypes.func.isRequired,
36+
onLogout: PropTypes.func.isRequired,
37+
onCreateAccount: PropTypes.func.isRequired,
38+
};
39+
40+
Header.defaultProps = {
41+
user: null,
42+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Header } from './Header';
2+
3+
export default {
4+
title: 'Example/Header',
5+
component: Header,
6+
};
7+
8+
const Template = (args) => <Header {...args} />;
9+
10+
export const LoggedIn = Template.bind({});
11+
LoggedIn.args = {
12+
user: {},
13+
};
14+
15+
export const LoggedOut = Template.bind({});
16+
LoggedOut.args = {};

0 commit comments

Comments
 (0)