Upgrade Storybook to version 9

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-09-14 14:37:55 +01:00
parent f58e7f1d0d
commit ffac5071fe
38 changed files with 506 additions and 729 deletions
+64 -3
View File
@@ -35,15 +35,76 @@ const config: StorybookConfig = {
stories,
addons: [
getAbsolutePath('@storybook/addon-links'),
getAbsolutePath('@storybook/addon-essentials'),
getAbsolutePath('@storybook/addon-interactions'),
getAbsolutePath('@storybook/addon-themes'),
getAbsolutePath('@storybook/addon-storysource'),
getAbsolutePath('@storybook/addon-docs'),
],
framework: {
name: getAbsolutePath('@storybook/react-vite'),
options: {},
},
viteFinal: async config => {
// Add Node.js polyfills for browser compatibility
//
// When upgrading from Storybook 8 to 9 with the react-vite framework,
// Node.js polyfills are no longer automatically included by Vite.
// This causes "ReferenceError: process is not defined" errors in the browser
// when code tries to access Node.js globals like `process` and `util`.
//
// The @vitest/mocker (included with Storybook 9) expects MSW v2 APIs,
// but we want to keep MSW v1 for the rest of the monorepo to avoid
// breaking changes. This configuration provides the necessary polyfills
// and handles the MSW compatibility issue specifically for Storybook.
//
// These polyfills provide browser-compatible versions of Node.js globals:
// - process: Node.js process object with env
// - global -> globalThis: Maps Node.js global to browser's globalThis
//
// Without these, Backstage components that rely on Node.js APIs will fail
// to load in Storybook's browser environment.
config.define = {
...config.define,
global: 'globalThis',
'process.env': '{}',
process: '{ env: {}, browser: true }',
};
config.resolve = {
...config.resolve,
alias: {
...config.resolve?.alias,
// Provide Node.js polyfills for browser
process: 'process/browser',
util: 'util',
buffer: 'buffer',
stream: 'stream-browserify',
// Fix MSW v2 imports for @vitest/mocker compatibility
// @vitest/mocker expects MSW v2 APIs but we want to keep MSW v1 for the rest of the monorepo
'msw/browser': join(__dirname, 'msw-browser-shim.js'),
'msw/core/http': join(__dirname, 'msw-http-shim.js'),
},
};
// Optimize dependencies for better performance
config.optimizeDeps = {
...config.optimizeDeps,
include: [
...(config.optimizeDeps?.include || []),
'process/browser',
'util',
'buffer',
'stream-browserify',
],
// Exclude MSW to prevent optimization conflicts with our shims
exclude: [
...(config.optimizeDeps?.exclude || []),
'msw',
'msw/browser',
'msw/core/http',
],
};
return config;
},
};
export default config;
+47
View File
@@ -0,0 +1,47 @@
// MSW v2 browser compatibility shim for @vitest/mocker
// This provides MSW v2 exports using MSW v1 APIs to maintain compatibility
// while keeping the rest of the monorepo on MSW v1
try {
// Try to import from MSW v1
const { setupWorker, rest } = require('msw');
// Export in MSW v2 format expected by @vitest/mocker
module.exports = {
setupWorker,
// MSW v2 uses 'http' instead of 'rest', but we provide both for compatibility
http: rest,
rest, // Keep rest for any MSW v1 code
};
} catch (error) {
// Fallback if MSW is not available - provide minimal mock
console.warn(
'MSW not available, providing minimal mock for @vitest/mocker compatibility',
);
module.exports = {
setupWorker: () => ({
start: () => Promise.resolve(),
stop: () => {},
use: () => {},
resetHandlers: () => {},
}),
http: {
get: () => {},
post: () => {},
put: () => {},
delete: () => {},
patch: () => {},
head: () => {},
options: () => {},
},
rest: {
get: () => {},
post: () => {},
put: () => {},
delete: () => {},
patch: () => {},
head: () => {},
options: () => {},
},
};
}
+47
View File
@@ -0,0 +1,47 @@
// MSW v2 http compatibility shim for @vitest/mocker
// This provides MSW v2 http exports using MSW v1 APIs
try {
// Try to import from MSW v1
const { rest } = require('msw');
// Export MSW v1 'rest' as MSW v2 'http' for @vitest/mocker compatibility
module.exports = {
http: rest,
// Provide individual methods that @vitest/mocker might expect
get: rest.get,
post: rest.post,
put: rest.put,
delete: rest.delete,
patch: rest.patch,
head: rest.head,
options: rest.options,
all: rest.all,
};
} catch (error) {
// Fallback if MSW is not available
console.warn(
'MSW not available, providing minimal http mock for @vitest/mocker compatibility',
);
const noop = () => {};
module.exports = {
http: {
get: noop,
post: noop,
put: noop,
delete: noop,
patch: noop,
head: noop,
options: noop,
all: noop,
},
get: noop,
post: noop,
put: noop,
delete: noop,
patch: noop,
head: noop,
options: noop,
all: noop,
};
}
+10 -2
View File
@@ -2,8 +2,8 @@ import React, { useEffect } from 'react';
import { TestApiProvider } from '@backstage/test-utils';
import { Content, AlertDisplay } from '@backstage/core-components';
import { apis } from './support/apis';
import type { Decorator, Preview } from '@storybook/react';
import { useGlobals } from '@storybook/preview-api';
import type { Decorator, Preview } from '@storybook/react-vite';
import { useGlobals } from 'storybook/preview-api';
import { UnifiedThemeProvider, themes } from '@backstage/theme';
// Default Backstage theme CSS (from packages/ui)
@@ -52,20 +52,24 @@ const preview: Preview = {
},
parameters: {
layout: 'fullscreen',
backgrounds: {
disable: true,
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
options: {
storySort: {
order: ['Backstage UI', 'Plugins', 'Layout', 'Navigation'],
},
},
viewport: {
viewports: {
initial: {
@@ -82,6 +86,10 @@ const preview: Preview = {
},
},
},
docs: {
codePanel: true,
},
},
decorators: [
Story => {
+5 -8
View File
@@ -129,13 +129,10 @@
"@octokit/rest": "^19.0.3",
"@playwright/test": "^1.32.3",
"@spotify/eslint-plugin": "^15.0.0",
"@storybook/addon-essentials": "^8.6.12",
"@storybook/addon-interactions": "^8.6.12",
"@storybook/addon-links": "^8.6.12",
"@storybook/addon-storysource": "^8.6.12",
"@storybook/addon-themes": "^8.6.12",
"@storybook/react": "^8.6.12",
"@storybook/react-vite": "^8.6.12",
"@storybook/addon-docs": "^9.1.5",
"@storybook/addon-links": "^9.1.5",
"@storybook/addon-themes": "^9.1.5",
"@storybook/react-vite": "^9.1.5",
"@techdocs/cli": "workspace:*",
"@types/cacheable-request": "^8.3.6",
"@types/memjs": "^1.3.3",
@@ -160,7 +157,7 @@
"shx": "^0.4.0",
"sloc": "^0.3.1",
"sort-package-json": "^2.8.0",
"storybook": "^8.6.12",
"storybook": "^9.1.5",
"typedoc": "^0.28.0",
"typescript": "~5.7.0",
"vite": "^7.1.2"
+2 -4
View File
@@ -40,18 +40,16 @@
"dependencies": {
"@base-ui-components/react": "1.0.0-alpha.7",
"@remixicon/react": "^4.6.0",
"@storybook/test": "^8.6.12",
"@tanstack/react-table": "^8.21.3",
"clsx": "^2.1.1",
"react-aria-components": "^1.10.1"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@storybook/react": "^8.6.12",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"chalk": "^5.4.1",
"eslint-plugin-storybook": "^0.12.0",
"eslint-plugin-storybook": "^9.1.5",
"glob": "^11.0.1",
"globals": "^15.11.0",
"lightningcss": "^1.29.1",
@@ -59,7 +57,7 @@
"react": "^18.0.2",
"react-dom": "^18.0.2",
"react-router-dom": "^6.3.0",
"storybook": "^8.6.12"
"storybook": "^9.1.5"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0",
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Avatar } from './index';
import { Flex } from '../..';
@@ -15,7 +15,7 @@
*/
import { ReactNode } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Box } from './Box';
import { Flex } from '../Flex';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Button } from './Button';
import { Flex } from '../Flex';
import { Text } from '../Text';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { ButtonIcon } from './ButtonIcon';
import { Flex } from '../Flex';
import { Text } from '../Text';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { ButtonLink } from './ButtonLink';
import { Flex } from '../Flex';
import { Text } from '../Text';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Card, CardHeader, CardBody, CardFooter } from './Card';
import { IconNames, Text } from '../..';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Checkbox } from './Checkbox';
import { Flex } from '../Flex';
import { Text } from '../Text';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Collapsible } from './Collapsible';
import { Button } from '../Button';
import { Box } from '../Box';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Box } from '../Box/Box';
import { Container } from './Container';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { TextField, Input, Form } from 'react-aria-components';
import { FieldError } from './FieldError';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { FieldLabel } from './FieldLabel';
const meta = {
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Flex } from './Flex';
import { Text } from '../Text';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Grid } from './Grid';
import type { GridItemProps } from './types';
import { Box } from '../Box/Box';
@@ -23,7 +23,7 @@ import { Flex } from '../Flex';
const meta = {
title: 'Backstage UI/Grid',
component: Grid.Root,
} satisfies Meta<typeof Grid>;
} satisfies Meta<typeof Grid.Root>;
export default meta;
type Story = StoryObj<typeof meta>;
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj, StoryFn } from '@storybook/react';
import type { Meta, StoryObj, StoryFn } from '@storybook/react-vite';
import { Header } from './Header';
import type { HeaderTab } from './types';
import {
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj, StoryFn } from '@storybook/react';
import type { Meta, StoryObj, StoryFn } from '@storybook/react-vite';
import { HeaderPage } from './HeaderPage';
import type { HeaderTab } from '../Header/types';
import { MemoryRouter } from 'react-router-dom';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { Meta, StoryObj } from '@storybook/react';
import { Meta, StoryObj } from '@storybook/react-vite';
import { Icon } from './Icon';
import { IconProvider } from './provider';
import { icons } from './icons';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryFn, StoryObj } from '@storybook/react';
import type { Meta, StoryFn, StoryObj } from '@storybook/react-vite';
import { Link } from './Link';
import { Flex } from '../Flex';
import { Text } from '../Text';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import {
MenuTrigger,
SubmenuTrigger,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { RadioGroup, Radio } from './RadioGroup';
const meta = {
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { ScrollArea } from './ScrollArea';
import { Text } from '../Text/Text';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { SearchField } from './SearchField';
import { Form } from 'react-aria-components';
import { Icon } from '../Icon';
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Select } from './Select';
import { Flex } from '../Flex';
import { Form } from 'react-aria-components';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Skeleton } from './Skeleton';
import { Flex } from '../Flex';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Switch } from './Switch';
const meta = {
@@ -15,7 +15,7 @@
*/
import { useState } from 'react';
import type { Meta, StoryFn, StoryObj } from '@storybook/react';
import type { Meta, StoryFn, StoryObj } from '@storybook/react-vite';
import {
Table,
TableHeader,
@@ -16,7 +16,7 @@
// TODO: Bring useArgs() back when we update Storybook to 9
// import { useArgs } from 'storybook/preview-api';
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { TablePagination } from './TablePagination';
const meta = {
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryFn, StoryObj } from '@storybook/react';
import type { Meta, StoryFn, StoryObj } from '@storybook/react-vite';
import { Tabs, TabList, Tab, TabPanel } from './Tabs';
import { MemoryRouter } from 'react-router-dom';
import { Box } from '../Box';
@@ -15,7 +15,7 @@
*/
import { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { TagGroup, Tag } from '.';
import type { Selection } from 'react-aria-components';
import { Flex, Icon, IconNames } from '../../';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Text } from './Text';
import { Flex } from '../Flex';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { TextField } from './TextField';
import { Form } from 'react-aria-components';
import { Icon } from '../Icon';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Placement } from '@react-types/overlays';
import { TooltipTrigger, Tooltip } from './Tooltip';
import { Button } from '../Button/Button';
@@ -41,7 +41,7 @@ const meta = {
control: { type: 'number' },
},
},
render: ({ tooltip, isOpen, isDisabled, placement, delay, closeDelay }) => (
render: ({ children, isOpen, isDisabled, placement, delay, closeDelay }) => (
<TooltipTrigger
isOpen={isOpen}
isDisabled={isDisabled}
@@ -49,11 +49,11 @@ const meta = {
closeDelay={closeDelay}
>
<Button>Button</Button>
<Tooltip placement={placement}>{tooltip ?? 'I am a tooltip'}</Tooltip>
<Tooltip placement={placement}>{children ?? 'I am a tooltip'}</Tooltip>
</TooltipTrigger>
),
} as Meta<{
tooltip?: string;
children?: string;
isOpen?: boolean;
isDisabled?: boolean;
placement?: Placement;
@@ -66,7 +66,7 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
tooltip: 'I am a tooltip',
children: 'I am a tooltip',
},
};
@@ -102,14 +102,14 @@ export const OrthogonalPlacements: Story = {
...Default.args,
isOpen: true,
},
render: ({ isOpen, tooltip }) => {
render: ({ isOpen, children }) => {
return (
<TooltipTrigger isOpen={isOpen}>
<Button>Button</Button>
<Tooltip placement="top">{tooltip}</Tooltip>
<Tooltip placement="right">{tooltip}</Tooltip>
<Tooltip placement="bottom">{tooltip}</Tooltip>
<Tooltip placement="left">{tooltip}</Tooltip>
<Tooltip placement="top">{children}</Tooltip>
<Tooltip placement="right">{children}</Tooltip>
<Tooltip placement="bottom">{children}</Tooltip>
<Tooltip placement="left">{children}</Tooltip>
</TooltipTrigger>
);
},
@@ -119,7 +119,7 @@ export const WithLongText: Story = {
args: {
...Default.args,
isOpen: true,
tooltip:
children:
'I am a tooltip with a very long text. orem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
},
};
+289 -670
View File
File diff suppressed because it is too large Load Diff