Skip to content

Commit

Permalink
feat(storybook): add createNodesV2 for plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Phillip9587 committed Sep 25, 2024
1 parent 4fa50ad commit 182c980
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 78 deletions.
1 change: 1 addition & 0 deletions packages/storybook/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export {
createNodes,
createNodesV2,
StorybookPluginOptions,
createDependencies,
} from './src/plugins/plugin';
43 changes: 29 additions & 14 deletions packages/storybook/src/plugins/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { CreateNodesContext } from '@nx/devkit';
import { TempFs } from '@nx/devkit/internal-testing-utils';
import type { StorybookConfig } from '@storybook/types';
import { join } from 'node:path';
import { createNodes } from './plugin';
import { createNodesV2 } from './plugin';

describe('@nx/storybook/plugin', () => {
let createNodesFunction = createNodes[1];
let createNodesFunction = createNodesV2[1];
let context: CreateNodesContext;
let tempFs: TempFs;

Expand Down Expand Up @@ -54,7 +54,7 @@ describe('@nx/storybook/plugin', () => {
});

const nodes = await createNodesFunction(
'my-app/.storybook/main.ts',
['my-app/.storybook/main.ts'],
{
buildStorybookTargetName: 'build-storybook',
staticStorybookTargetName: 'static-storybook',
Expand All @@ -64,9 +64,10 @@ describe('@nx/storybook/plugin', () => {
context
);

expect(nodes?.['projects']?.['my-app']?.targets).toBeDefined();
expect(nodes.at(0)?.[0]).toStrictEqual('my-app/.storybook/main.ts');
expect(nodes.at(0)?.[1]?.['projects']?.['my-app']?.targets).toBeDefined();
expect(
nodes?.['projects']?.['my-app']?.targets?.['build-storybook']
nodes.at(0)?.[1]?.['projects']?.['my-app']?.targets['build-storybook']
).toMatchObject({
command: 'storybook build',
options: {
Expand All @@ -86,7 +87,7 @@ describe('@nx/storybook/plugin', () => {
],
});
expect(
nodes?.['projects']?.['my-app']?.targets?.['serve-storybook']
nodes.at(0)?.[1]?.['projects']?.['my-app']?.targets['serve-storybook']
).toMatchObject({
command: 'storybook dev',
});
Expand All @@ -104,7 +105,7 @@ describe('@nx/storybook/plugin', () => {
});

const nodes = await createNodesFunction(
'my-ng-app/.storybook/main.ts',
['my-ng-app/.storybook/main.ts'],
{
buildStorybookTargetName: 'build-storybook',
staticStorybookTargetName: 'static-storybook',
Expand All @@ -114,9 +115,14 @@ describe('@nx/storybook/plugin', () => {
context
);

expect(nodes?.['projects']?.['my-ng-app']?.targets).toBeDefined();
expect(nodes.at(0)?.[0]).toStrictEqual('my-ng-app/.storybook/main.ts');
expect(
nodes?.['projects']?.['my-ng-app']?.targets?.['build-storybook']
nodes.at(0)?.[1]?.['projects']?.['my-ng-app']?.targets
).toBeDefined();
expect(
nodes.at(0)?.[1]?.['projects']?.['my-ng-app']?.targets?.[
'build-storybook'
]
).toMatchObject({
executor: '@storybook/angular:build-storybook',
options: {
Expand Down Expand Up @@ -145,7 +151,9 @@ describe('@nx/storybook/plugin', () => {
],
});
expect(
nodes?.['projects']?.['my-ng-app']?.targets?.['serve-storybook']
nodes.at(0)?.[1]?.['projects']?.['my-ng-app']?.targets?.[
'serve-storybook'
]
).toMatchObject({
executor: '@storybook/angular:start-storybook',
options: {
Expand All @@ -172,7 +180,7 @@ describe('@nx/storybook/plugin', () => {
});

const nodes = await createNodesFunction(
'my-react-lib/.storybook/main.js',
['my-react-lib/.storybook/main.js'],
{
buildStorybookTargetName: 'build-storybook',
staticStorybookTargetName: 'static-storybook',
Expand All @@ -182,9 +190,14 @@ describe('@nx/storybook/plugin', () => {
context
);

expect(nodes?.['projects']?.['my-react-lib']?.targets).toBeDefined();
expect(nodes.at(0)?.[0]).toStrictEqual('my-react-lib/.storybook/main.js');
expect(
nodes.at(0)?.[1]?.['projects']?.['my-react-lib']?.targets
).toBeDefined();
expect(
nodes?.['projects']?.['my-react-lib']?.targets?.['build-storybook']
nodes.at(0)?.[1]?.['projects']?.['my-react-lib']?.targets?.[
'build-storybook'
]
).toMatchObject({
command: 'storybook build',
options: {
Expand All @@ -204,7 +217,9 @@ describe('@nx/storybook/plugin', () => {
],
});
expect(
nodes?.['projects']?.['my-react-lib']?.targets?.['serve-storybook']
nodes.at(0)?.[1]?.['projects']?.['my-react-lib']?.targets?.[
'serve-storybook'
]
).toMatchObject({
command: 'storybook dev',
});
Expand Down
183 changes: 119 additions & 64 deletions packages/storybook/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import {
CreateDependencies,
CreateNodes,
CreateNodesContext,
createNodesFromFiles,
CreateNodesV2,
detectPackageManager,
getPackageManagerCommand,
joinPathFragments,
logger,
parseJson,
readJsonFile,
TargetConfiguration,
Expand All @@ -17,6 +21,9 @@ import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import { getLockFileName } from '@nx/js';
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
import type { StorybookConfig } from '@storybook/types';
import { hashObject } from 'nx/src/hasher/file-hasher';

const pmc = getPackageManagerCommand();

export interface StorybookPluginOptions {
buildStorybookTargetName?: string;
Expand All @@ -25,82 +32,127 @@ export interface StorybookPluginOptions {
testStorybookTargetName?: string;
}

const cachePath = join(workspaceDataDirectory, 'storybook.hash');
const targetsCache = readTargetsCache();

function readTargetsCache(): Record<
string,
Record<string, TargetConfiguration>
> {
function readTargetsCache(
cachePath: string
): Record<string, Record<string, TargetConfiguration>> {
return existsSync(cachePath) ? readJsonFile(cachePath) : {};
}

function writeTargetsToCache() {
const oldCache = readTargetsCache();
writeJsonFile(cachePath, {
...oldCache,
...targetsCache,
});
function writeTargetsToCache(
cachePath: string,
results: Record<string, Record<string, TargetConfiguration>>
) {
writeJsonFile(cachePath, results);
}

/**
* @deprecated The 'createDependencies' function is now a no-op. This functionality is included in 'createNodesV2'.
*/
export const createDependencies: CreateDependencies = () => {
writeTargetsToCache();
return [];
};

export const createNodes: CreateNodes<StorybookPluginOptions> = [
'**/.storybook/main.{js,ts,mjs,mts,cjs,cts}',
async (configFilePath, options, context) => {
let projectRoot = '';
if (configFilePath.includes('/.storybook')) {
projectRoot = dirname(configFilePath).replace('/.storybook', '');
} else {
projectRoot = dirname(configFilePath).replace('.storybook', '');
}
const storybookConfigGlob = '**/.storybook/main.{js,ts,mjs,mts,cjs,cts}';

if (projectRoot === '') {
projectRoot = '.';
}

// Do not create a project if package.json and project.json isn't there.
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
if (
!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')
) {
return {};
export const createNodesV2: CreateNodesV2<StorybookPluginOptions> = [
storybookConfigGlob,
async (configFilePaths, options, context) => {
const normalizedOptions = normalizeOptions(options);
const optionsHash = hashObject(normalizedOptions);
const cachePath = join(
workspaceDataDirectory,
`storybook-${optionsHash}.hash`
);
const targetsCache = readTargetsCache(cachePath);

try {
return await createNodesFromFiles(
(configFile, _, context) =>
createNodesInternal(
configFile,
normalizedOptions,
context,
targetsCache
),
configFilePaths,
normalizedOptions,
context
);
} finally {
writeTargetsToCache(cachePath, targetsCache);
}
},
];

options = normalizeOptions(options);
const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
export const createNodes: CreateNodes<StorybookPluginOptions> = [
storybookConfigGlob,
(configFilePath, options, context) => {
logger.warn(
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
);

const projectName = buildProjectName(projectRoot, context.workspaceRoot);

targetsCache[hash] ??= await buildStorybookTargets(
return createNodesInternal(
configFilePath,
projectRoot,
options,
normalizeOptions(options),
context,
projectName
{}
);
},
];

const result = {
projects: {
[projectRoot]: {
root: projectRoot,
targets: targetsCache[hash],
},
async function createNodesInternal(
configFilePath: string,
options: Required<StorybookPluginOptions>,
context: CreateNodesContext,
targetsCache: Record<string, Record<string, TargetConfiguration>>
) {
let projectRoot = '';
if (configFilePath.includes('/.storybook')) {
projectRoot = dirname(configFilePath).replace('/.storybook', '');
} else {
projectRoot = dirname(configFilePath).replace('.storybook', '');
}

if (projectRoot === '') {
projectRoot = '.';
}

// Do not create a project if package.json and project.json isn't there.
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
if (
!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')
) {
return {};
}

const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);

const projectName = buildProjectName(projectRoot, context.workspaceRoot);

targetsCache[hash] ??= await buildStorybookTargets(
configFilePath,
projectRoot,
options,
context,
projectName
);

const result = {
projects: {
[projectRoot]: {
root: projectRoot,
targets: targetsCache[hash],
},
};
},
};

return result;
},
];
return result;
}

async function buildStorybookTargets(
configFilePath: string,
Expand Down Expand Up @@ -287,13 +339,16 @@ function getOutputs(): string[] {

function normalizeOptions(
options: StorybookPluginOptions
): StorybookPluginOptions {
options ??= {};
options.buildStorybookTargetName ??= 'build-storybook';
options.serveStorybookTargetName ??= 'storybook';
options.testStorybookTargetName ??= 'test-storybook';
options.staticStorybookTargetName ??= 'static-storybook';
return options;
): Required<StorybookPluginOptions> {
return {
buildStorybookTargetName:
options.buildStorybookTargetName ?? 'build-storybook',
serveStorybookTargetName: options.serveStorybookTargetName ?? 'storybook',
testStorybookTargetName:
options.testStorybookTargetName ?? 'test-storybook',
staticStorybookTargetName:
options.staticStorybookTargetName ?? 'static-storybook',
};
}

function buildProjectName(
Expand Down

0 comments on commit 182c980

Please sign in to comment.