@storybook-astro/framework 1.2.0 → 1.3.0-canary.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/{base-IRZo3zgK.d.ts → base-DT67T5pi.d.ts} +1 -0
  2. package/dist/{chunk-T7NWIO5S.js → chunk-2EABPTOY.js} +5 -5
  3. package/dist/{chunk-PJEDXZVN.js → chunk-7YBE4TTI.js} +2 -1
  4. package/dist/chunk-7YBE4TTI.js.map +1 -0
  5. package/dist/{chunk-POHTFYST.js → chunk-AYYMNFI6.js} +104 -6
  6. package/dist/chunk-AYYMNFI6.js.map +1 -0
  7. package/dist/{chunk-OUEDTRBO.js → chunk-B454DGX6.js} +259 -67
  8. package/dist/chunk-B454DGX6.js.map +1 -0
  9. package/dist/chunk-B5HHF6FC.js +116 -0
  10. package/dist/chunk-B5HHF6FC.js.map +1 -0
  11. package/dist/chunk-CU57AJUW.js +1402 -0
  12. package/dist/chunk-CU57AJUW.js.map +1 -0
  13. package/dist/{chunk-DNGQBPT7.js → chunk-PUTCAN6X.js} +5 -2
  14. package/dist/{chunk-DNGQBPT7.js.map → chunk-PUTCAN6X.js.map} +1 -1
  15. package/dist/{chunk-4SWPVM6R.js → chunk-WUTCMEF5.js} +2 -2
  16. package/dist/index.d.ts +17 -7
  17. package/dist/index.js +6 -5
  18. package/dist/index.js.map +1 -1
  19. package/dist/integrations/index.d.ts +2 -1
  20. package/dist/integrations/index.js +1 -1
  21. package/dist/middleware.js +18 -131
  22. package/dist/middleware.js.map +1 -1
  23. package/dist/{types-C-jan6Px.d.ts → preset-BvgHg2of.d.ts} +8 -11
  24. package/dist/preset.d.ts +2 -10
  25. package/dist/preset.js +5 -4
  26. package/dist/renderer/renderer-dev.js +62 -0
  27. package/dist/renderer/renderer-dev.js.map +1 -0
  28. package/dist/renderer/renderer-server.js +92 -0
  29. package/dist/renderer/renderer-server.js.map +1 -0
  30. package/dist/renderer/renderer-static.js +54 -0
  31. package/dist/renderer/renderer-static.js.map +1 -0
  32. package/dist/testing.js +12 -11
  33. package/dist/testing.js.map +1 -1
  34. package/dist/{viteStorybookAstroMiddlewarePlugin-2EFKTECT.js → viteStorybookAstroMiddlewarePlugin-UB6ZLJ4B.js} +4 -3
  35. package/dist/vitest/global-setup.js +6 -5
  36. package/dist/vitest/global-setup.js.map +1 -1
  37. package/dist/vitest/index.d.ts +1 -1
  38. package/dist/vitest/index.js +3 -3
  39. package/package.json +14 -43
  40. package/src/astroImageService.ts +57 -0
  41. package/src/astroRenderHandler.ts +205 -0
  42. package/src/importAstroConfig.ts +1 -1
  43. package/src/index.ts +2 -0
  44. package/src/integrations/alpine.ts +1 -0
  45. package/src/integrations/base.ts +6 -0
  46. package/src/lib/revive-dates.test.ts +106 -0
  47. package/src/lib/revive-dates.ts +51 -0
  48. package/src/middleware.ts +29 -200
  49. package/src/module-mocks.ts +153 -5
  50. package/src/preset.ts +38 -8
  51. package/src/productionRenderRuntime.ts +187 -0
  52. package/src/rules.test.ts +52 -4
  53. package/src/rules.ts +54 -7
  54. package/src/server/index.ts +101 -31
  55. package/src/storyRulesRuntime.ts +34 -0
  56. package/src/storySsrVite.ts +240 -0
  57. package/src/types.ts +0 -9
  58. package/src/virtual.d.ts +17 -3
  59. package/src/vite/{astroFilesVirtualModulePlugin.ts → astroFilesPlugin.ts} +4 -4
  60. package/src/vite/sanitizeConfigPlugin.ts +18 -0
  61. package/src/vite/{storybookAstroServerAuthConfigVirtualModulePlugin.test.ts → serverAuthPlugin.test.ts} +7 -10
  62. package/src/vite/{storybookAstroServerAuthConfigVirtualModulePlugin.ts → serverAuthPlugin.ts} +6 -9
  63. package/src/vite/serverRuntimePlugin.ts +109 -0
  64. package/src/vite/{storybookAstroRulesConfigVirtualModulePlugin.ts → storyRulesPlugin.ts} +6 -7
  65. package/src/vite/{createVirtualModulePlugin.test.ts → virtualModulePlugin.test.ts} +5 -5
  66. package/src/vite/{createVirtualModulePlugin.ts → virtualModulePlugin.ts} +2 -2
  67. package/src/viteAstroContainerRenderersPlugin.ts +72 -2
  68. package/src/vitePluginAstroBuildPrerender.ts +75 -646
  69. package/src/vitePluginAstroBuildServer.ts +217 -165
  70. package/src/vitePluginAstroBuildShared.test.ts +87 -0
  71. package/src/vitePluginAstroBuildShared.ts +465 -0
  72. package/src/vitePluginStoryModuleMocks.ts +29 -0
  73. package/src/viteStorybookAstroMiddlewarePlugin.ts +8 -0
  74. package/src/viteStorybookAstroRendererPlugin.ts +13 -6
  75. package/src/viteStorybookRendererFallbackPlugin.ts +2 -2
  76. package/dist/chunk-OUEDTRBO.js.map +0 -1
  77. package/dist/chunk-PBISP7PA.js +0 -1137
  78. package/dist/chunk-PBISP7PA.js.map +0 -1
  79. package/dist/chunk-PJEDXZVN.js.map +0 -1
  80. package/dist/chunk-POHTFYST.js.map +0 -1
  81. package/dist/node/index.d.ts +0 -10
  82. package/dist/node/index.js +0 -10
  83. package/dist/node/index.js.map +0 -1
  84. package/src/vite/storybookAstroSanitizationConfigVirtualModulePlugin.ts +0 -21
  85. /package/dist/{chunk-T7NWIO5S.js.map → chunk-2EABPTOY.js.map} +0 -0
  86. /package/dist/{chunk-4SWPVM6R.js.map → chunk-WUTCMEF5.js.map} +0 -0
  87. /package/dist/{viteStorybookAstroMiddlewarePlugin-2EFKTECT.js.map → viteStorybookAstroMiddlewarePlugin-UB6ZLJ4B.js.map} +0 -0
package/src/middleware.ts CHANGED
@@ -1,51 +1,34 @@
1
+ /// <reference path="./virtual.d.ts" />
2
+
1
3
  import { pathToFileURL } from 'node:url';
2
4
  import { experimental_AstroContainer as AstroContainer } from 'astro/container';
5
+ import { ensureAstroPassthroughImageService } from './astroImageService.ts';
6
+ import { createAstroRenderHandler, type HandlerProps } from './astroRenderHandler.ts';
3
7
  import type { Integration } from './integrations/index.ts';
4
- import { installPassthroughImageService } from './lib/passthrough-image-service.ts';
5
8
  import type { SanitizationOptions } from './lib/sanitization.ts';
6
- import { resolveSanitizationOptions, sanitizeRenderPayload } from './lib/sanitization.ts';
7
- import { resolveStoryModuleMock, withStoryModuleMocks } from './module-mocks.ts';
8
- import { selectStoryRules, withStoryRuleCleanups } from './rules.ts';
9
- import type { RenderStoryInput } from './types.ts';
9
+ import { resolveStoryModuleMock } from './module-mocks.ts';
10
10
  import { addRenderers, resolveClientModules } from 'virtual:astro-container-renderers';
11
11
 
12
12
  type ResolveRulesConfigModule = () => unknown | Promise<unknown>;
13
13
 
14
- type AstroCreateResult = {
15
- createAstro?: (...args: unknown[]) => unknown;
16
- };
17
-
18
- type AstroComponentFactory = ((
19
- result: AstroCreateResult,
20
- props: unknown,
21
- slots: unknown
22
- ) => unknown) & {
23
- isAstroComponentFactory?: boolean;
24
- moduleId?: string;
25
- propagation?: unknown;
26
- };
27
-
28
- export type HandlerProps = {
29
- component: string;
30
- args?: Record<string, unknown>;
31
- slots?: Record<string, unknown>;
32
- story?: RenderStoryInput;
33
- };
34
-
35
14
  type HandlerFactoryOptions = {
36
15
  sanitization?: SanitizationOptions;
37
16
  rulesConfigFilePath?: string;
38
17
  resolveRulesConfigModule?: ResolveRulesConfigModule;
39
18
  loadModule?: (id: string) => Promise<{ default: unknown }>;
19
+ invalidateModuleGraph?: () => void;
20
+ resolveModule?: (specifier: string) => string | undefined;
40
21
  };
41
22
 
42
- export async function handlerFactory(_integrations: Integration[], options?: HandlerFactoryOptions) {
43
- // Inject a passthrough image service before any component renders. See
44
- // `lib/passthrough-image-service.ts` for why this is necessary.
45
- installPassthroughImageService();
23
+ export type { HandlerProps };
24
+
25
+ export async function handlerFactory(
26
+ _integrations: Integration[],
27
+ options?: HandlerFactoryOptions
28
+ ) {
29
+ ensureAstroPassthroughImageService();
46
30
 
47
31
  const container = await AstroContainer.create({
48
- // Somewhat hacky way to force client-side Storybook's Vite to resolve modules properly
49
32
  resolve: async (specifier) => {
50
33
  const mockedModule = resolveStoryModuleMock(specifier);
51
34
 
@@ -53,6 +36,12 @@ export async function handlerFactory(_integrations: Integration[], options?: Han
53
36
  return mockedModule;
54
37
  }
55
38
 
39
+ const customResolution = options?.resolveModule?.(specifier);
40
+
41
+ if (customResolution) {
42
+ return customResolution;
43
+ }
44
+
56
45
  if (specifier.startsWith('astro:scripts')) {
57
46
  return `/@id/${specifier}`;
58
47
  }
@@ -68,7 +57,7 @@ export async function handlerFactory(_integrations: Integration[], options?: Han
68
57
  });
69
58
 
70
59
  addRenderers(container);
71
- const sanitizationOptions = resolveSanitizationOptions(options?.sanitization);
60
+
72
61
  const loadModule =
73
62
  options?.loadModule ??
74
63
  ((id: string) => {
@@ -76,173 +65,13 @@ export async function handlerFactory(_integrations: Integration[], options?: Han
76
65
 
77
66
  return import(/* @vite-ignore */ normalizedId);
78
67
  });
79
- const componentCache = new Map<string, Promise<AstroComponentFactory>>();
80
- let renderQueue = Promise.resolve<void>(undefined);
81
-
82
- async function loadPatchedComponent(componentId: string, useCache = true) {
83
- if (!useCache) {
84
- const { default: component } = await loadModule(componentId);
85
-
86
- return patchCreateAstroCompat(component);
87
- }
88
-
89
- if (!componentCache.has(componentId)) {
90
- componentCache.set(componentId, (async () => {
91
- const { default: component } = await loadModule(componentId);
92
-
93
- return patchCreateAstroCompat(component);
94
- })());
95
- }
96
-
97
- const cachedComponent = componentCache.get(componentId);
98
-
99
- if (!cachedComponent) {
100
- throw new Error(`Failed to load Astro component: ${componentId}`);
101
- }
102
-
103
- try {
104
- return await cachedComponent;
105
- } catch (error) {
106
- // Drop failed entries so transient/module errors can recover on the next request.
107
- componentCache.delete(componentId);
108
- throw error;
109
- }
110
- }
111
-
112
- return async function handler(data: HandlerProps) {
113
- const executeRender = async () => {
114
- const rulesConfigModule = options?.resolveRulesConfigModule
115
- ? await options.resolveRulesConfigModule()
116
- : undefined;
117
-
118
- const selectedRules = await selectStoryRules({
119
- configModule: rulesConfigModule,
120
- configFilePath: options?.rulesConfigFilePath,
121
- story: data.story
122
- });
123
-
124
- return withStoryRuleCleanups(selectedRules.cleanups, async () => {
125
- return withStoryModuleMocks(selectedRules.moduleMocks, async () => {
126
- const patchedComponent = await loadPatchedComponent(
127
- data.component,
128
- selectedRules.moduleMocks.size === 0
129
- );
130
- const processedArgs = await processImageMetadata(data.args ?? {});
131
- const sanitizedPayload = sanitizeRenderPayload(
132
- {
133
- args: processedArgs,
134
- slots: data.slots ?? {}
135
- },
136
- sanitizationOptions
137
- );
138
-
139
- return container.renderToString(
140
- patchedComponent as Parameters<typeof container.renderToString>[0],
141
- {
142
- props: sanitizedPayload.args,
143
- slots: sanitizedPayload.slots
144
- }
145
- );
146
- });
147
- });
148
- };
149
-
150
- const resultPromise = renderQueue.then(executeRender, executeRender);
151
-
152
- renderQueue = resultPromise.then(
153
- () => undefined,
154
- () => undefined
155
- );
156
-
157
- return resultPromise;
158
- };
159
- }
160
-
161
- function patchCreateAstroCompat(component: unknown): AstroComponentFactory {
162
- if (typeof component !== 'function') {
163
- throw new Error('Expected Astro component factory to be a function.');
164
- }
165
-
166
- const originalComponent = component as AstroComponentFactory;
167
- const wrapped = ((result: AstroCreateResult, props: unknown, slots: unknown) => {
168
- if (result && typeof result.createAstro === 'function') {
169
- const originalCreateAstro = result.createAstro;
170
- const runtimeExpectsAstroGlobal = originalCreateAstro.length >= 3;
171
68
 
172
- result.createAstro = (...args: unknown[]) => {
173
- if (args.length === 3 && !runtimeExpectsAstroGlobal) {
174
- return originalCreateAstro(args[1], args[2]);
175
- }
176
-
177
- return originalCreateAstro(...args);
178
- };
179
- }
180
-
181
- return originalComponent(result, props, slots);
182
- }) as AstroComponentFactory;
183
-
184
- wrapped.isAstroComponentFactory = originalComponent.isAstroComponentFactory;
185
- wrapped.moduleId = originalComponent.moduleId;
186
- wrapped.propagation = originalComponent.propagation;
187
-
188
- return wrapped;
189
- }
190
-
191
- async function processImageMetadata(
192
- args: Record<string, unknown>
193
- ): Promise<Record<string, unknown>> {
194
- const processed: Record<string, unknown> = {};
195
-
196
- for (const [key, value] of Object.entries(args)) {
197
- if (isImageMetadata(value)) {
198
- // Keep ImageMetadata as a plain object — Astro's image service checks
199
- // isESMImportedImage (typeof src === 'object') and skips the /@fs/ string
200
- // validation that throws LocalImageUsedWrongly. Converting to a URL string
201
- // causes that error when the string starts with /@fs/.
202
- processed[key] = value;
203
-
204
- continue;
205
- }
206
-
207
- if (Array.isArray(value)) {
208
- processed[key] = await Promise.all(
209
- value.map(async (item) => {
210
- if (isImageMetadata(item)) {
211
- return item;
212
- }
213
-
214
- if (isRecord(item)) {
215
- return processImageMetadata(item);
216
- }
217
-
218
- return item;
219
- })
220
- );
221
-
222
- continue;
223
- }
224
-
225
- if (isRecord(value)) {
226
- processed[key] = await processImageMetadata(value);
227
-
228
- continue;
229
- }
230
-
231
- processed[key] = value;
232
- }
233
-
234
- return processed;
235
- }
236
-
237
- function isImageMetadata(value: unknown): value is Record<string, unknown> {
238
- return (
239
- isRecord(value) &&
240
- typeof value.src === 'string' &&
241
- ('width' in value || 'height' in value || 'format' in value)
242
- );
243
- }
244
-
245
-
246
- function isRecord(value: unknown): value is Record<string, unknown> {
247
- return typeof value === 'object' && value !== null;
69
+ return createAstroRenderHandler({
70
+ container,
71
+ sanitization: options?.sanitization,
72
+ rulesConfigFilePath: options?.rulesConfigFilePath,
73
+ resolveRulesConfigModule: options?.resolveRulesConfigModule,
74
+ loadModule,
75
+ invalidateModuleGraph: options?.invalidateModuleGraph
76
+ });
248
77
  }
@@ -1,16 +1,164 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
1
+ const STORYBOOK_ASTRO_GET_MOCK_EXPORT = '__STORYBOOK_ASTRO_GET_STORY_MODULE_MOCK_EXPORT__';
2
2
 
3
- export type StoryModuleMocks = Map<string, string>;
3
+ export const STORYBOOK_ASTRO_INLINE_MODULE_PREFIX = 'virtual:storybook-astro-inline-module:';
4
4
 
5
- const moduleMockStorage = new AsyncLocalStorage<StoryModuleMocks>();
5
+ export type StoryModuleMockEntry = {
6
+ replacement: string;
7
+ inlineModuleId?: string;
8
+ };
9
+
10
+ export type StoryModuleMockFactoryResult = Record<string, unknown>;
11
+
12
+ export type StoryModuleMocks = Map<string, StoryModuleMockEntry>;
13
+
14
+ type StoryModuleMocksGlobal = typeof globalThis & {
15
+ __STORYBOOK_ASTRO_STORY_MODULE_MOCK_STATE__?: {
16
+ activeModuleMocksStack: StoryModuleMocks[];
17
+ inlineModuleExports: Map<string, StoryModuleMockFactoryResult>;
18
+ inlineModuleSequence: number;
19
+ };
20
+ __STORYBOOK_ASTRO_GET_STORY_MODULE_MOCK_EXPORT__?: (moduleId: string, exportName: string) => unknown;
21
+ };
22
+
23
+ const moduleMocksGlobal = globalThis as StoryModuleMocksGlobal;
24
+
25
+ const moduleMockState =
26
+ moduleMocksGlobal.__STORYBOOK_ASTRO_STORY_MODULE_MOCK_STATE__ ??
27
+ (moduleMocksGlobal.__STORYBOOK_ASTRO_STORY_MODULE_MOCK_STATE__ = {
28
+ activeModuleMocksStack: [],
29
+ inlineModuleExports: new Map<string, StoryModuleMockFactoryResult>(),
30
+ inlineModuleSequence: 0
31
+ });
32
+
33
+ if (typeof moduleMocksGlobal[STORYBOOK_ASTRO_GET_MOCK_EXPORT] !== 'function') {
34
+ moduleMocksGlobal[STORYBOOK_ASTRO_GET_MOCK_EXPORT] = getStoryModuleMockExport;
35
+ }
6
36
 
7
37
  export async function withStoryModuleMocks<T>(
8
38
  moduleMocks: StoryModuleMocks,
9
39
  callback: () => Promise<T>
10
40
  ): Promise<T> {
11
- return moduleMockStorage.run(moduleMocks, callback);
41
+ moduleMockState.activeModuleMocksStack.push(moduleMocks);
42
+
43
+ try {
44
+ return await callback();
45
+ } finally {
46
+ moduleMockState.activeModuleMocksStack.pop();
47
+ cleanupInlineModuleMocks(moduleMocks);
48
+ }
12
49
  }
13
50
 
14
51
  export function resolveStoryModuleMock(specifier: string): string | undefined {
15
- return moduleMockStorage.getStore()?.get(specifier);
52
+ return getActiveModuleMocks()?.get(specifier)?.replacement;
53
+ }
54
+
55
+ export function createPathStoryModuleMock(replacement: string): StoryModuleMockEntry {
56
+ return {
57
+ replacement
58
+ };
59
+ }
60
+
61
+ export function createInlineStoryModuleMock(
62
+ exportsObject: StoryModuleMockFactoryResult
63
+ ): StoryModuleMockEntry {
64
+ const inlineModuleId = `storybook-astro-inline-module:${moduleMockState.inlineModuleSequence}`;
65
+
66
+ moduleMockState.inlineModuleSequence += 1;
67
+ moduleMockState.inlineModuleExports.set(inlineModuleId, exportsObject);
68
+
69
+ return {
70
+ replacement: `${STORYBOOK_ASTRO_INLINE_MODULE_PREFIX}${inlineModuleId}`,
71
+ inlineModuleId
72
+ };
73
+ }
74
+
75
+ export function loadStoryInlineModule(resolvedId: string): string | undefined {
76
+ const inlineModuleId = normalizeInlineModuleId(resolvedId);
77
+
78
+ if (!inlineModuleId) {
79
+ return undefined;
80
+ }
81
+
82
+ const exportsObject = moduleMockState.inlineModuleExports.get(inlineModuleId);
83
+
84
+ if (!exportsObject) {
85
+ return undefined;
86
+ }
87
+
88
+ return createInlineModuleSource(inlineModuleId, exportsObject);
89
+ }
90
+
91
+ function cleanupInlineModuleMocks(moduleMocks: StoryModuleMocks) {
92
+ for (const mockEntry of moduleMocks.values()) {
93
+ if (mockEntry.inlineModuleId) {
94
+ moduleMockState.inlineModuleExports.delete(mockEntry.inlineModuleId);
95
+ }
96
+ }
97
+ }
98
+
99
+ function getActiveModuleMocks(): StoryModuleMocks | undefined {
100
+ return moduleMockState.activeModuleMocksStack[moduleMockState.activeModuleMocksStack.length - 1];
101
+ }
102
+
103
+ function getStoryModuleMockExport(moduleId: string, exportName: string): unknown {
104
+ const exportsObject = moduleMockState.inlineModuleExports.get(moduleId);
105
+
106
+ if (!exportsObject) {
107
+ throw new Error(`Inline story module mock is unavailable: ${moduleId}`);
108
+ }
109
+
110
+ return exportsObject[exportName];
111
+ }
112
+
113
+ function createInlineModuleSource(
114
+ inlineModuleId: string,
115
+ exportsObject: StoryModuleMockFactoryResult
116
+ ): string {
117
+ const exportNames = Object.keys(exportsObject);
118
+ const sourceLines = [
119
+ `const getStoryModuleMockExport = globalThis.${STORYBOOK_ASTRO_GET_MOCK_EXPORT};`,
120
+ "if (typeof getStoryModuleMockExport !== 'function') {",
121
+ " throw new Error('Inline story module mock helper is unavailable.');",
122
+ '}',
123
+ ''
124
+ ];
125
+
126
+ if (Object.prototype.hasOwnProperty.call(exportsObject, 'default')) {
127
+ sourceLines.push(
128
+ `export default getStoryModuleMockExport(${JSON.stringify(inlineModuleId)}, 'default');`
129
+ );
130
+ }
131
+
132
+ for (const exportName of exportNames) {
133
+ if (exportName === 'default') {
134
+ continue;
135
+ }
136
+
137
+ assertValidExportName(exportName);
138
+ sourceLines.push(
139
+ `export const ${exportName} = getStoryModuleMockExport(${JSON.stringify(inlineModuleId)}, ${JSON.stringify(exportName)});`
140
+ );
141
+ }
142
+
143
+ if (sourceLines[sourceLines.length - 1] === '') {
144
+ sourceLines.push('export {};');
145
+ }
146
+
147
+ return sourceLines.join('\n');
148
+ }
149
+
150
+ function assertValidExportName(exportName: string) {
151
+ if (!/^[$A-Z_a-z][$\w]*$/u.test(exportName)) {
152
+ throw new Error(`Story module mock export name is invalid: ${exportName}`);
153
+ }
154
+ }
155
+
156
+ function normalizeInlineModuleId(resolvedId: string): string | undefined {
157
+ const normalizedId = resolvedId.startsWith('\0') ? resolvedId.slice(1) : resolvedId;
158
+
159
+ if (!normalizedId.startsWith(STORYBOOK_ASTRO_INLINE_MODULE_PREFIX)) {
160
+ return undefined;
161
+ }
162
+
163
+ return normalizedId.slice(STORYBOOK_ASTRO_INLINE_MODULE_PREFIX.length);
16
164
  }
package/src/preset.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { dirname } from 'node:path';
1
2
  import type { StorybookConfigVite, FrameworkOptions } from './types.ts';
2
3
  import { vitePluginStorybookAstroMiddleware } from './viteStorybookAstroMiddlewarePlugin.ts';
3
4
  import { viteStorybookRendererFallbackPlugin } from './viteStorybookRendererFallbackPlugin.ts';
@@ -23,15 +24,19 @@ export const core = {
23
24
  renderer: import.meta.resolve('@storybook-astro/renderer')
24
25
  };
25
26
 
26
- export const viteFinal: StorybookConfigVite['viteFinal'] = async (config, { configType, presets }) => {
27
- const options = await presets.apply<FrameworkOptions>('frameworkOptions');
27
+ export const viteFinal: StorybookConfigVite['viteFinal'] = async (config, storybookOptions) => {
28
+ const { configType, presets, configDir } = storybookOptions;
29
+ const frameworkOptions = await presets.apply<FrameworkOptions>('frameworkOptions');
30
+ const options = {
31
+ ...frameworkOptions,
32
+ resolveFrom: frameworkOptions.resolveFrom ?? dirname(configDir)
33
+ } satisfies FrameworkOptions;
28
34
 
29
35
  if (!config.plugins) {
30
36
  config.plugins = [];
31
37
  }
32
38
 
33
39
  const integrations = options.integrations ?? [];
34
- const resolveFrom = options.resolveFrom ?? process.cwd();
35
40
  const renderMode = options.renderMode ?? 'server';
36
41
  const mode = configType === 'DEVELOPMENT' ? 'development' : 'production';
37
42
  const command = configType === 'DEVELOPMENT' ? 'serve' : 'build';
@@ -54,7 +59,7 @@ export const viteFinal: StorybookConfigVite['viteFinal'] = async (config, { conf
54
59
  vitePluginAstroComponentMarker() as any,
55
60
  vitePluginAstroIntegrationOptsFallback(),
56
61
  vitePluginAstroToolbarFallback(),
57
- vitePluginAstroVueFallback(),
62
+ vitePluginAstroVueFallback()
58
63
  );
59
64
 
60
65
  if (configType === 'DEVELOPMENT') {
@@ -97,7 +102,13 @@ export const viteFinal: StorybookConfigVite['viteFinal'] = async (config, { conf
97
102
  aliases['react-dom'] = 'react-dom';
98
103
  }
99
104
 
100
- const finalConfig = await mergeWithAstroConfig(config, integrations, resolveFrom, mode, command);
105
+ const finalConfig = await mergeWithAstroConfig(
106
+ config,
107
+ integrations,
108
+ options.resolveFrom,
109
+ mode,
110
+ command
111
+ );
101
112
 
102
113
  // Exclude Astro integration packages from dependency optimization because
103
114
  // they import virtual modules that esbuild cannot resolve.
@@ -107,7 +118,17 @@ export const viteFinal: StorybookConfigVite['viteFinal'] = async (config, { conf
107
118
  if (!finalConfig.optimizeDeps.exclude) {
108
119
  finalConfig.optimizeDeps.exclude = [];
109
120
  }
110
- for (const pkg of ['@astrojs/vue', '@astrojs/react', '@astrojs/preact']) {
121
+ for (const pkg of [
122
+ '@astrojs/vue',
123
+ '@astrojs/vue/client.js',
124
+ '@astrojs/vue/server.js',
125
+ '@astrojs/react',
126
+ '@astrojs/react/client.js',
127
+ '@astrojs/react/server.js',
128
+ '@astrojs/preact',
129
+ '@astrojs/preact/client.js',
130
+ '@astrojs/preact/server.js'
131
+ ]) {
111
132
  if (!finalConfig.optimizeDeps.exclude.includes(pkg)) {
112
133
  finalConfig.optimizeDeps.exclude.push(pkg);
113
134
  }
@@ -121,6 +142,16 @@ export const viteFinal: StorybookConfigVite['viteFinal'] = async (config, { conf
121
142
  if (!finalConfig.optimizeDeps.exclude.includes('@storybook-astro/renderer')) {
122
143
  finalConfig.optimizeDeps.exclude.push('@storybook-astro/renderer');
123
144
  }
145
+ // fsevents is a macOS-only native chokidar dep with a .node binary that
146
+ // esbuild's prebundler can't load. storybook/internal/preview-api can pass
147
+ // through the transform pipeline twice when used by CSF Next portable
148
+ // stories, producing a duplicate __vite__injectQuery import in the
149
+ // generated chunk; excluding it from prebundling collapses the duplicate.
150
+ for (const pkg of ['fsevents', 'storybook/internal/preview-api']) {
151
+ if (!finalConfig.optimizeDeps.exclude.includes(pkg)) {
152
+ finalConfig.optimizeDeps.exclude.push(pkg);
153
+ }
154
+ }
124
155
  // Mark integration virtual modules as external so the dep bundler doesn't
125
156
  // try to resolve them (they are Vite virtual modules with no real package).
126
157
  // Set both esbuildOptions (Vite ≤7) and rolldownOptions (Vite 8+, Rolldown)
@@ -146,8 +177,7 @@ export const viteFinal: StorybookConfigVite['viteFinal'] = async (config, { conf
146
177
  }
147
178
  }
148
179
 
149
- // Vite 8+ (Rolldown-based optimizer) same semantics, different key
150
- // Use a loose cast because rolldownOptions is absent from Vite <8 types.
180
+ // Vite 8+ uses Rolldown for dependency optimization.
151
181
  const optimizeDepsMut = finalConfig.optimizeDeps as Record<string, unknown>;
152
182
  const rolldownOpts = (optimizeDepsMut.rolldownOptions ?? {}) as { external?: string[] };
153
183