fix(storybook): improve speed of storybook plugin (#31277)
## Current Behavior #22953 updated the way that storybook parsing works to always do full TS tree resolution instead of AST parsing. While this is more accurate, it's orders of magnitude slower...creating a bottleneck in graph creation for larger repos which use the plugin. The only reason we need to do this complex functionality is to determine if we use angular or not. ## Expected Behavior Graph creation should be quite fast. This PR returns the old behavior, and uses the new behavior as an additive fallback. In most cases this will result in extremely fast parsing when the framework is defined inline, and in the failure case, it will result in unnoticeably slower parsing as the incremental difference is minor. Before: ``` Time for '@nx/storybook/plugin:createNodes' 13536.203667 ``` After: ``` Time for '@nx/storybook/plugin:createNodes' 292.584667 ``` An alternative solve (at least in our case) would be to add an option to skip angular detection...essentially letting people bypass the whole reason for doing this config parsing. Although that's probably not a sustainable option. NOTE: A majority of the remaining slowness in this plugin is spent hashing the files for the target cache. If we wanted to, we could further speed this up by making some assumptions there...but that may drastically harm repos which rely on the fully resolution behavior ## Related Issue(s) Fixes #31276
This commit is contained in:
parent
7e0719cc0a
commit
3a33d5f54f
@ -5,7 +5,6 @@ import {
|
|||||||
createNodesFromFiles,
|
createNodesFromFiles,
|
||||||
CreateNodesV2,
|
CreateNodesV2,
|
||||||
detectPackageManager,
|
detectPackageManager,
|
||||||
getPackageManagerCommand,
|
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
logger,
|
logger,
|
||||||
parseJson,
|
parseJson,
|
||||||
@ -22,6 +21,7 @@ import { getLockFileName } from '@nx/js';
|
|||||||
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
|
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
|
||||||
import type { StorybookConfig } from '@storybook/types';
|
import type { StorybookConfig } from '@storybook/types';
|
||||||
import { hashObject } from 'nx/src/hasher/file-hasher';
|
import { hashObject } from 'nx/src/hasher/file-hasher';
|
||||||
|
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||||
|
|
||||||
export interface StorybookPluginOptions {
|
export interface StorybookPluginOptions {
|
||||||
buildStorybookTargetName?: string;
|
buildStorybookTargetName?: string;
|
||||||
@ -163,10 +163,11 @@ async function buildStorybookTargets(
|
|||||||
|
|
||||||
const namedInputs = getNamedInputs(projectRoot, context);
|
const namedInputs = getNamedInputs(projectRoot, context);
|
||||||
|
|
||||||
const storybookFramework = await getStorybookFramework(
|
// First attempt to do a very fast lookup for the framework
|
||||||
configFilePath,
|
// If that fails, the framework might be inherited, so do a very heavyweight lookup
|
||||||
context
|
const storybookFramework =
|
||||||
);
|
(await getStorybookFramework(configFilePath, context)) ||
|
||||||
|
(await getStorybookFullyResolvedFramework(configFilePath, context));
|
||||||
|
|
||||||
const frameworkIsAngular = storybookFramework === '@storybook/angular';
|
const frameworkIsAngular = storybookFramework === '@storybook/angular';
|
||||||
|
|
||||||
@ -328,6 +329,63 @@ function serveStaticTarget(
|
|||||||
async function getStorybookFramework(
|
async function getStorybookFramework(
|
||||||
configFilePath: string,
|
configFilePath: string,
|
||||||
context: CreateNodesContext
|
context: CreateNodesContext
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
return tsquery.query(namePropertyAssignment, `StringLiteral`)?.[0]?.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getStorybookFullyResolvedFramework(
|
||||||
|
configFilePath: string,
|
||||||
|
context: CreateNodesContext
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const resolvedPath = join(context.workspaceRoot, configFilePath);
|
const resolvedPath = join(context.workspaceRoot, configFilePath);
|
||||||
const { framework } = await loadConfigFile<StorybookConfig>(resolvedPath);
|
const { framework } = await loadConfigFile<StorybookConfig>(resolvedPath);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user