Skip to content

Commit

Permalink
Merge pull request #12470 from storybookjs/react/refresh2
Browse files Browse the repository at this point in the history
React: Add react-refresh
  • Loading branch information
shilman authored Sep 20, 2020
2 parents de35c21 + 336a1f3 commit ea341b5
Show file tree
Hide file tree
Showing 18 changed files with 209 additions and 13 deletions.
8 changes: 7 additions & 1 deletion addons/storyshots/storyshots-puppeteer/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { MatchImageSnapshotOptions } from 'jest-image-snapshot';
import { Base64ScreenShotOptions, Browser, DirectNavigationOptions, Page, ElementHandle } from 'puppeteer';
import {
Base64ScreenShotOptions,
Browser,
DirectNavigationOptions,
Page,
ElementHandle,
} from 'puppeteer';

export interface Context {
kind: string;
Expand Down
2 changes: 2 additions & 0 deletions app/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"dependencies": {
"@babel/preset-flow": "^7.10.4",
"@babel/preset-react": "^7.10.4",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.2",
"@storybook/addons": "6.1.0-alpha.11",
"@storybook/core": "6.1.0-alpha.11",
"@storybook/node-logger": "6.1.0-alpha.11",
Expand All @@ -53,6 +54,7 @@
"react-dev-utils": "^10.0.0",
"react-docgen-typescript-plugin": "^0.5.2",
"react-dom": "^16.8.3",
"react-refresh": "^0.8.3",
"regenerator-runtime": "^0.13.3",
"ts-dedent": "^1.1.1",
"webpack": "^4.43.0"
Expand Down
6 changes: 2 additions & 4 deletions app/react/src/server/framework-preset-cra.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Configuration } from 'webpack';
import { logger } from '@storybook/node-logger';
import { isReactScriptsInstalled } from './cra-config';
import type { StorybookOptions } from './types';

type Preset = string | { name: string };

Expand All @@ -19,10 +20,7 @@ const checkForNewPreset = (presetsList: Preset[]) => {
}
};

export function webpackFinal(
config: Configuration,
{ presetsList, configDir }: { presetsList: Preset[]; configDir: string }
) {
export function webpackFinal(config: Configuration, { presetsList }: StorybookOptions) {
if (isReactScriptsInstalled()) {
checkForNewPreset(presetsList);
}
Expand Down
3 changes: 2 additions & 1 deletion app/react/src/server/framework-preset-react-docgen.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ReactDocgenTypescriptPlugin from 'react-docgen-typescript-plugin';
import * as preset from './framework-preset-react-docgen';
import type { StorybookOptions } from './types';

describe('framework-preset-react-docgen', () => {
const babelPluginReactDocgenPath = require.resolve('babel-plugin-react-docgen');
Expand All @@ -13,7 +14,7 @@ describe('framework-preset-react-docgen', () => {

const config = preset.babel(babelConfig, {
typescriptOptions: { check: false, reactDocgen: 'react-docgen' },
});
} as StorybookOptions);

expect(config).toEqual({
babelrc: false,
Expand Down
2 changes: 1 addition & 1 deletion app/react/src/server/framework-preset-react-docgen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { TransformOptions } from '@babel/core';
import type { Configuration } from 'webpack';
import type { StorybookOptions } from '@storybook/core/types';
import ReactDocgenTypescriptPlugin from 'react-docgen-typescript-plugin';
import type { StorybookOptions } from './types';

export function babel(config: TransformOptions, { typescriptOptions }: StorybookOptions) {
const { reactDocgen } = typescriptOptions;
Expand Down
54 changes: 54 additions & 0 deletions app/react/src/server/framework-preset-react.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import webpack from 'webpack';
import * as preset from './framework-preset-react';
import type { StorybookOptions } from './types';

describe('framework-preset-react', () => {
const babelLoaderPath = require.resolve('babel-loader');
const reactRefreshPath = require.resolve('react-refresh/babel');
const webpackConfigMock: webpack.Configuration = {
mode: 'development',
plugins: [],
module: {
rules: [],
},
};

describe('webpackFinal', () => {
it('should return a config with fast refresh plugin when fast refresh is enabled', () => {
const config = preset.webpackFinal(webpackConfigMock, {
reactOptions: { fastRefresh: true },
} as StorybookOptions);

expect(config.module.rules).toEqual([
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: [
{
loader: babelLoaderPath,
options: {
plugins: [reactRefreshPath],
},
},
],
},
]);
});

it('should not return a config with fast refresh plugin when fast refresh is disabled', () => {
const config = preset.webpackFinal(webpackConfigMock, {
reactOptions: { fastRefresh: false },
} as StorybookOptions);

expect(config.module.rules).toEqual([]);
});

it('should not return a config with fast refresh plugin when mode is not development', () => {
const config = preset.webpackFinal({ ...webpackConfigMock, mode: 'production' }, {
reactOptions: { fastRefresh: true },
} as StorybookOptions);

expect(config.module.rules).toEqual([]);
});
});
});
37 changes: 37 additions & 0 deletions app/react/src/server/framework-preset-react.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { TransformOptions } from '@babel/core';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import type { Configuration } from 'webpack';
import { logger } from '@storybook/node-logger';
import type { StorybookOptions } from './types';

export function babelDefault(config: TransformOptions) {
return {
Expand All @@ -11,3 +15,36 @@ export function babelDefault(config: TransformOptions) {
plugins: [...(config.plugins || []), require.resolve('babel-plugin-add-react-displayname')],
};
}

export function webpackFinal(config: Configuration, { reactOptions }: StorybookOptions) {
const isDevelopment = config.mode === 'development';
const fastRefreshEnabled =
isDevelopment && (reactOptions?.fastRefresh || process.env.FAST_REFRESH === 'true');
if (fastRefreshEnabled) {
logger.info('=> Using React fast refresh feature.');
}
return {
...config,
module: {
...config.module,
rules: [
...config.module.rules,
fastRefreshEnabled && {
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve('babel-loader'),
options: {
plugins: [require.resolve('react-refresh/babel')],
},
},
],
},
].filter(Boolean),
},
plugins: [...config.plugins, fastRefreshEnabled && new ReactRefreshWebpackPlugin()].filter(
Boolean
),
};
}
10 changes: 10 additions & 0 deletions app/react/src/server/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { StorybookOptions as BaseOptions } from '@storybook/core/types';

/**
* The internal options object, used by Storybook frameworks and addons.
*/
export interface StorybookOptions extends BaseOptions {
reactOptions?: {
fastRefresh?: boolean;
};
}
10 changes: 10 additions & 0 deletions app/react/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { StorybookConfig as BaseConfig } from '@storybook/core/types';

/**
* The interface for Storybook configuration in `main.ts` files.
*/
export interface StorybookConfig extends BaseConfig {
reactOptions?: {
fastRefresh?: boolean;
};
}
21 changes: 21 additions & 0 deletions docs/workflows/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,27 @@ module.exports = {
};
```

### How do I setup React Fast Refresh with Storybook?

Fast refresh is an opt-in feature that can be used in Storybook React.
There are two ways that you can enable it, go ahead and pick one:

* You can set a `FAST_REFRESH` environment variable in your `.env` file:
```
FAST_REFRESH=true
```

* Or you can set the following properties in your `.storybook/main.js` files:
```js
module.exports = {
reactOptions: {
fastRefresh: true,
}
};
```

**Note: Fast Refresh requires React 16.10 or higher and is only enabled in development mode.**

### Why is there no addons channel?

A common error is that an addon tries to access the "channel", but the channel is not set. This can happen in a few different cases:
Expand Down
3 changes: 1 addition & 2 deletions examples/html-kitchen-sink/stories/button.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,4 @@ export const Effect = () => {
return '<button id="button">I should be yellow</button>';
};

export const Script = () =>
'<div>JS alert</div><script>alert("hello")</script>';
export const Script = () => '<div>JS alert</div><script>alert("hello")</script>';
5 changes: 4 additions & 1 deletion examples/official-storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { StorybookConfig } from '@storybook/core/types';
import type { StorybookConfig } from '@storybook/react/types';

module.exports = {
stories: [
Expand All @@ -8,6 +8,9 @@ module.exports = {
'./stories/**/*.stories.@(js|ts|tsx|mdx)',
'./../../addons/docs/**/*.stories.tsx',
],
reactOptions: {
fastRefresh: true,
},
addons: [
{
name: '@storybook/addon-docs',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

export const Refresh = () => {
const [value, setValue] = React.useState('abc');
return (
<>
<input value={value} onChange={(event) => setValue(event.target.value)} />
<p>Change the input value then this text in the component.</p>
<p>The state of the input should be kept.</p>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';
import { Refresh } from './react-refresh-example';

export default {
title: 'Core/React Refresh',
};

export const Default = () => <Refresh />;
2 changes: 1 addition & 1 deletion examples/react-ts/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { StorybookConfig } from '@storybook/core/types';
import type { StorybookConfig } from '@storybook/react/types';

module.exports = {
stories: ['./src/*.stories.*'],
Expand Down
10 changes: 9 additions & 1 deletion lib/core/src/server/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,19 @@ async function getPreviewWebpackConfig(options, presets) {
const babelOptions = await presets.apply('babel', {}, { ...options, typescriptOptions });
const entries = await presets.apply('entries', [], options);
const stories = await presets.apply('stories', [], options);
const frameworkOptions = await presets.apply(`${options.framework}Options`, {}, options);

return presets.apply(
'webpack',
{},
{ ...options, babelOptions, entries, stories, typescriptOptions }
{
...options,
babelOptions,
entries,
stories,
typescriptOptions,
[`${options.framework}Options`]: frameworkOptions,
}
);
}

Expand Down
3 changes: 3 additions & 0 deletions lib/core/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
import type { PluginOptions } from 'react-docgen-typescript-plugin';
import { Configuration } from 'webpack';

type Preset = string | { name: string };

/**
* The interface for Storybook configuration in `main.ts` files.
*/
Expand Down Expand Up @@ -36,6 +38,7 @@ export interface StorybookConfig {
*/
export interface StorybookOptions {
configType: 'DEVELOPMENT' | 'PRODUCTION';
presetsList: Preset[];
typescriptOptions: TypescriptOptions;
}

Expand Down
26 changes: 25 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4738,6 +4738,18 @@
"@parcel/utils" "^1.11.0"
physical-cpu-count "^2.0.0"

"@pmmmwh/react-refresh-webpack-plugin@^0.4.2":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.2.tgz#1f9741e0bde9790a0e13272082ed7272a083620d"
integrity sha512-Loc4UDGutcZ+Bd56hBInkm6JyjyCwWy4t2wcDXzN8EDPANgVRj0VP8Nxn0Zq2pc+WKauZwEivQgbDGg4xZO20A==
dependencies:
ansi-html "^0.0.7"
error-stack-parser "^2.0.6"
html-entities "^1.2.1"
native-url "^0.2.6"
schema-utils "^2.6.5"
source-map "^0.7.3"

"@popperjs/core@^2.4.4":
version "2.4.4"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.4.tgz#11d5db19bd178936ec89cd84519c4de439574398"
Expand Down Expand Up @@ -14202,7 +14214,7 @@ error-ex@^1.2.0, error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"

error-stack-parser@^2.0.0, error-stack-parser@^2.0.1:
error-stack-parser@^2.0.0, error-stack-parser@^2.0.1, error-stack-parser@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8"
integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==
Expand Down Expand Up @@ -23593,6 +23605,13 @@ napi-macros@~2.0.0:
resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b"
integrity sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==

native-url@^0.2.6:
version "0.2.6"
resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.2.6.tgz#ca1258f5ace169c716ff44eccbddb674e10399ae"
integrity sha512-k4bDC87WtgrdD362gZz6zoiXQrl40kYlBmpfmSjwRO1VU0V5ccwJTlxuE72F6m3V0vc1xOf6n3UCP9QyerRqmA==
dependencies:
querystring "^0.2.0"

natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
Expand Down Expand Up @@ -27579,6 +27598,11 @@ react-popper@^2.2.3:
react-fast-compare "^3.0.1"
warning "^4.0.2"

react-refresh@^0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==

[email protected]:
version "3.0.1"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.0.1.tgz#e5565350d8069cc9966b5998d3fe3befe3d243ac"
Expand Down

0 comments on commit ea341b5

Please sign in to comment.