Skip to content

Commit

Permalink
Support next/image component in Next.js 12/13 properly
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinpalkovic committed Dec 1, 2022
1 parent 4c4f502 commit 2d30461
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 113 deletions.
2 changes: 1 addition & 1 deletion code/frameworks/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
},
"devDependencies": {
"@storybook/addon-actions": "7.0.0-alpha.56",
"next": "^12.2.4",
"next": "^13.0.5",
"typescript": "^4.9.3",
"webpack": "^5.65.0"
},
Expand Down
77 changes: 56 additions & 21 deletions code/frameworks/nextjs/src/images/next-image-stub.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,67 @@
/* eslint-disable no-underscore-dangle, @typescript-eslint/no-non-null-assertion */
import * as React from 'react';
import type * as _NextImage from 'next/image';
import type * as _NextLegacyImage from 'next/legacy/image';
import semver from 'semver';

// next v9 (doesn't have next/image)
if (semver.gt(process.env.__NEXT_VERSION!, '9.0.0')) {
const NextImage = require('next/image') as typeof _NextImage;
const defaultLoader = ({ src, width, quality }: _NextImage.ImageLoaderProps) => {
const missingValues = [];
if (!src) {
missingValues.push('src');
}

if (!width) {
missingValues.push('width');
}

if (missingValues.length > 0) {
throw new Error(
`Next Image Optimization requires ${missingValues.join(
', '
)} to be provided. Make sure you pass them as props to the \`next/image\` component. Received: ${JSON.stringify(
{
src,
width,
quality,
}
)}`
);
}

return `${src}?w=${width}&q=${quality ?? 75}`;
};

const NextImage = require('next/image') as typeof _NextImage;

const OriginalNextImage = NextImage.default;
const OriginalNextImage = NextImage.default;

Object.defineProperty(NextImage, 'default', {
Object.defineProperty(NextImage, 'default', {
configurable: true,
value: (props: _NextImage.ImageProps) => {
return <OriginalNextImage {...props} loader={props.loader ?? defaultLoader} />;
},
});

if (semver.satisfies(process.env.__NEXT_VERSION!, '^13.0.0')) {
const LegacyNextImage = require('next/legacy/image') as typeof _NextLegacyImage;
const OriginalNextLegacyImage = LegacyNextImage.default;

Object.defineProperty(OriginalNextLegacyImage, 'default', {
configurable: true,
value: (props: _NextImage.ImageProps) =>
typeof props.src === 'string' ? (
<OriginalNextImage {...props} unoptimized blurDataURL={props.src} />
) : (
<OriginalNextImage {...props} unoptimized />
),
value: (props: _NextLegacyImage.ImageProps) => (
<OriginalNextLegacyImage {...props} loader={props.loader ?? defaultLoader} />
),
});
}

// https://github.com/vercel/next.js/issues/36417#issuecomment-1117360509
if (
semver.gte(process.env.__NEXT_VERSION!, '12.1.5') &&
semver.lt(process.env.__NEXT_VERSION!, '12.2.0')
) {
Object.defineProperty(NextImage, '__esModule', {
configurable: true,
value: true,
});
}
if (semver.satisfies(process.env.__NEXT_VERSION!, '^12.0.0')) {
const NextFutureImage = require('next/future/image') as typeof _NextImage;
const OriginalNextFutureImage = NextFutureImage.default;

Object.defineProperty(OriginalNextFutureImage, 'default', {
configurable: true,
value: (props: _NextImage.ImageProps) => (
<OriginalNextFutureImage {...props} loader={props.loader ?? defaultLoader} />
),
});
}
29 changes: 29 additions & 0 deletions code/frameworks/nextjs/src/nextImport/webpack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { Configuration as WebpackConfig } from 'webpack';
import semver from 'semver';
import { IgnorePlugin } from 'webpack';
import { getNextjsVersion } from '../utils';

export function configureNextImport(baseConfig: WebpackConfig) {
const nextJSVersion = getNextjsVersion();

const isNext12 = semver.satisfies(nextJSVersion, '~12');
const isNext13 = semver.satisfies(nextJSVersion, '~13');

baseConfig.plugins = baseConfig.plugins ?? [];

if (!isNext13) {
baseConfig.plugins.push(
new IgnorePlugin({
resourceRegExp: /next\/legacy\/image$/,
})
);
}

if (!isNext12) {
baseConfig.plugins.push(
new IgnorePlugin({
resourceRegExp: /next\/future\/image$/,
})
);
}
}
2 changes: 2 additions & 0 deletions code/frameworks/nextjs/src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { configureStyledJsx } from './styledJsx/webpack';
import { configureImages } from './images/webpack';
import { configureRuntimeNextjsVersionResolution } from './utils';
import type { FrameworkOptions, StorybookConfig } from './types';
import { configureNextImport } from './nextImport/webpack';

export const addons: PresetProperty<'addons', StorybookConfig> = [
dirname(require.resolve(join('@storybook/preset-react-webpack', 'package.json'))),
Expand Down Expand Up @@ -122,6 +123,7 @@ export const webpackFinal: StorybookConfig['webpackFinal'] = async (baseConfig,
configDir: options.configDir,
});

configureNextImport(baseConfig);
configureRuntimeNextjsVersionResolution(baseConfig);
configureImports(baseConfig);
configureCss(baseConfig, nextConfig);
Expand Down
44 changes: 41 additions & 3 deletions code/frameworks/nextjs/template/stories/Image.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,48 @@ import Image from 'next/image';
// eslint-disable-next-line import/extensions
import StackAlt from '../../assets/colors.svg';

const Component = () => <Image src={StackAlt} placeholder="blur" />;

export default {
component: Component,
component: Image,
args: {
src: StackAlt,
alt: 'Stack Alt',
},
};

export const Default = {};

export const BlurredPlaceholder = {
args: {
placeholder: 'blur',
},
};

export const BlurredAbsolutePlaceholder = {
args: {
src: 'https://via.placeholder.com/100',
width: 100,
height: 100,
blurDataURL:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABP5JREFUWEeNlwtz2zgMhEGKsv/k9XFp82xe11763yOJVGcXC4m2czM3GYa0E2s/LACSTi+vv1czM/7CvPpqxY/ejPeS3khmFiPHOiVLHKaZi4ux4j18GpMlS6cALupEQBCKQM4BdnGzjIcJgs//QBxAPQAem55f3yL4PWJIdyCyhlMfPdYZot0cwj3Ayg/5JwHA13paen7pADphxr/n5VI8JQsHCCGQ3gVGLLsxQ3h/LYSn5383B05rwNOws3Z576LOTOduvwfrOd5FtVat4akx0uPTrw8BNuUz23vLsY7hmg7i4ipqM2saiAdruNuirh4ff0bNdb3Qy3vkvfAQwrkHoDxTTZtDCOKrC1bMEkdnsQh/PLyetOGHkXeRAgQAQ84efQcBdUhvFofoulpdm9WGGTA+AJEe7l+i37a2c371tCFKs5zzJjxQNBMi1im7OCudP2aNghJuzZaGdSMEZjpwf/t0UgNdg9DOyLGLnY0BUHlzwVtNDkgEQhBeKkb1tUDgQrq7frwAiIJi5BKAeIFgHk5mOpPzvgltOfcoK0Rrs7lWHwsgqtXarK3N0u23h5Ne8+3Cqxn5RYSMfHCAMgDAx4CBWlA9RAGw0GA/ol0gvFB4WjAvBAFUa83SzdUdAbYMqp28uHpxCRefxwAYhksAFBlthxCiXig+zT4TYqkC+Hq7OdAfJv8lPpZiZShWBBIuRP+jspDb2lwcDkzz7OLzbO/zvAHAoXTz5eYMQL0t2yHAiCFcfPY1QDwNFylA5bPoFpsV9fsEiMl8dhcc4PP1CYD3drYcBYdIKQrx0cbRxd2JHSDcQ297/vvoZ5smRC+AyV2AQ+nm03evge08Tyy4jGqXzWWEoIvTgXHU38pWiNgH4ixB/ukAcy/xycXfp4kwdAAAt399W+OCgMjxILQacxvRQ3gEwHgKUIr/rz53CuDFNyP/Eob4+/vEWkBq6AAA/HIi62n/Lk67Q7wDYQ0UpQB7hc54T4E6gACLTYxeAwB0YKZL6U4ATEGIBwCs7qPfQJCCHkCnoK50noJKcXcAojsEAJZZKXhgCoziGKxqWV8IMNp4kP2aC+oB0TMFvhGxDQHQfIPhDrilwKOm/YCZASAHfgBABQjr3f7CyAkA0cPB03AQULRhKd4xAIjzHymo2Gp7gN0FAMAVOoA2fPz03a9ssh/RM7Iz8QKIzYF9HyB0XEZ1xJ4DzNoDOAfAslhDDTyjDfv8A2AcBeCiu/jBHQEgxnYW6Kp6BlCVAkQM8VnieF2Xyr0ivXy+XvsCzKOihwNHCCryw8HrQXVB8dgFeRfAVQiXjMbIIgXINQYB2H7Kf5wF/2Ar7h0AgKKGuAP4zOjhzlkLbpcRXKRZhNUjxG6HIQDOjN47gCn4+fWW3xVS9urPESEEwwHMo9IhAGxS2ISiA1iEnQOoA4hXRAwItp7WzL9Ow18ESJaw/ar4NgeOR49cAHCAnaH8swBhv+6CBGjeBSxEOUAI7HyKHkD4O9xKb3/feQouAI4uLBciHRRHmgbfA7h/xFc9AngNBADthvii1sMOiPwDAFeyt6s7FSFS4PmnA1v0vQvqDqQKAAPE/weAUuEgsj8c+H11Twdw/AKANXA82EDr5cJBEEzB3oI4Mb0AdR3nNw8vQnegWuvqAABwJFJEBwDgNdA7IOs3gL0LhuJdwBY8c4BfNnDdVgooHiOqn/b7JoSW/QODjTHXhU7hMQAAAABJRU5ErkJggg==',
placeholder: 'blur',
},
};

export const FilledParent = {
args: {
fill: true,
},
decorator: [
(Story) => <div style={{ width: 500, height: 500, position: 'relative' }}>{Story()}</div>,
],
};

export const Sized = {
args: {
fill: true,
sizes: '(max-width: 600px) 100vw, 600px',
decorator: [
(Story) => <div style={{ width: 800, height: 800, position: 'relative' }}>{Story()}</div>,
],
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import Image from 'next/future/image';
// eslint-disable-next-line import/extensions
import StackAlt from '../../assets/colors.svg';

export default {
component: Image,
args: {
src: StackAlt,
alt: 'Stack Alt',
},
};

export const Default = {};

export const BlurredPlaceholder = {
args: {
placeholder: 'blur',
},
};

export const BlurredAbsolutePlaceholder = {
args: {
src: 'https://via.placeholder.com/100',
width: 100,
height: 100,
blurDataURL:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABP5JREFUWEeNlwtz2zgMhEGKsv/k9XFp82xe11763yOJVGcXC4m2czM3GYa0E2s/LACSTi+vv1czM/7CvPpqxY/ejPeS3khmFiPHOiVLHKaZi4ux4j18GpMlS6cALupEQBCKQM4BdnGzjIcJgs//QBxAPQAem55f3yL4PWJIdyCyhlMfPdYZot0cwj3Ayg/5JwHA13paen7pADphxr/n5VI8JQsHCCGQ3gVGLLsxQ3h/LYSn5383B05rwNOws3Z576LOTOduvwfrOd5FtVat4akx0uPTrw8BNuUz23vLsY7hmg7i4ipqM2saiAdruNuirh4ff0bNdb3Qy3vkvfAQwrkHoDxTTZtDCOKrC1bMEkdnsQh/PLyetOGHkXeRAgQAQ84efQcBdUhvFofoulpdm9WGGTA+AJEe7l+i37a2c371tCFKs5zzJjxQNBMi1im7OCudP2aNghJuzZaGdSMEZjpwf/t0UgNdg9DOyLGLnY0BUHlzwVtNDkgEQhBeKkb1tUDgQrq7frwAiIJi5BKAeIFgHk5mOpPzvgltOfcoK0Rrs7lWHwsgqtXarK3N0u23h5Ne8+3Cqxn5RYSMfHCAMgDAx4CBWlA9RAGw0GA/ol0gvFB4WjAvBAFUa83SzdUdAbYMqp28uHpxCRefxwAYhksAFBlthxCiXig+zT4TYqkC+Hq7OdAfJv8lPpZiZShWBBIuRP+jspDb2lwcDkzz7OLzbO/zvAHAoXTz5eYMQL0t2yHAiCFcfPY1QDwNFylA5bPoFpsV9fsEiMl8dhcc4PP1CYD3drYcBYdIKQrx0cbRxd2JHSDcQ297/vvoZ5smRC+AyV2AQ+nm03evge08Tyy4jGqXzWWEoIvTgXHU38pWiNgH4ixB/ukAcy/xycXfp4kwdAAAt399W+OCgMjxILQacxvRQ3gEwHgKUIr/rz53CuDFNyP/Eob4+/vEWkBq6AAA/HIi62n/Lk67Q7wDYQ0UpQB7hc54T4E6gACLTYxeAwB0YKZL6U4ATEGIBwCs7qPfQJCCHkCnoK50noJKcXcAojsEAJZZKXhgCoziGKxqWV8IMNp4kP2aC+oB0TMFvhGxDQHQfIPhDrilwKOm/YCZASAHfgBABQjr3f7CyAkA0cPB03AQULRhKd4xAIjzHymo2Gp7gN0FAMAVOoA2fPz03a9ssh/RM7Iz8QKIzYF9HyB0XEZ1xJ4DzNoDOAfAslhDDTyjDfv8A2AcBeCiu/jBHQEgxnYW6Kp6BlCVAkQM8VnieF2Xyr0ivXy+XvsCzKOihwNHCCryw8HrQXVB8dgFeRfAVQiXjMbIIgXINQYB2H7Kf5wF/2Ar7h0AgKKGuAP4zOjhzlkLbpcRXKRZhNUjxG6HIQDOjN47gCn4+fWW3xVS9urPESEEwwHMo9IhAGxS2ISiA1iEnQOoA4hXRAwItp7WzL9Ow18ESJaw/ar4NgeOR49cAHCAnaH8swBhv+6CBGjeBSxEOUAI7HyKHkD4O9xKb3/feQouAI4uLBciHRRHmgbfA7h/xFc9AngNBADthvii1sMOiPwDAFeyt6s7FSFS4PmnA1v0vQvqDqQKAAPE/weAUuEgsj8c+H11Twdw/AKANXA82EDr5cJBEEzB3oI4Mb0AdR3nNw8vQnegWuvqAABwJFJEBwDgNdA7IOs3gL0LhuJdwBY8c4BfNnDdVgooHiOqn/b7JoSW/QODjTHXhU7hMQAAAABJRU5ErkJggg==',
placeholder: 'blur',
},
};

export const FilledParent = {
args: {
fill: true,
},
decorator: [
(Story) => <div style={{ width: 500, height: 500, position: 'relative' }}>{Story()}</div>,
],
};

export const Sized = {
args: {
fill: true,
sizes: '(max-width: 600px) 100vw, 600px',
decorator: [
(Story) => <div style={{ width: 800, height: 800, position: 'relative' }}>{Story()}</div>,
],
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import Image from 'next/legacy/image';
// eslint-disable-next-line import/extensions
import StackAlt from '../../assets/colors.svg';

export default {
component: Image,
args: {
src: StackAlt,
alt: 'Stack Alt',
},
};

export const Default = {};

export const BlurredPlaceholder = {
args: {
placeholder: 'blur',
},
};

export const BlurredAbsolutePlaceholder = {
args: {
src: 'https://via.placeholder.com/100',
width: 100,
height: 100,
blurDataURL:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABP5JREFUWEeNlwtz2zgMhEGKsv/k9XFp82xe11763yOJVGcXC4m2czM3GYa0E2s/LACSTi+vv1czM/7CvPpqxY/ejPeS3khmFiPHOiVLHKaZi4ux4j18GpMlS6cALupEQBCKQM4BdnGzjIcJgs//QBxAPQAem55f3yL4PWJIdyCyhlMfPdYZot0cwj3Ayg/5JwHA13paen7pADphxr/n5VI8JQsHCCGQ3gVGLLsxQ3h/LYSn5383B05rwNOws3Z576LOTOduvwfrOd5FtVat4akx0uPTrw8BNuUz23vLsY7hmg7i4ipqM2saiAdruNuirh4ff0bNdb3Qy3vkvfAQwrkHoDxTTZtDCOKrC1bMEkdnsQh/PLyetOGHkXeRAgQAQ84efQcBdUhvFofoulpdm9WGGTA+AJEe7l+i37a2c371tCFKs5zzJjxQNBMi1im7OCudP2aNghJuzZaGdSMEZjpwf/t0UgNdg9DOyLGLnY0BUHlzwVtNDkgEQhBeKkb1tUDgQrq7frwAiIJi5BKAeIFgHk5mOpPzvgltOfcoK0Rrs7lWHwsgqtXarK3N0u23h5Ne8+3Cqxn5RYSMfHCAMgDAx4CBWlA9RAGw0GA/ol0gvFB4WjAvBAFUa83SzdUdAbYMqp28uHpxCRefxwAYhksAFBlthxCiXig+zT4TYqkC+Hq7OdAfJv8lPpZiZShWBBIuRP+jspDb2lwcDkzz7OLzbO/zvAHAoXTz5eYMQL0t2yHAiCFcfPY1QDwNFylA5bPoFpsV9fsEiMl8dhcc4PP1CYD3drYcBYdIKQrx0cbRxd2JHSDcQ297/vvoZ5smRC+AyV2AQ+nm03evge08Tyy4jGqXzWWEoIvTgXHU38pWiNgH4ixB/ukAcy/xycXfp4kwdAAAt399W+OCgMjxILQacxvRQ3gEwHgKUIr/rz53CuDFNyP/Eob4+/vEWkBq6AAA/HIi62n/Lk67Q7wDYQ0UpQB7hc54T4E6gACLTYxeAwB0YKZL6U4ATEGIBwCs7qPfQJCCHkCnoK50noJKcXcAojsEAJZZKXhgCoziGKxqWV8IMNp4kP2aC+oB0TMFvhGxDQHQfIPhDrilwKOm/YCZASAHfgBABQjr3f7CyAkA0cPB03AQULRhKd4xAIjzHymo2Gp7gN0FAMAVOoA2fPz03a9ssh/RM7Iz8QKIzYF9HyB0XEZ1xJ4DzNoDOAfAslhDDTyjDfv8A2AcBeCiu/jBHQEgxnYW6Kp6BlCVAkQM8VnieF2Xyr0ivXy+XvsCzKOihwNHCCryw8HrQXVB8dgFeRfAVQiXjMbIIgXINQYB2H7Kf5wF/2Ar7h0AgKKGuAP4zOjhzlkLbpcRXKRZhNUjxG6HIQDOjN47gCn4+fWW3xVS9urPESEEwwHMo9IhAGxS2ISiA1iEnQOoA4hXRAwItp7WzL9Ow18ESJaw/ar4NgeOR49cAHCAnaH8swBhv+6CBGjeBSxEOUAI7HyKHkD4O9xKb3/feQouAI4uLBciHRRHmgbfA7h/xFc9AngNBADthvii1sMOiPwDAFeyt6s7FSFS4PmnA1v0vQvqDqQKAAPE/weAUuEgsj8c+H11Twdw/AKANXA82EDr5cJBEEzB3oI4Mb0AdR3nNw8vQnegWuvqAABwJFJEBwDgNdA7IOs3gL0LhuJdwBY8c4BfNnDdVgooHiOqn/b7JoSW/QODjTHXhU7hMQAAAABJRU5ErkJggg==',
placeholder: 'blur',
},
};
16 changes: 13 additions & 3 deletions code/lib/cli/src/repro-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const allTemplates = {
},
'cra/default-ts': {
name: 'Create React App (Typescript)',
script: 'npx create-react-app . --template typescript',
script: 'yarn create next-app . --template typescript',
// Re-enable once https://github.com/storybookjs/storybook/issues/19351 is fixed.
skipTasks: ['smoke-test'],
expected: {
Expand All @@ -21,9 +21,19 @@ export const allTemplates = {
builder: '@storybook/builder-webpack5',
},
},
'nextjs/12-js': {
name: 'Next.js v12 (JavaScript)',
script:
'yarn create next-app {{beforeDir}} -e https://github.com/vercel/next.js/tree/next-12-3-2/examples/hello-world && cd {{beforeDir}} && npm pkg set "dependencies.next"="^12" && yarn && git add . && git commit --amend --no-edit && cd ..',
expected: {
framework: '@storybook/nextjs',
renderer: '@storybook/react',
builder: '@storybook/builder-webpack5',
},
},
'nextjs/default-js': {
name: 'Next.js (JavaScript)',
script: 'npx create-next-app {{beforeDir}}',
script: 'yarn create next-app {{beforeDir}}',
expected: {
framework: '@storybook/nextjs',
renderer: '@storybook/react',
Expand All @@ -32,7 +42,7 @@ export const allTemplates = {
},
'nextjs/default-ts': {
name: 'Next.js (TypeScript)',
script: 'npx create-next-app {{beforeDir}} --typescript',
script: 'yarn create next-app {{beforeDir}} --typescript',
expected: {
framework: '@storybook/nextjs',
renderer: '@storybook/react',
Expand Down
Loading

0 comments on commit 2d30461

Please sign in to comment.