mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
## Summary
Fixes Storybook rendering issue where all components fail to load with
`_sfc_main is not defined` error in **local development** since v1.29.3.
## Problem
After upgrading to v1.29.3, all Storybook components fail to render **in
local development** (`pnpm storybook`) with the following error:
```
ReferenceError: _sfc_main is not defined
The component failed to render properly, likely due to a configuration issue in Storybook.
```
**Important**: This issue only affects **local development**
environments. The deployed/built Storybook works correctly.
This affects both:
- Main Storybook (`pnpm storybook`)
- Desktop-ui Storybook instances
## Root Cause
In v1.29.3, commit `64430708e` ("perf: tree shaking and minify #6068")
enabled build optimizations in `vite.config.mts`:
```typescript
// Before (v1.29.2)
rollupOptions: {
treeshake: false
}
esbuild: {
minifyIdentifiers: false
}
// After (v1.29.3)
rollupOptions: {
treeshake: true // ⚠️ Enabled
}
esbuild: {
minifyIdentifiers: SHOULD_MINIFY // ⚠️ Conditionally enabled
}
```
While these optimizations are beneficial for production builds, they
cause issues in **Storybook's local dev server**:
1. **Tree-shaking in dev mode**: Rollup incorrectly identifies Vue SFC's
`_sfc_main` exports as unused code during the dev server's module
transformation
2. **Identifier minification**: esbuild minifies `_sfc_main` to shorter
names in development, breaking Storybook's HMR (Hot Module Replacement)
and dynamic module loading
Since Storybook's `main.ts` inherits settings from `vite.config.mts` via
`mergeConfig`, these optimizations were applied to Storybook's dev
server configuration, causing Vue components to fail rendering in local
development.
**Why deployed Storybook works**: Production builds have different
optimization pipelines that handle Vue SFCs correctly, but the dev
server's real-time transformation breaks with these settings.
## Solution
Added explicit build configuration overrides in both Storybook
configurations to ensure the **dev server** doesn't inherit problematic
optimizations:
**Files changed:**
- `.storybook/main.ts`
- `apps/desktop-ui/.storybook/main.ts`
**Changes:**
```typescript
esbuild: {
// Prevent minification of identifiers to preserve _sfc_main in dev mode
minifyIdentifiers: false,
keepNames: true
},
build: {
rollupOptions: {
// Disable tree-shaking for Storybook dev server to prevent Vue SFC exports from being removed
treeshake: false,
// ... existing onwarn config
}
}
```
This ensures Storybook's **local development server** prioritizes
stability and debuggability over bundle size optimization, while
production builds continue to benefit from tree-shaking and
minification.
## Testing
1. Cleared Storybook and Vite caches: `rm -rf .storybook/.cache
node_modules/.vite`
2. Started local Storybook dev server with `pnpm storybook`
3. Verified all component stories render correctly without `_sfc_main`
errors
4. Ran `pnpm typecheck` to ensure TypeScript compilation succeeds
5. Tested HMR (Hot Module Replacement) works correctly with component
changes
## Context
- This is a **local development-only** issue; deployed Storybook builds
work fine
- Storybook dev server requires special handling because it dynamically
imports and hot-reloads all stories at runtime
- Vue SFC compilation generates `_sfc_main` as an internal identifier
that must be preserved during dev transformations
- Development tools like Storybook benefit from unoptimized builds for
better debugging, HMR, and stability
- Production builds remain optimized with tree-shaking and minification
enabled
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6635-bugfix-Fix-Storybook-_sfc_main-undefined-error-after-v1-29-3-2a56d73d36508194a25eef56789e5e2b)
by [Unito](https://www.unito.io)
111 lines
3.4 KiB
TypeScript
111 lines
3.4 KiB
TypeScript
import type { StorybookConfig } from '@storybook/vue3-vite'
|
|
import { FileSystemIconLoader } from 'unplugin-icons/loaders'
|
|
import IconsResolver from 'unplugin-icons/resolver'
|
|
import Icons from 'unplugin-icons/vite'
|
|
import Components from 'unplugin-vue-components/vite'
|
|
import type { InlineConfig } from 'vite'
|
|
|
|
const config: StorybookConfig = {
|
|
stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
|
addons: ['@storybook/addon-docs'],
|
|
framework: {
|
|
name: '@storybook/vue3-vite',
|
|
options: {}
|
|
},
|
|
staticDirs: [{ from: '../public', to: '/' }],
|
|
async viteFinal(config) {
|
|
// Use dynamic import to avoid CJS deprecation warning
|
|
const { mergeConfig } = await import('vite')
|
|
const { default: tailwindcss } = await import('@tailwindcss/vite')
|
|
|
|
// Filter out any plugins that might generate import maps
|
|
if (config.plugins) {
|
|
config.plugins = config.plugins
|
|
// Type guard: ensure we have valid plugin objects with names
|
|
.filter(
|
|
(plugin): plugin is NonNullable<typeof plugin> & { name: string } => {
|
|
return (
|
|
plugin !== null &&
|
|
plugin !== undefined &&
|
|
typeof plugin === 'object' &&
|
|
'name' in plugin &&
|
|
typeof plugin.name === 'string'
|
|
)
|
|
}
|
|
)
|
|
// Business logic: filter out import-map plugins
|
|
.filter((plugin) => !plugin.name.includes('import-map'))
|
|
}
|
|
|
|
return mergeConfig(config, {
|
|
// Replace plugins entirely to avoid inheritance issues
|
|
plugins: [
|
|
// Only include plugins we explicitly need for Storybook
|
|
tailwindcss(),
|
|
Icons({
|
|
compiler: 'vue3',
|
|
customCollections: {
|
|
comfy: FileSystemIconLoader(
|
|
process.cwd() + '/../../packages/design-system/src/icons'
|
|
)
|
|
}
|
|
}),
|
|
Components({
|
|
dts: false, // Disable dts generation in Storybook
|
|
resolvers: [
|
|
IconsResolver({
|
|
customCollections: ['comfy']
|
|
})
|
|
],
|
|
dirs: [
|
|
process.cwd() + '/src/components',
|
|
process.cwd() + '/src/views'
|
|
],
|
|
deep: true,
|
|
extensions: ['vue'],
|
|
directoryAsNamespace: true
|
|
})
|
|
],
|
|
server: {
|
|
allowedHosts: true
|
|
},
|
|
resolve: {
|
|
alias: {
|
|
'@': process.cwd() + '/src',
|
|
'@frontend-locales': process.cwd() + '/../../src/locales'
|
|
}
|
|
},
|
|
esbuild: {
|
|
// Prevent minification of identifiers to preserve _sfc_main
|
|
minifyIdentifiers: false,
|
|
keepNames: true
|
|
},
|
|
build: {
|
|
rollupOptions: {
|
|
// Disable tree-shaking for Storybook to prevent Vue SFC exports from being removed
|
|
treeshake: false,
|
|
onwarn: (warning, warn) => {
|
|
// Suppress specific warnings
|
|
if (
|
|
warning.code === 'UNUSED_EXTERNAL_IMPORT' &&
|
|
warning.message?.includes('resolveComponent')
|
|
) {
|
|
return
|
|
}
|
|
// Suppress Storybook font asset warnings
|
|
if (
|
|
warning.code === 'UNRESOLVED_IMPORT' &&
|
|
warning.message?.includes('nunito-sans')
|
|
) {
|
|
return
|
|
}
|
|
warn(warning)
|
|
}
|
|
},
|
|
chunkSizeWarningLimit: 1000
|
|
}
|
|
} satisfies InlineConfig)
|
|
}
|
|
}
|
|
export default config
|