feat(repo): update storybook to v7 (#16174) (#16289)

This commit is contained in:
Katerina Skroumpelou 2023-04-24 17:11:41 +03:00 committed by GitHub
parent 4d5cc73c9b
commit 9bb5d0d7db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1832 additions and 4938 deletions

View File

@ -6,7 +6,7 @@
},
"ignorePatterns": ["**/*.ts"],
"plugins": ["@typescript-eslint", "@nx"],
"extends": [],
"extends": ["plugin:storybook/recommended"],
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off",
"no-restricted-imports": ["error", "create-nx-workspace"],

View File

@ -15,9 +15,9 @@ describe('Storybook executors for Angular', () => {
const angularStorybookLib = uniq('test-ui-ng-lib');
beforeAll(() => {
newProject();
createTestUILib(angularStorybookLib);
runCLI(`g @nx/angular:library ${angularStorybookLib} --no-interactive`);
runCLI(
`generate @nrwl/angular:storybook-configuration ${angularStorybookLib} --configureCypress --generateStories --generateCypressSpecs --no-interactive`
`generate @nx/angular:storybook-configuration ${angularStorybookLib} --configureCypress --generateStories --generateCypressSpecs --no-interactive`
);
});
@ -25,8 +25,7 @@ describe('Storybook executors for Angular', () => {
cleanupProject();
});
// TODO: Enable on SB7
xdescribe('serve and build storybook', () => {
describe('serve and build storybook', () => {
afterAll(() => killPorts());
it('should serve an Angular based Storybook setup', async () => {
@ -49,6 +48,7 @@ describe('Storybook executors for Angular', () => {
xdescribe('run cypress tests using storybook', () => {
it('should execute e2e tests using Cypress running against Storybook', async () => {
if (runCypressTests()) {
addTestButtonToUILib(angularStorybookLib);
writeFileSync(
tmpProjPath(
`apps/${angularStorybookLib}-e2e/src/e2e/test-button/test-button.component.cy.ts`
@ -82,10 +82,9 @@ describe('Storybook executors for Angular', () => {
});
});
export function createTestUILib(libName: string): void {
runCLI(`g @nrwl/angular:library ${libName} --no-interactive`);
function addTestButtonToUILib(libName: string): void {
runCLI(
`g @nrwl/angular:component test-button --project=${libName} --no-interactive`
`g @nx/angular:component test-button --project=${libName} --no-interactive`
);
writeFileSync(

View File

@ -1,6 +1,7 @@
import {
checkFilesExist,
cleanupProject,
getSelectedPackageManager,
killPorts,
readJson,
runCLI,
@ -10,6 +11,7 @@ import {
uniq,
} from '@nrwl/e2e/utils';
import { writeFileSync } from 'fs';
import { createFileSync } from 'fs-extra';
describe('Storybook generators and executors for standalone workspaces - using React + Vite', () => {
const wsName = uniq('react');
@ -22,10 +24,11 @@ describe('Storybook generators and executors for standalone workspaces - using R
appName,
style: 'css',
bundler: 'vite',
packageManager: getSelectedPackageManager(),
});
runCLI(
`generate @nrwl/react:storybook-configuration ${appName} --generateStories --no-interactive`
`generate @nx/react:storybook-configuration ${appName} --generateStories --no-interactive`
);
runCLI(`report`);
@ -50,8 +53,7 @@ describe('Storybook generators and executors for standalone workspaces - using R
});
});
// TODO: Use --storybook7Configuration and re-enable this test - or else it NEEDS NODE 16
xdescribe('serve storybook', () => {
describe('serve storybook', () => {
afterEach(() => killPorts());
it('should serve a React based Storybook setup that uses Vite', async () => {
@ -59,73 +61,86 @@ describe('Storybook generators and executors for standalone workspaces - using R
return /Storybook.*started/gi.test(output);
});
p.kill();
}, 40000);
}, 60000);
});
// TODO: Use --storybook7Configuration and re-enable this test - or else it NEEDS NODE 16
xdescribe('build storybook', () => {
describe('build storybook', () => {
it('should build a React based storybook that uses Vite', () => {
runCLI(`run ${appName}:build-storybook --verbose`);
checkFilesExist(`dist/storybook/${appName}/index.html`);
}, 40000);
}, 60000);
// This test makes sure path resolution works
// This needs fixing on the Storybook side
// vite paths resolution is not working on standalone
xit('should build a React based storybook that references another lib and uses Vite', () => {
const reactLib = uniq('test-lib-react');
runCLI(`generate @nrwl/react:lib ${reactLib} --no-interactive`);
const anotherReactLib = uniq('test-another-lib-react');
runCLI(`generate @nx/react:lib ${anotherReactLib} --no-interactive`);
// create a React component we can reference
createFileSync(tmpProjPath(`${anotherReactLib}/src/lib/mytestcmp.tsx`));
writeFileSync(
tmpProjPath(`${reactLib}/src/lib/mytestcmp.tsx`),
tmpProjPath(`${anotherReactLib}/src/lib/mytestcmp.tsx`),
`
import React from 'react';
export function MyTestCmp() {
return (
<div>
<h1>Welcome to OtherLib!</h1>
</div>
);
}
/* eslint-disable-next-line */
export interface MyTestCmpProps {}
export const MyTestCmp = (props: MyTestCmpProps) => {
return (
<div>
<h1>Welcome to test cmp!</h1>
</div>
);
};
export default MyTestCmp;
export default MyTestCmp;
`
);
// update index.ts and export it
writeFileSync(
tmpProjPath(`${reactLib}/src/index.ts`),
tmpProjPath(`${anotherReactLib}/src/index.ts`),
`
export * from './lib/mytestcmp';
`
);
// create a story in the first lib to reference the cmp from the 2nd lib
// create a component and a story in the first lib to reference the cmp from the 2nd lib
createFileSync(tmpProjPath(`src/app/test-button.tsx`));
writeFileSync(
tmpProjPath(`${reactLib}/src/lib/myteststory.stories.tsx`),
tmpProjPath(`src/app/test-button.tsx`),
`
import React from 'react';
import { MyTestCmp } from '@${wsName}/${anotherReactLib}';
import { MyTestCmp, MyTestCmpProps } from '@${wsName}/${reactLib}';
export function TestButton() {
return (
<div>
<MyTestCmp />
</div>
);
}
export default {
component: MyTestCmp,
title: 'MyTestCmp',
};
export const primary = () => {
/* eslint-disable-next-line */
const props: MyTestCmpProps = {};
return <MyTestCmp />;
};
export default TestButton;
`
);
// create a story in the first lib to reference the cmp from the 2nd lib
createFileSync(tmpProjPath(`src/app/test-button.stories.tsx`));
writeFileSync(
tmpProjPath(`src/app/test-button.stories.tsx`),
`
import type { Meta } from '@storybook/react';
import { TestButton } from './test-button';
const Story: Meta<typeof TestButton> = {
component: TestButton,
title: 'TestButton',
};
export default Story;
export const Primary = {
args: {},
};
`
);
// build React lib
runCLI(`run ${reactLib}:build-storybook --verbose`);
checkFilesExist(`dist/storybook/${reactLib}/index.html`);
}, 40000);
runCLI(`run ${appName}:build-storybook --verbose`);
checkFilesExist(`dist/storybook/${appName}/index.html`);
}, 60000);
});
});

View File

@ -2,47 +2,28 @@ import {
checkFilesExist,
cleanupProject,
killPorts,
newProject,
runCLI,
runCommandUntil,
tmpProjPath,
uniq,
getPackageManagerCommand,
runCommand,
newProject,
updateJson,
} from '@nrwl/e2e/utils';
import { writeFileSync } from 'fs';
describe('Storybook generators and executors for monorepos', () => {
const previousPM = process.env.SELECTED_PM;
// TODO: re-enable once the issue is fixed with long build times
describe.skip('Storybook generators and executors for monorepos', () => {
const reactStorybookLib = uniq('test-ui-lib-react');
let proj;
beforeAll(() => {
process.env.SELECTED_PM = 'yarn';
proj = newProject({
packageManager: 'yarn',
});
runCLI(`generate @nrwl/react:lib ${reactStorybookLib} --no-interactive`);
proj = newProject();
runCLI(`generate @nx/react:lib ${reactStorybookLib} --no-interactive`);
runCLI(
`generate @nrwl/react:storybook-configuration ${reactStorybookLib} --generateStories --no-interactive --bundler=webpack`
`generate @nx/react:storybook-configuration ${reactStorybookLib} --generateStories --no-interactive --bundler=webpack`
);
// TODO(jack): Overriding enhanced-resolve to 5.10.0 now until the package is fixed.
// TODO: Use --storybook7Configuration and remove this
// See: https://github.com/webpack/enhanced-resolve/issues/362
updateJson('package.json', (json) => {
json['overrides'] = {
'enhanced-resolve': '5.10.0',
};
return json;
});
runCommand(getPackageManagerCommand().install);
});
afterAll(() => {
cleanupProject();
process.env.SELECTED_PM = previousPM;
});
describe('serve and build storybook', () => {
@ -57,18 +38,18 @@ describe('Storybook generators and executors for monorepos', () => {
}
);
p.kill();
}, 50000);
}, 60000);
it('should build a React based storybook setup that uses webpack', () => {
// build
runCLI(`run ${reactStorybookLib}:build-storybook --verbose`);
checkFilesExist(`dist/storybook/${reactStorybookLib}/index.html`);
}, 50000);
}, 60000);
// This test makes sure path resolution works
it('should build a React based storybook that references another lib and uses webpack', () => {
const anotherReactLib = uniq('test-another-lib-react');
runCLI(`generate @nrwl/react:lib ${anotherReactLib} --no-interactive`);
runCLI(`generate @nx/react:lib ${anotherReactLib} --no-interactive`);
// create a React component we can reference
writeFileSync(
tmpProjPath(`libs/${anotherReactLib}/src/lib/mytestcmp.tsx`),
@ -117,6 +98,6 @@ describe('Storybook generators and executors for monorepos', () => {
// build React lib
runCLI(`run ${reactStorybookLib}:build-storybook --verbose`);
checkFilesExist(`dist/storybook/${reactStorybookLib}/index.html`);
}, 50000);
}, 60000);
});
});

View File

@ -1,12 +1,16 @@
/* eslint-disable storybook/no-uninstalled-addons */
module.exports = {
core: { builder: 'webpack5' },
stories: [
'../src/app/**/*.stories.mdx',
'../src/app/**/*.stories.@(js|jsx|ts|tsx)',
],
stories: ['../src/app/**/*.stories.@(mdx|js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-essentials',
'@nx/react/plugins/storybook',
'storybook-dark-mode',
],
framework: {
name: '@storybook/react-webpack5',
options: {},
},
docs: {
autodocs: true,
},
};

View File

@ -245,7 +245,6 @@
"storybook": {
"executor": "@nx/storybook:storybook",
"options": {
"uiFramework": "@storybook/react",
"port": 4400,
"configDir": "graph/client/.storybook"
},
@ -259,7 +258,6 @@
"executor": "@nx/storybook:build",
"outputs": ["{options.outputDir}"],
"options": {
"uiFramework": "@storybook/react",
"configDir": "graph/client/.storybook",
"outputDir": "dist/storybook/graph-client"
},

View File

@ -1,8 +1,12 @@
/* eslint-disable storybook/no-uninstalled-addons */
module.exports = {
core: { builder: 'webpack5' },
stories: [
'../src/lib/**/*.stories.mdx',
'../src/lib/**/*.stories.@(js|jsx|ts|tsx)',
],
stories: ['../src/lib/**/*.stories.@(mdx|js|jsx|ts|tsx)'],
addons: ['@storybook/addon-essentials', '@nx/react/plugins/storybook'],
framework: {
name: '@storybook/react-webpack5',
options: {},
},
docs: {
autodocs: true,
},
};

View File

@ -10,7 +10,6 @@
"storybook": {
"executor": "@nx/storybook:storybook",
"options": {
"uiFramework": "@storybook/react",
"port": 4400,
"configDir": "graph/ui-components/.storybook"
},
@ -24,7 +23,6 @@
"executor": "@nx/storybook:build",
"outputs": ["{options.outputDir}"],
"options": {
"uiFramework": "@storybook/react",
"configDir": "graph/ui-components/.storybook",
"outputDir": "dist/storybook/graph-ui-components"
},

View File

@ -1,8 +1,12 @@
/* eslint-disable storybook/no-uninstalled-addons */
module.exports = {
core: { builder: 'webpack5' },
stories: [
'../src/lib/**/*.stories.mdx',
'../src/lib/**/*.stories.@(js|jsx|ts|tsx)',
],
stories: ['../src/lib/**/*.stories.@(mdx|js|jsx|ts|tsx)'],
addons: ['@storybook/addon-essentials', '@nx/react/plugins/storybook'],
framework: {
name: '@storybook/react-webpack5',
options: {},
},
docs: {
autodocs: true,
},
};

View File

@ -10,7 +10,6 @@
"storybook": {
"executor": "@nx/storybook:storybook",
"options": {
"uiFramework": "@storybook/react",
"port": 4400,
"configDir": "graph/ui-graph/.storybook"
},
@ -24,7 +23,6 @@
"executor": "@nx/storybook:build",
"outputs": ["{options.outputDir}"],
"options": {
"uiFramework": "@storybook/react",
"configDir": "graph/ui-graph/.storybook",
"outputDir": "dist/storybook/graph-ui-graph"
},

View File

@ -1,8 +1,12 @@
/* eslint-disable storybook/no-uninstalled-addons */
module.exports = {
core: { builder: 'webpack5' },
stories: [
'../src/lib/**/*.stories.mdx',
'../src/lib/**/*.stories.@(js|jsx|ts|tsx)',
],
stories: ['../src/lib/**/*.stories.@(mdx|js|jsx|ts|tsx)'],
addons: ['@storybook/addon-essentials', '@nx/react/plugins/storybook'],
framework: {
name: '@storybook/react-webpack5',
options: {},
},
docs: {
autodocs: true,
},
};

View File

@ -10,7 +10,6 @@
"storybook": {
"executor": "@nx/storybook:storybook",
"options": {
"uiFramework": "@storybook/react",
"port": 4400,
"configDir": "graph/ui-tooltips/.storybook"
},
@ -24,7 +23,6 @@
"executor": "@nx/storybook:build",
"outputs": ["{options.outputDir}"],
"options": {
"uiFramework": "@storybook/react",
"configDir": "graph/ui-tooltips/.storybook",
"outputDir": "dist/storybook/graph-ui-tooltips"
},

View File

@ -72,13 +72,12 @@
"@rollup/plugin-node-resolve": "^13.0.4",
"@rollup/plugin-url": "^7.0.0",
"@schematics/angular": "~15.2.0",
"@storybook/addon-essentials": "^6.5.15",
"@storybook/angular": "^6.5.15",
"@storybook/builder-webpack5": "^6.5.15",
"@storybook/core-server": "^6.5.15",
"@storybook/manager-webpack5": "^6.5.15",
"@storybook/react": "^6.5.15",
"@storybook/types": "^7.0.0-alpha.44",
"@storybook/addon-essentials": "^7.0.2",
"@storybook/angular": "^7.0.2",
"@storybook/core-server": "^7.0.2",
"@storybook/react": "^7.0.2",
"@storybook/react-webpack5": "^7.0.2",
"@storybook/types": "^7.0.2",
"@svgr/rollup": "^6.1.2",
"@svgr/webpack": "^6.1.2",
"@swc-node/register": "^1.4.2",
@ -149,6 +148,7 @@
"eslint-plugin-jsx-a11y": "6.6.1",
"eslint-plugin-react": "7.31.11",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-storybook": "^0.6.11",
"express": "^4.18.1",
"fast-xml-parser": "^4.0.9",
"file-loader": "^6.2.0",
@ -222,7 +222,7 @@
"source-map": "0.7.3",
"source-map-loader": "^3.0.0",
"source-map-support": "0.5.19",
"storybook-dark-mode": "^1.1.2",
"storybook-dark-mode": "^3.0.0",
"style-loader": "^3.3.0",
"styled-components": "5.3.6",
"stylus": "^0.55.0",

View File

@ -1,12 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`StorybookConfiguration generator should configure storybook to use webpack 5 1`] = `
"module.exports = {
core: { builder: 'webpack5' },
stories: ['../**/*.stories.mdx', '../**/*.stories.@(js|jsx|ts|tsx)'],
"const config = {
stories: ['../**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: ['@storybook/addon-essentials'],
framework: {
name: '@storybook/angular',
options: {},
},
};
export default config;
// To customize your webpack configuration you can use the webpackFinal field.
// Check https://storybook.js.org/docs/react/builders/webpack#extending-storybooks-webpack-config
// and https://nx.dev/packages/storybook/documents/custom-builder-configs

View File

@ -24,27 +24,35 @@ describe('Build storybook', () => {
beforeEach(async () => {
options = {
configDir: join(__dirname, `/../../utils/test-configs/.storybook`),
uiFramework: '@storybook/react',
outputDir: `/root/dist/storybook`,
};
context = executorContext as ExecutorContext;
});
it('should call the storybook buildStaticStandalone', async () => {
it('should call the storybook build', async () => {
const loggerSpy = jest.spyOn(logger, 'info');
const standaloneSpy = jest
.spyOn(build, 'buildStaticStandalone')
const buildSpy = jest
.spyOn(build, 'build')
.mockImplementation(() => Promise.resolve());
const result = await storybookBuilder(options, context);
expect(standaloneSpy).toHaveBeenCalled();
expect(loggerSpy).toHaveBeenCalledWith(`NX ui framework: @storybook/react`);
expect(loggerSpy).toHaveBeenCalledWith(
`NX Storybook files available in /root/dist/storybook`
expect(buildSpy).toHaveBeenCalled();
expect(loggerSpy).toHaveBeenNthCalledWith(
1,
'NX Storybook builder starting ...'
);
expect(loggerSpy).toHaveBeenNthCalledWith(
2,
'NX Storybook builder finished ...'
);
expect(loggerSpy).toHaveBeenNthCalledWith(
3,
'NX Storybook files available in /root/dist/storybook'
);
expect(result.success).toBeTruthy();
});
});

View File

@ -51,15 +51,22 @@ export default async function buildStorybookExecutor(
}
}
function runInstance(options: CLIOptions, storybook7: boolean): Promise<void> {
function runInstance(
options: CLIOptions,
storybook7: boolean
): Promise<void | {
port: number;
address: string;
networkAddress: string;
}> {
const env = process.env.NODE_ENV ?? 'production';
process.env.NODE_ENV = env;
if (storybook7) {
return build['build']({
return build.build({
...options,
mode: 'static',
} as any); // TODO(katerina): Change to actual types when Storybook 7
});
} else {
const nodeVersion = process.version.slice(1).split('.');
if (+nodeVersion[0] === 18) {

View File

@ -1,45 +1,31 @@
import { fs as fsMock, vol } from 'memfs';
import * as fs from 'fs';
import { ExecutorContext } from '@nx/devkit';
jest.mock('@storybook/core-server', () => ({
buildDev: jest.fn().mockImplementation(() => Promise.resolve()),
build: jest.fn().mockImplementation(() => Promise.resolve()),
build: jest.fn().mockImplementation(() =>
Promise.resolve({
port: 4400,
})
),
}));
import { buildDev } from '@storybook/core-server';
import { build } from '@storybook/core-server';
import storybookExecutor from './storybook.impl';
import { join } from 'path';
import { readFileSync } from 'fs-extra';
import { CLIOptions } from '@storybook/types';
import { CommonNxStorybookConfig } from '../../utils/models';
// TODO(katerina): Update when Storybook 7
describe('@nx/storybook:storybook', () => {
let context: ExecutorContext;
let options: CLIOptions & CommonNxStorybookConfig;
beforeEach(() => {
// preserve original package.json file to memory
const rootPath = join(__dirname, `../../../../../`);
const packageJsonPath = join(
rootPath,
`node_modules/@storybook/react/package.json`
);
const storybookPath = join(rootPath, '.storybook');
options = {
uiFramework: '@storybook/react',
port: 4400,
configDir: storybookPath,
configDir: join(__dirname, `/../../utils/test-configs/.storybook`),
};
vol.fromJSON({
[packageJsonPath]: readFileSync(packageJsonPath).toString(),
});
vol.mkdirSync(storybookPath, {
recursive: true,
});
context = {
root: rootPath,
cwd: rootPath,
@ -54,22 +40,7 @@ describe('@nx/storybook:storybook', () => {
targets: {
build: {
executor: '@nx/web:webpack',
options: {
compiler: 'babel',
outputPath: 'dist/apps/webre',
index: 'apps/webre/src/index.html',
baseHref: '/',
main: 'apps/webre/src/main.tsx',
polyfills: 'apps/webre/src/polyfills.ts',
tsConfig: 'apps/webre/tsconfig.app.json',
assets: [
'apps/webre/src/favicon.ico',
'apps/webre/src/assets',
],
styles: ['apps/webre/src/styles.css'],
scripts: [],
webpackConfig: '@nx/react/plugins/webpack',
},
options: {},
},
storybook: {
executor: '@nx/storybook:storybook',
@ -82,16 +53,18 @@ describe('@nx/storybook:storybook', () => {
nxJsonConfiguration: {},
isVerbose: false,
};
jest.mock('fs', () => fsMock);
jest.spyOn(fs, 'statSync').mockReturnValue({
isDirectory: () => true,
} as fs.Stats);
});
it('should provide options to storybook', async () => {
const iterator = storybookExecutor(options, context);
const { value } = await iterator.next();
expect(value).toEqual({ success: true });
expect(buildDev).toHaveBeenCalled();
expect(value).toEqual({
success: true,
info: {
baseUrl: 'http://localhost:4400',
port: 4400,
},
});
expect(build).toHaveBeenCalled();
});
});

View File

@ -24,17 +24,14 @@ export default async function* storybookExecutor(
storybookConfigExistsCheck(options.configDir, context.projectName);
if (storybook7) {
const buildOptions: CLIOptions = options;
const result: { port: number } = await runInstance(
buildOptions,
storybook7
);
const result = await runInstance(buildOptions, storybook7);
yield {
success: true,
info: {
port: result?.port,
port: result?.['port'],
baseUrl: `${options.https ? 'https' : 'http'}://${
options.host ?? 'localhost'
}:${result?.port}`,
}:${result?.['port']}`,
},
};
await new Promise<{ success: boolean }>(() => {});
@ -58,18 +55,24 @@ export default async function* storybookExecutor(
}
}
function runInstance(options: CLIOptions, storybook7: boolean) {
function runInstance(
options: CLIOptions,
storybook7: boolean
): Promise<void | {
port: number;
address: string;
networkAddress: string;
}> {
const env = process.env.NODE_ENV ?? 'development';
process.env.NODE_ENV = env;
if (storybook7) {
return build['build']({
return build.build({
...options,
mode: 'dev',
} as any); // TODO(katerina): Change to actual types when Storybook 7
});
} else {
// TODO(katerina): Remove when Storybook 7
return build.buildDev({
return build['buildDev']({
...options,
configType: env.toUpperCase(),
mode: 'dev',

View File

@ -1,6 +1,7 @@
import {
addDependenciesToPackageJson,
convertNxGenerator,
detectPackageManager,
GeneratorCallback,
readJson,
readNxJson,
@ -76,6 +77,17 @@ function checkDependenciesInstalled(host: Tree, schema: Schema) {
devDependencies['@storybook/react-native'] = storybookReactNativeVersion;
} else {
devDependencies[schema.uiFramework] = storybook7VersionToInstall;
const isPnpm = detectPackageManager(host.root) === 'pnpm';
if (isPnpm) {
// If it's pnpm, it needs the framework without the builder
// as a dependency too (eg. @storybook/react)
const matchResult = schema.uiFramework?.match(/^@storybook\/(\w+)/);
const uiFrameworkWithoutBuilder = matchResult ? matchResult[0] : null;
if (uiFrameworkWithoutBuilder) {
devDependencies[uiFrameworkWithoutBuilder] =
storybook7VersionToInstall;
}
}
}
devDependencies['@storybook/core-server'] = storybook7VersionToInstall;
devDependencies['@storybook/addon-essentials'] = storybook7VersionToInstall;

6387
yarn.lock

File diff suppressed because it is too large Load Diff