Skip to content

Commit

Permalink
feat(storybook): for nested projects (#13314)
Browse files Browse the repository at this point in the history
  • Loading branch information
mandarini authored Nov 24, 2022
1 parent 61f6e2d commit 2614452
Show file tree
Hide file tree
Showing 20 changed files with 1,397 additions and 288 deletions.
199 changes: 199 additions & 0 deletions e2e/storybook/src/storybook-nested.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import {
checkFilesExist,
cleanupProject,
killPorts,
newProject,
runCLI,
runCommandUntil,
tmpProjPath,
uniq,
updateJson,
getPackageManagerCommand,
runCommand,
runCreateWorkspace,
getSelectedPackageManager,
updateFile,
readJson,
} from '@nrwl/e2e/utils';
import { writeFileSync } from 'fs';

describe('Storybook generators for nested workspaces', () => {
const previousPM = process.env.SELECTED_PM;
const wsName = uniq('react');
const appName = uniq('app');
const packageManager = getSelectedPackageManager() || 'yarn';

beforeAll(() => {
process.env.SELECTED_PM = 'yarn';

// create a workspace with a single react app at the root
runCreateWorkspace(wsName, {
preset: 'react-experimental',
appName,
style: 'css',
packageManager,
});

runCLI(
`generate @nrwl/react:storybook-configuration ${appName} --generateStories --no-interactive`
);

// TODO(jack): Overriding enhanced-resolve to 5.10.0 now until the package is fixed.
// See: https://github.com/webpack/enhanced-resolve/issues/362
updateJson('package.json', (json) => {
json['overrides'] = {
'enhanced-resolve': '5.10.0',
};

return json;
});

// TODO(katerina): Once Storybook vite generators are fixed, remove this.
updateJson('package.json', (json) => {
json['devDependencies'] = {
...json['devDependencies'],
'@storybook/builder-vite': '0.2.5',
};
return json;
});

runCommand(getPackageManagerCommand().install);

// TODO(katerina): Once Storybook vite generators are fixed, remove this.
updateFile(
'./storybook/main.js',
`const rootMain = require(‘./main.root’);
module.exports = {
…rootMain,
core: { …rootMain.core, builder: ‘@storybook/builder-vite’ },
stories: [
…rootMain.stories,
‘../src/app/**/*.stories.mdx’,
‘../src/app/**/*.stories.@(js|jsx|ts|tsx)’,
],
addons: […rootMain.addons, ‘@nrwl/react/plugins/storybook’],
webpackFinal: async (config, { configType }) => {
if (rootMain.webpackFinal) {
config = await rootMain.webpackFinal(config, { configType });
}
return config;
},
};`
);
});

afterAll(() => {
cleanupProject();
process.env.SELECTED_PM = previousPM;
});

describe('Storybook generated files', () => {
it('should generate storybook files', () => {
checkFilesExist(
'.storybook/main.js',
'.storybook/main.root.js',
'.storybook/preview.js',
'.storybook/tsconfig.json'
);
});

it('should edit root tsconfig.json', () => {
const tsconfig = readJson(`tsconfig.base.json`);
expect(tsconfig['ts-node']?.compilerOptions?.module).toEqual('commonjs');
});

it('should generate correct files for nested app', () => {
const nestedAppName = uniq('other-app');
runCLI(`generate @nrwl/react:app ${nestedAppName} --no-interactive`);
runCLI(
`generate @nrwl/react:storybook-configuration ${nestedAppName} --generateStories --no-interactive`
);
checkFilesExist(
`${nestedAppName}/.storybook/main.js`,
`${nestedAppName}/.storybook/tsconfig.json`
);
});
});

describe('serve storybook', () => {
afterEach(() => killPorts());

it('should run a React based Storybook setup', async () => {
// serve the storybook
const p = await runCommandUntil(`run ${appName}:storybook`, (output) => {
return /Storybook.*started/gi.test(output);
});
p.kill();
}, 1000000);
});

describe('build storybook', () => {
it('should build and lint a React based storybook', () => {
// build
runCLI(`run ${appName}:build-storybook --verbose`);
checkFilesExist(`dist/storybook/${appName}/index.html`);

// lint
const output = runCLI(`run ${appName}:lint`);
expect(output).toContain('All files pass linting.');
}, 1000000);

it('should build a React based storybook that references another lib', () => {
const reactLib = uniq('test-lib-react');
runCLI(`generate @nrwl/react:lib ${reactLib} --no-interactive`);
// create a React component we can reference
writeFileSync(
tmpProjPath(`${reactLib}/src/lib/mytestcmp.tsx`),
`
import React from 'react';
/* eslint-disable-next-line */
export interface MyTestCmpProps {}
export const MyTestCmp = (props: MyTestCmpProps) => {
return (
<div>
<h1>Welcome to test cmp!</h1>
</div>
);
};
export default MyTestCmp;
`
);
// update index.ts and export it
writeFileSync(
tmpProjPath(`${reactLib}/src/index.ts`),
`
export * from './lib/mytestcmp';
`
);

// create a story in the first lib to reference the cmp from the 2nd lib
writeFileSync(
tmpProjPath(`${reactLib}/src/lib/myteststory.stories.tsx`),
`
import React from 'react';
import { MyTestCmp, MyTestCmpProps } from '@${wsName}/${reactLib}';
export default {
component: MyTestCmp,
title: 'MyTestCmp',
};
export const primary = () => {
/* eslint-disable-next-line */
const props: MyTestCmpProps = {};
return <MyTestCmp />;
};
`
);

// build React lib
runCLI(`run ${reactLib}:build-storybook --verbose`);
checkFilesExist(`dist/storybook/${reactLib}/index.html`);
}, 1000000);
});
});
10 changes: 10 additions & 0 deletions packages/storybook/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@
"glob": "**/root-files-ts/.storybook/**",
"output": "/"
},
{
"input": "packages/storybook",
"glob": "**/root-files-nested/.storybook/**",
"output": "/"
},
{
"input": "packages/storybook",
"glob": "**/root-files-nested-ts/.storybook/**",
"output": "/"
},
{
"input": "packages/storybook",
"glob": "**/*.json",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { NxJsonConfiguration, Tree, updateJson, writeJson } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';

import configurationGenerator from './configuration';
import * as rootProjectConfiguration from './test-configs/root-project-configuration.json';
import * as workspaceConfiguration from './test-configs/root-workspace-configuration.json';

describe('@nrwl/storybook:configuration for workspaces with Root project', () => {
describe('basic functionalities', () => {
let tree: Tree;

beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
json.namedInputs = {
production: ['default'],
};
return json;
});

writeJson(tree, 'project.json', rootProjectConfiguration);
writeJson(tree, 'tsconfig.json', {
extends: './tsconfig.base.json',
compilerOptions: {
jsx: 'react-jsx',
allowJs: false,
esModuleInterop: false,
allowSyntheticDefaultImports: true,
forceConsistentCasingInFileNames: true,
isolatedModules: true,
lib: ['DOM', 'DOM.Iterable', 'ESNext'],
module: 'ESNext',
moduleResolution: 'Node',
noEmit: true,
resolveJsonModule: true,
skipLibCheck: true,
strict: true,
target: 'ESNext',
types: ['vite/client'],
useDefineForClassFields: true,
noImplicitOverride: true,
noPropertyAccessFromIndexSignature: true,
noImplicitReturns: true,
noFallthroughCasesInSwitch: true,
},
files: [],
include: [],
references: [
{
path: './tsconfig.app.json',
},
{
path: './tsconfig.spec.json',
},
{
path: './.storybook/tsconfig.json',
},
],
});
writeJson(tree, 'workspace.json', workspaceConfiguration);
writeJson(tree, 'package.json', {
devDependencies: {
'@storybook/addon-essentials': '~6.2.9',
'@storybook/react': '~6.2.9',
},
});
});

it('should generate files for root app', async () => {
await configurationGenerator(tree, {
name: 'web',
uiFramework: '@storybook/react',
standaloneConfig: false,
});

expect(tree.exists('.storybook/main.js')).toBeTruthy();
expect(tree.exists('.storybook/main.root.js')).toBeTruthy();
expect(tree.exists('.storybook/tsconfig.json')).toBeTruthy();
expect(tree.exists('.storybook/preview.js')).toBeTruthy();
});

it('should generate Storybook files for nested first - then for root', async () => {
writeJson(tree, 'apps/reapp/tsconfig.json', {});

await configurationGenerator(tree, {
name: 'reapp',
uiFramework: '@storybook/react',
tsConfiguration: true,
});

expect(tree.exists('.storybook/main.ts')).toBeFalsy();
expect(tree.exists('.storybook/main.root.ts')).toBeTruthy();
expect(tree.exists('.storybook/tsconfig.json')).toBeFalsy();
expect(tree.exists('.storybook/preview.ts')).toBeFalsy();

expect(tree.exists('apps/reapp/.storybook/main.ts')).toBeTruthy();
expect(tree.exists('apps/reapp/.storybook/tsconfig.json')).toBeTruthy();
expect(tree.exists('apps/reapp/.storybook/preview.ts')).toBeTruthy();

await configurationGenerator(tree, {
name: 'web',
uiFramework: '@storybook/react',
});

expect(tree.exists('.storybook/main.ts')).toBeTruthy();
expect(tree.exists('.storybook/tsconfig.json')).toBeTruthy();
expect(tree.exists('.storybook/preview.ts')).toBeTruthy();
});
});
});
42 changes: 31 additions & 11 deletions packages/storybook/src/generators/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
configureTsSolutionConfig,
createProjectStorybookDir,
createRootStorybookDir,
createRootStorybookDirForRootProjectInNestedWorkspace,
projectIsRootProjectInNestedWorkspace,
updateLintConfig,
} from './util-functions';
import { Linter } from '@nrwl/linter';
Expand All @@ -39,7 +41,10 @@ export async function configurationGenerator(

const tasks: GeneratorCallback[] = [];

const { projectType, targets } = readProjectConfiguration(tree, schema.name);
const { projectType, targets, root, sourceRoot } = readProjectConfiguration(
tree,
schema.name
);
const { nextBuildTarget, compiler } =
findStorybookAndBuildTargetsAndCompiler(targets);

Expand All @@ -48,16 +53,31 @@ export async function configurationGenerator(
});
tasks.push(initTask);

createRootStorybookDir(tree, schema.js, schema.tsConfiguration);
createProjectStorybookDir(
tree,
schema.name,
schema.uiFramework,
schema.js,
schema.tsConfiguration,
!!nextBuildTarget,
compiler === 'swc'
);
if (projectIsRootProjectInNestedWorkspace(root)) {
createRootStorybookDirForRootProjectInNestedWorkspace(
tree,
schema.name,
schema.uiFramework,
schema.js,
schema.tsConfiguration,
root,
projectType,
!!nextBuildTarget,
compiler === 'swc'
);
} else {
createRootStorybookDir(tree, schema.js, schema.tsConfiguration);
createProjectStorybookDir(
tree,
schema.name,
schema.uiFramework,
schema.js,
schema.tsConfiguration,
!!nextBuildTarget,
compiler === 'swc'
);
}

configureTsProjectConfig(tree, schema);
configureTsSolutionConfig(tree, schema);
updateLintConfig(tree, schema);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { rootMain } from '<%= offsetFromRoot %>../.storybook/main';
import { rootMain } from '<%= offsetFromRoot %>../.storybook/<%= rootMainName %>';
import type { StorybookConfig, Options } from '@storybook/core-common';
<% if (isNextJs){ %>import path from 'path';<% } %>

Expand Down
Loading

0 comments on commit 2614452

Please sign in to comment.