Skip to content

Commit

Permalink
fix(storybook): handle inherited config correctly when identifying th…
Browse files Browse the repository at this point in the history
…e framework used for inferred tasks (#22953)

(cherry picked from commit 6cab4c9)
  • Loading branch information
leosvelperez authored and FrozenPandaz committed Apr 25, 2024
1 parent ccddf0e commit 9a48b69
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 120 deletions.
117 changes: 54 additions & 63 deletions packages/storybook/src/plugins/plugin.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { CreateNodesContext } from '@nx/devkit';

import { createNodes } from './plugin';
import { TempFs } from '@nx/devkit/internal-testing-utils';
import type { StorybookConfig } from '@storybook/types';
import { join } from 'node:path';
import { createNodes } from './plugin';

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

beforeEach(async () => {
tempFs = new TempFs('storybook-plugin');
context = {
nxJsonConfiguration: {
namedInputs: {
Expand All @@ -35,34 +37,22 @@ describe('@nx/storybook/plugin', () => {

afterEach(() => {
jest.resetModules();
tempFs = new TempFs('test');
tempFs.cleanup();
tempFs = null;
});

it('should create nodes', () => {
tempFs.createFileSync(
'my-app/.storybook/main.ts',
`import type { StorybookConfig } from '@storybook/react-vite';
it('should create nodes', async () => {
tempFs.createFileSync('my-app/.storybook/main.ts', '');
mockStorybookMainConfig('my-app/.storybook/main.ts', {
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
framework: {
name: '@storybook/react-vite',
options: {},
},
});

import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { mergeConfig } from 'vite';
const config: StorybookConfig = {
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
framework: {
name: '@storybook/react-vite',
options: {},
},
viteFinal: async (config) =>
mergeConfig(config, {
plugins: [nxViteTsPaths()],
}),
};
export default config;`
);
const nodes = createNodesFunction(
const nodes = await createNodesFunction(
'my-app/.storybook/main.ts',
{
buildStorybookTargetName: 'build-storybook',
Expand Down Expand Up @@ -94,7 +84,6 @@ describe('@nx/storybook/plugin', () => {
{ externalDependencies: ['storybook', '@storybook/test-runner'] },
],
});

expect(
nodes?.['projects']?.['my-app']?.targets?.['storybook']
).toMatchObject({
Expand All @@ -107,23 +96,18 @@ describe('@nx/storybook/plugin', () => {
});
});

it('should create angular nodes', () => {
tempFs.createFileSync(
'my-ng-app/.storybook/main.ts',
`import type { StorybookConfig } from '@storybook/angular';
it('should create angular nodes', async () => {
tempFs.createFileSync('my-ng-app/.storybook/main.ts', '');
mockStorybookMainConfig('my-ng-app/.storybook/main.ts', {
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
framework: {
name: '@storybook/angular',
options: {},
},
});

const config: StorybookConfig = {
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
framework: {
name: '@storybook/angular',
options: {},
},
};
export default config;`
);
const nodes = createNodesFunction(
const nodes = await createNodesFunction(
'my-ng-app/.storybook/main.ts',
{
buildStorybookTargetName: 'build-storybook',
Expand Down Expand Up @@ -164,7 +148,6 @@ describe('@nx/storybook/plugin', () => {
},
],
});

expect(
nodes?.['projects']?.['my-ng-app']?.targets?.['storybook']
).toMatchObject({
Expand All @@ -182,24 +165,22 @@ describe('@nx/storybook/plugin', () => {
});
});

it('should support main.js', () => {
tempFs.createFileSync(
'my-react-lib/.storybook/main.js',
`const config = {
stories: ['../src/lib/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: ['@storybook/addon-essentials'],
framework: {
name: '@storybook/react-vite',
options: {
builder: {
viteConfigPath: 'vite.config.js',
},
it('should support main.js', async () => {
tempFs.createFileSync('my-react-lib/.storybook/main.js', '');
mockStorybookMainConfig('my-react-lib/.storybook/main.js', {
stories: ['../src/lib/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: ['@storybook/addon-essentials'],
framework: {
name: '@storybook/react-vite',
options: {
builder: {
viteConfigPath: 'vite.config.js',
},
},
};
export default config;`
);
const nodes = createNodesFunction(
},
});

const nodes = await createNodesFunction(
'my-react-lib/.storybook/main.js',
{
buildStorybookTargetName: 'build-storybook',
Expand Down Expand Up @@ -231,7 +212,6 @@ describe('@nx/storybook/plugin', () => {
{ externalDependencies: ['storybook', '@storybook/test-runner'] },
],
});

expect(
nodes?.['projects']?.['my-react-lib']?.targets?.['storybook']
).toMatchObject({
Expand All @@ -243,4 +223,15 @@ describe('@nx/storybook/plugin', () => {
command: 'test-storybook',
});
});

function mockStorybookMainConfig(
mainTsPath: string,
mainTsConfig: StorybookConfig
) {
jest.mock(
join(tempFs.tempDir, mainTsPath),
() => ({ default: mainTsConfig }),
{ virtual: true }
);
}
});
71 changes: 14 additions & 57 deletions packages/storybook/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { existsSync, readFileSync, readdirSync } from 'fs';
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory';
import { getLockFileName } from '@nx/js';
import { tsquery } from '@phenomnomnominal/tsquery';
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
import type { StorybookConfig } from '@storybook/types';

export interface StorybookPluginOptions {
buildStorybookTargetName?: string;
Expand Down Expand Up @@ -52,7 +53,7 @@ export const createDependencies: CreateDependencies = () => {

export const createNodes: CreateNodes<StorybookPluginOptions> = [
'**/.storybook/main.{js,ts,mjs,mts,cjs,cts}',
(configFilePath, options, context) => {
async (configFilePath, options, context) => {
let projectRoot = '';
if (configFilePath.includes('/.storybook')) {
projectRoot = dirname(configFilePath).replace('/.storybook', '');
Expand Down Expand Up @@ -82,7 +83,7 @@ export const createNodes: CreateNodes<StorybookPluginOptions> = [

const targets = targetsCache[hash]
? targetsCache[hash]
: buildStorybookTargets(
: await buildStorybookTargets(
configFilePath,
projectRoot,
options,
Expand All @@ -105,7 +106,7 @@ export const createNodes: CreateNodes<StorybookPluginOptions> = [
},
];

function buildStorybookTargets(
async function buildStorybookTargets(
configFilePath: string,
projectRoot: string,
options: StorybookPluginOptions,
Expand All @@ -116,9 +117,12 @@ function buildStorybookTargets(

const namedInputs = getNamedInputs(projectRoot, context);

const storybookFramework = getStorybookFramework(configFilePath, context);
const storybookFramework = await getStorybookFramework(
configFilePath,
context
);

const frameworkIsAngular = storybookFramework === "'@storybook/angular'";
const frameworkIsAngular = storybookFramework === '@storybook/angular';

if (frameworkIsAngular && !projectName) {
throw new Error(
Expand Down Expand Up @@ -262,61 +266,14 @@ function serveStaticTarget(
return targetConfig;
}

function getStorybookFramework(
async function getStorybookFramework(
configFilePath: string,
context: CreateNodesContext
): string {
): Promise<string> {
const resolvedPath = join(context.workspaceRoot, configFilePath);
const mainTsJs = readFileSync(resolvedPath, 'utf-8');
const importDeclarations = tsquery.query(
mainTsJs,
'ImportDeclaration:has(ImportSpecifier:has([text="StorybookConfig"]))'
)?.[0];

if (!importDeclarations) {
return parseFrameworkName(mainTsJs);
}

const storybookConfigImportPackage = tsquery.query(
importDeclarations,
'StringLiteral'
)?.[0];

if (storybookConfigImportPackage?.getText() === `'@storybook/core-common'`) {
return parseFrameworkName(mainTsJs);
}

return storybookConfigImportPackage?.getText();
}

function parseFrameworkName(mainTsJs: string) {
const frameworkPropertyAssignment = tsquery.query(
mainTsJs,
`PropertyAssignment:has(Identifier:has([text="framework"]))`
)?.[0];

if (!frameworkPropertyAssignment) {
return undefined;
}

const propertyAssignments = tsquery.query(
frameworkPropertyAssignment,
`PropertyAssignment:has(Identifier:has([text="name"]))`
);

const namePropertyAssignment = propertyAssignments?.find((expression) => {
return expression.getText().startsWith('name');
});

if (!namePropertyAssignment) {
const storybookConfigImportPackage = tsquery.query(
frameworkPropertyAssignment,
'StringLiteral'
)?.[0];
return storybookConfigImportPackage?.getText();
}
const { framework } = await loadConfigFile<StorybookConfig>(resolvedPath);

return tsquery.query(namePropertyAssignment, `StringLiteral`)?.[0]?.getText();
return typeof framework === 'string' ? framework : framework.name;
}

function getOutputs(projectRoot: string): string[] {
Expand Down

0 comments on commit 9a48b69

Please sign in to comment.