-
-
Notifications
You must be signed in to change notification settings - Fork 9.9k
Description
Analysis of commit 533dcc0
Assignee: @copilot
Summary
The same ~12-line pattern for resolving builder options from the framework preset config is duplicated across 6 framework preset files. An existing utility function getBuilderOptions in code/core/src/common/utils/get-builder-options.ts already implements similar logic but is not used by these presets.
Duplication Details
Pattern: core export builder options resolution
-
Severity: Medium
-
Occurrences: 6 instances
-
Locations:
code/frameworks/react-webpack5/src/preset.ts(lines 13–24)code/frameworks/server-webpack5/src/preset.ts(lines 7–18)code/frameworks/angular/src/preset.ts(lines 36–46)code/frameworks/ember/src/preset.ts(lines 43–53)code/frameworks/nextjs/src/preset.ts(lines 24–50)code/frameworks/nextjs-vite/src/preset.ts(lines 22–35)
-
Code Sample (representative, from
server-webpack5/src/preset.ts):export const core: PresetProperty<'core'> = async (config, options) => { const framework = await options.presets.apply('framework'); return { ...config, builder: { name: import.meta.resolve('`@storybook/builder-webpack5`'), options: typeof framework === 'string' ? {} : framework.options.builder || {}, }, renderer: import.meta.resolve('`@storybook/server`/preset'), }; };
The near-identical block above is copy-pasted with only the name and renderer values differing across all 6 files.
Existing Utility (code/core/src/common/utils/get-builder-options.ts):
export async function getBuilderOptions(T extends Record<string, any)>(
options: Options
): Promise(T | Record<string, never)> {
const framework = await options.presets.apply('framework', {}, options);
if (typeof framework !== 'string' && framework?.options?.builder) {
return framework.options.builder;
}
const { builder } = await options.presets.apply('core', {}, options);
if (typeof builder !== 'string' && builder?.options) {
return builder.options as T;
}
return {};
}This utility already handles framework.options.builder extraction, yet the framework presets re-implement this logic inline.
Impact Analysis
- Maintainability: Any change to how builder options are resolved must be made in 6 separate files. A bug fix applied to one preset will not automatically propagate to the others.
- Bug Risk: The inline variants use
framework.options.builder || {}whilegetBuilderOptionsuses optional chaining (framework?.options?.builder), creating subtle inconsistency. If the resolution logic ever needs updating, divergence is likely. - Code Bloat: ~12 lines × 6 files = ~72 lines of near-identical logic that could be reduced to a single shared helper or extended utility.
Refactoring Recommendations
-
Extend
getBuilderOptionsor create a focused helper- Consider extracting a
resolveBuilderOptions(framework)helper incode/core/src/common/utils/get-builder-options.tsthat takes the already-resolvedframeworkvalue (rather thanoptions) so it can be called within thecorepreset export:export function extractBuilderOptions(framework: string | { options?: { builder?: unknown } }) { return typeof framework === 'string' ? {} : (framework.options?.builder ?? {}); }
- Estimated effort: Low (1–2 hours)
- Benefits: Single source of truth for builder options resolution; eliminates 6 duplicate implementations
- Consider extracting a
-
Use the helper in each framework preset
- Update all 6 framework preset files to import and call the shared helper:
import { extractBuilderOptions } from 'storybook/internal/common'; export const core: PresetProperty<'core'> = async (config, options) => { const framework = await options.presets.apply('framework'); return { ...config, builder: { name: import.meta.resolve('`@storybook/builder-webpack5`'), options: extractBuilderOptions(framework), }, renderer: import.meta.resolve('`@storybook/server`/preset'), }; };
- Estimated effort: Low (1–2 hours)
- Benefits: Consistent behavior, easier testing, single fix point
- Update all 6 framework preset files to import and call the shared helper:
Implementation Checklist
- Review duplication findings
- Add
extractBuilderOptions(or similar) tocode/core/src/common/utils/get-builder-options.tsand export it fromstorybook/internal/common - Refactor
code/frameworks/react-webpack5/src/preset.ts - Refactor
code/frameworks/server-webpack5/src/preset.ts - Refactor
code/frameworks/angular/src/preset.ts - Refactor
code/frameworks/ember/src/preset.ts - Refactor
code/frameworks/nextjs/src/preset.ts - Refactor
code/frameworks/nextjs-vite/src/preset.ts - Update tests
- Verify no functionality broken
Analysis Metadata
- Analyzed Files: 6 framework preset files + 1 existing utility
- Detection Method: Serena semantic code analysis + pattern search
- Commit: 533dcc0
- Analysis Date: 2026-03-09T08:28:00Z
Generated by Duplicate Code Detector · ◷
To install this agentic workflow, run
gh aw add github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619