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

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 (85) 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-B5HHF6FC.js +116 -0
  8. package/dist/chunk-B5HHF6FC.js.map +1 -0
  9. package/dist/chunk-H3XZHW6Z.js +1402 -0
  10. package/dist/chunk-H3XZHW6Z.js.map +1 -0
  11. package/dist/{chunk-DNGQBPT7.js → chunk-PUTCAN6X.js} +5 -2
  12. package/dist/{chunk-DNGQBPT7.js.map → chunk-PUTCAN6X.js.map} +1 -1
  13. package/dist/{chunk-OUEDTRBO.js → chunk-TWAO2IQW.js} +229 -67
  14. package/dist/chunk-TWAO2IQW.js.map +1 -0
  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 +203 -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/middleware.ts +29 -200
  47. package/src/module-mocks.ts +153 -5
  48. package/src/preset.ts +38 -8
  49. package/src/productionRenderRuntime.ts +187 -0
  50. package/src/rules.test.ts +52 -4
  51. package/src/rules.ts +54 -7
  52. package/src/server/index.ts +101 -31
  53. package/src/storyRulesRuntime.ts +34 -0
  54. package/src/storySsrVite.ts +240 -0
  55. package/src/types.ts +0 -9
  56. package/src/virtual.d.ts +17 -3
  57. package/src/vite/{astroFilesVirtualModulePlugin.ts → astroFilesPlugin.ts} +4 -4
  58. package/src/vite/sanitizeConfigPlugin.ts +18 -0
  59. package/src/vite/{storybookAstroServerAuthConfigVirtualModulePlugin.test.ts → serverAuthPlugin.test.ts} +7 -10
  60. package/src/vite/{storybookAstroServerAuthConfigVirtualModulePlugin.ts → serverAuthPlugin.ts} +6 -9
  61. package/src/vite/serverRuntimePlugin.ts +109 -0
  62. package/src/vite/{storybookAstroRulesConfigVirtualModulePlugin.ts → storyRulesPlugin.ts} +6 -7
  63. package/src/vite/{createVirtualModulePlugin.test.ts → virtualModulePlugin.test.ts} +5 -5
  64. package/src/vite/{createVirtualModulePlugin.ts → virtualModulePlugin.ts} +2 -2
  65. package/src/viteAstroContainerRenderersPlugin.ts +72 -2
  66. package/src/vitePluginAstroBuildPrerender.ts +75 -646
  67. package/src/vitePluginAstroBuildServer.ts +217 -165
  68. package/src/vitePluginAstroBuildShared.test.ts +87 -0
  69. package/src/vitePluginAstroBuildShared.ts +465 -0
  70. package/src/vitePluginStoryModuleMocks.ts +29 -0
  71. package/src/viteStorybookAstroMiddlewarePlugin.ts +8 -0
  72. package/src/viteStorybookAstroRendererPlugin.ts +13 -6
  73. package/src/viteStorybookRendererFallbackPlugin.ts +2 -2
  74. package/dist/chunk-OUEDTRBO.js.map +0 -1
  75. package/dist/chunk-PBISP7PA.js +0 -1137
  76. package/dist/chunk-PBISP7PA.js.map +0 -1
  77. package/dist/chunk-PJEDXZVN.js.map +0 -1
  78. package/dist/chunk-POHTFYST.js.map +0 -1
  79. package/dist/node/index.d.ts +0 -10
  80. package/dist/node/index.js +0 -10
  81. package/dist/node/index.js.map +0 -1
  82. package/src/vite/storybookAstroSanitizationConfigVirtualModulePlugin.ts +0 -21
  83. /package/dist/{chunk-T7NWIO5S.js.map → chunk-2EABPTOY.js.map} +0 -0
  84. /package/dist/{chunk-4SWPVM6R.js.map → chunk-WUTCMEF5.js.map} +0 -0
  85. /package/dist/{viteStorybookAstroMiddlewarePlugin-2EFKTECT.js.map → viteStorybookAstroMiddlewarePlugin-UB6ZLJ4B.js.map} +0 -0
@@ -0,0 +1,34 @@
1
+ import { withStoryModuleMocks } from './module-mocks.ts';
2
+ import { selectStoryRules, withStoryRuleCleanups, type StoryRuleSelection } from './rules.ts';
3
+ import type { RenderStoryInput } from './types.ts';
4
+
5
+ export type ResolveRulesConfigModule = () => unknown | Promise<unknown>;
6
+
7
+ type RunWithStoryRulesOptions = {
8
+ story?: RenderStoryInput;
9
+ rulesConfigFilePath?: string;
10
+ resolveRulesConfigModule?: ResolveRulesConfigModule;
11
+ invalidateModuleGraph?: () => void;
12
+ };
13
+
14
+ export async function runWithStoryRules<T>(
15
+ options: RunWithStoryRulesOptions,
16
+ callback: (selection: StoryRuleSelection) => Promise<T>
17
+ ): Promise<T> {
18
+ const rulesConfigModule = options.resolveRulesConfigModule
19
+ ? await options.resolveRulesConfigModule()
20
+ : undefined;
21
+ const selectedRules = await selectStoryRules({
22
+ configModule: rulesConfigModule,
23
+ configFilePath: options.rulesConfigFilePath,
24
+ story: options.story
25
+ });
26
+
27
+ if (selectedRules.moduleMocks.size > 0) {
28
+ options.invalidateModuleGraph?.();
29
+ }
30
+
31
+ return withStoryRuleCleanups(selectedRules.cleanups, async () => {
32
+ return withStoryModuleMocks(selectedRules.moduleMocks, async () => callback(selectedRules));
33
+ });
34
+ }
@@ -0,0 +1,240 @@
1
+ import { createRequire } from 'node:module';
2
+ import { createServer, mergeConfig, type Plugin, type ViteDevServer } from 'vite';
3
+ import type { experimental_AstroContainer as AstroContainer } from 'astro/container';
4
+ import { ensureAstroPassthroughImageService } from './astroImageService.ts';
5
+ import { importAstroConfig } from './importAstroConfig.ts';
6
+ import type { Integration } from './integrations/index.ts';
7
+ import { ssrLoadModuleWithFsFallback } from './lib/ssr-load-module-with-fs-fallback.ts';
8
+ import { resolveStoryModuleMock } from './module-mocks.ts';
9
+ import { vitePluginAstroFontsFallback } from './vitePluginAstroFontsFallback.ts';
10
+ import { vitePluginAstroIntegrationOptsFallback } from './vitePluginAstroIntegrationOptsFallback.ts';
11
+ import { vitePluginAstroRoutesFallback } from './vitePluginAstroRoutesFallback.ts';
12
+ import { vitePluginAstroVueFallback } from './vitePluginAstroVueFallback.ts';
13
+ import { vitePluginStoryModuleMocks } from './vitePluginStoryModuleMocks.ts';
14
+
15
+ export async function createStorySsrViteServer(options: {
16
+ integrations: Integration[];
17
+ trackedSpecifiers: Set<string>;
18
+ resolveFrom: string;
19
+ }) {
20
+ const { getViteConfig, passthroughImageService } = await importAstroConfig(options.resolveFrom);
21
+ const astroConfig = await getViteConfig(
22
+ { root: options.resolveFrom },
23
+ {
24
+ configFile: false,
25
+ integrations: await Promise.all(
26
+ options.integrations.map((integration) => integration.loadIntegration(options.resolveFrom))
27
+ ),
28
+ image: { service: passthroughImageService() }
29
+ }
30
+ )({
31
+ mode: 'production',
32
+ command: 'serve'
33
+ });
34
+
35
+ const config = mergeConfig(astroConfig, {
36
+ appType: 'custom',
37
+ server: {
38
+ middlewareMode: true
39
+ },
40
+ ssr: {
41
+ // Keep Astro runtime classes in the Vite SSR graph so containers and
42
+ // components share SlotString/HTMLString instances during prerendering.
43
+ noExternal: /^astro(\/.+)?$/
44
+ },
45
+ plugins: [
46
+ createProjectAstroResolutionPlugin(options.resolveFrom),
47
+ vitePluginAstroFontsFallback(),
48
+ vitePluginAstroIntegrationOptsFallback(),
49
+ vitePluginAstroVueFallback(),
50
+ vitePluginAstroRoutesFallback(),
51
+ vitePluginStoryModuleMocks(),
52
+ createTrackedSpecifierStubPlugin(options.trackedSpecifiers)
53
+ ]
54
+ });
55
+
56
+ const viteServer = await createServer(config);
57
+
58
+ await viteServer.pluginContainer.buildStart({});
59
+
60
+ return viteServer;
61
+ }
62
+
63
+ export async function loadRulesConfigModule(viteServer: ViteDevServer, configFilePath?: string) {
64
+ if (!configFilePath) {
65
+ return undefined;
66
+ }
67
+
68
+ try {
69
+ return await ssrLoadModuleWithFsFallback(viteServer, configFilePath, {
70
+ fixStacktrace: true
71
+ });
72
+ } catch (error) {
73
+ const reason = error instanceof Error ? error.message : String(error);
74
+
75
+ throw new Error(
76
+ `Unable to load framework.options.storyRules config module at ${configFilePath}: ${reason}`
77
+ );
78
+ }
79
+ }
80
+
81
+ export function createClientModuleResolver(
82
+ integrations: Integration[],
83
+ staticModuleMap: Record<string, string>
84
+ ) {
85
+ return function resolveClientModule(specifier: string) {
86
+ if (Object.hasOwn(staticModuleMap, specifier)) {
87
+ return staticModuleMap[specifier];
88
+ }
89
+
90
+ const normalizedSpecifier = specifier.replace(/\\/g, '/').replace(/\?.*$/, '');
91
+
92
+ if (Object.hasOwn(staticModuleMap, normalizedSpecifier)) {
93
+ return staticModuleMap[normalizedSpecifier];
94
+ }
95
+
96
+ for (const integration of integrations) {
97
+ const resolution = integration.resolveClient(specifier);
98
+
99
+ if (resolution) {
100
+ return resolution;
101
+ }
102
+ }
103
+ };
104
+ }
105
+
106
+ export async function createProductionAstroContainer(options: {
107
+ integrations: Integration[];
108
+ resolveClientModule: (specifier: string) => string | undefined;
109
+ viteServer: ViteDevServer;
110
+ }) {
111
+ ensureAstroPassthroughImageService();
112
+
113
+ // Astro 6's container wraps each slot value in a SlotString, and the
114
+ // rendering pipeline detects raw-HTML slot chunks via `instanceof SlotString`.
115
+ // If the container is loaded by Node's ESM resolver while components are
116
+ // loaded through Vite's SSR graph, the two paths produce different
117
+ // SlotString classes and the instanceof check fails — slot content then
118
+ // takes the escaping code path. Loading the container via the same Vite
119
+ // SSR server keeps the class identity consistent.
120
+ const containerModule = (await options.viteServer.ssrLoadModule('astro/container')) as {
121
+ experimental_AstroContainer: typeof AstroContainer;
122
+ };
123
+ const ViteAstroContainer = containerModule.experimental_AstroContainer;
124
+
125
+ const container = await ViteAstroContainer.create({
126
+ resolve: async (specifier) => {
127
+ const mockedModule = resolveStoryModuleMock(specifier);
128
+
129
+ if (mockedModule) {
130
+ return mockedModule;
131
+ }
132
+
133
+ const resolution = options.resolveClientModule(specifier);
134
+
135
+ if (resolution) {
136
+ return resolution;
137
+ }
138
+
139
+ return specifier;
140
+ }
141
+ });
142
+
143
+ await addContainerRenderers(
144
+ container,
145
+ options.integrations,
146
+ options.resolveClientModule,
147
+ options.viteServer
148
+ );
149
+
150
+ return container;
151
+ }
152
+
153
+ export async function addContainerRenderers(
154
+ container: Awaited<ReturnType<typeof AstroContainer.create>>,
155
+ integrations: Integration[],
156
+ resolveClientModule: (specifier: string) => string | undefined,
157
+ viteServer: ViteDevServer
158
+ ) {
159
+ for (const integration of integrations) {
160
+ const serverRenderer = integration.renderer.server;
161
+
162
+ if (serverRenderer) {
163
+ const serverRendererModule = await viteServer.ssrLoadModule(serverRenderer.entrypoint);
164
+ const renderer = serverRendererModule.default ?? serverRendererModule;
165
+
166
+ if (integration.name === 'solid' && isRecord(renderer)) {
167
+ container.addServerRenderer({
168
+ name: serverRenderer.name,
169
+ renderer: {
170
+ ...renderer,
171
+ name: serverRenderer.name
172
+ } as never
173
+ });
174
+ } else {
175
+ container.addServerRenderer({
176
+ name: serverRenderer.name,
177
+ renderer
178
+ });
179
+ }
180
+ }
181
+
182
+ const clientRenderer = integration.renderer.client;
183
+
184
+ if (clientRenderer) {
185
+ const resolvedEntrypoint =
186
+ resolveClientModule(clientRenderer.entrypoint) ?? clientRenderer.entrypoint;
187
+
188
+ container.addClientRenderer({
189
+ name: clientRenderer.name,
190
+ entrypoint: resolvedEntrypoint
191
+ });
192
+ }
193
+ }
194
+ }
195
+
196
+ function createProjectAstroResolutionPlugin(resolveFrom: string): Plugin {
197
+ const require = createRequire(import.meta.url);
198
+
199
+ return {
200
+ name: 'storybook-astro:resolve-project-astro-shared',
201
+ enforce: 'pre',
202
+ resolveId(id: string) {
203
+ if (id !== 'astro' && !id.startsWith('astro/')) {
204
+ return null;
205
+ }
206
+
207
+ try {
208
+ return require.resolve(id, {
209
+ paths: [resolveFrom]
210
+ });
211
+ } catch {
212
+ return null;
213
+ }
214
+ }
215
+ } satisfies Plugin;
216
+ }
217
+
218
+ function createTrackedSpecifierStubPlugin(trackedSpecifiers: Set<string>): Plugin {
219
+ return {
220
+ name: 'storybook-astro:shared-ssr-stubs',
221
+ resolveId(id: string) {
222
+ if (trackedSpecifiers.has(id)) {
223
+ return `\0storybook-astro-shared-ssr-stub:${encodeURIComponent(id)}`;
224
+ }
225
+
226
+ return null;
227
+ },
228
+ load(id: string) {
229
+ if (id.startsWith('\0storybook-astro-shared-ssr-stub:')) {
230
+ return 'export default undefined;';
231
+ }
232
+
233
+ return null;
234
+ }
235
+ } satisfies Plugin;
236
+ }
237
+
238
+ function isRecord(value: unknown): value is Record<string, unknown> {
239
+ return typeof value === 'object' && value !== null;
240
+ }
package/src/types.ts CHANGED
@@ -37,15 +37,6 @@ type StaticFrameworkOptions = BaseFrameworkOptions & {
37
37
  renderMode: 'static';
38
38
  storyRules?: StoryRulesOptions;
39
39
  server?: never;
40
- /**
41
- * Additional source directories (relative to `resolveFrom`) to scan for
42
- * hydratable client components (JSX/TSX/Vue/Svelte). Use this when stories
43
- * reference components that live outside the default `src/components` scan
44
- * root — for example, workspace packages included in the `stories` globs.
45
- *
46
- * @example ['../../packages/components/src']
47
- */
48
- componentRoots?: string[];
49
40
  };
50
41
 
51
42
  export type FrameworkOptions = ServerFrameworkOptions | StaticFrameworkOptions;
package/src/virtual.d.ts CHANGED
@@ -22,7 +22,7 @@ declare module 'virtual:storybook-astro-renderer' {
22
22
  export function applyStyles(): void;
23
23
  }
24
24
 
25
- declare module 'virtual:storybook-astro-sanitization-config' {
25
+ declare module 'virtual:storybook-astro/sanitize-config' {
26
26
  import type { SanitizationOptions } from './lib/sanitization.ts';
27
27
 
28
28
  const sanitization: SanitizationOptions | undefined;
@@ -30,16 +30,30 @@ declare module 'virtual:storybook-astro-sanitization-config' {
30
30
  export default sanitization;
31
31
  }
32
32
 
33
- declare module 'virtual:storybook-astro-story-rules-config' {
33
+ declare module 'virtual:storybook-astro/story-rules' {
34
34
  const configModule: unknown;
35
35
 
36
36
  export default configModule;
37
37
  export const storybookAstroStoryRulesConfigFilePath: string | undefined;
38
38
  }
39
39
 
40
- declare module 'virtual:storybook-astro-server-auth-config' {
40
+ declare module 'virtual:storybook-astro/server-auth' {
41
41
  export const storybookAstroServerAuthToken: string | undefined;
42
42
  export const storybookAstroServerAuthHeader: string;
43
43
  }
44
44
 
45
+ declare module 'virtual:storybook-astro/server-runtime' {
46
+ import type { Integration } from './integrations/index.ts';
47
+
48
+ export const runtimeConfig: {
49
+ snapshotDirName: string;
50
+ storyRulesConfigRelativePath: string | undefined;
51
+ componentPathMap: Record<string, string>;
52
+ staticModuleMap: Record<string, string>;
53
+ staticCssMap: Record<string, string[]>;
54
+ trackedSpecifiers: string[];
55
+ };
56
+ export const integrations: Integration[];
57
+ }
58
+
45
59
  declare module 'virtual:storybook-renderer-fallback' {}
@@ -1,5 +1,5 @@
1
1
  import type { Plugin } from 'vite';
2
- import { createVirtualModulePlugin } from './createVirtualModulePlugin.ts';
2
+ import { createVirtualModule } from './virtualModulePlugin.ts';
3
3
 
4
4
  type ImportRecord = {
5
5
  id: string;
@@ -7,9 +7,9 @@ type ImportRecord = {
7
7
  importStatement: string;
8
8
  };
9
9
 
10
- export function astroFilesVirtualModulePlugin(astroComponents: string[]): Plugin {
11
- return createVirtualModulePlugin({
12
- pluginName: 'storybook-astro:virtual-astro-files',
10
+ export function astroFilesPlugin(astroComponents: string[]): Plugin {
11
+ return createVirtualModule({
12
+ pluginName: 'storybook-astro:astro-files',
13
13
  virtualModuleId: 'virtual:astro-files',
14
14
  load() {
15
15
  const imports = astroComponents.reduce<ImportRecord[]>((records, file, index) => {
@@ -0,0 +1,18 @@
1
+ import type { Plugin } from 'vite';
2
+ import type { SanitizationOptions } from '../lib/sanitization.ts';
3
+ import { serializeSanitizationOptions } from '../lib/sanitization.ts';
4
+ import { createVirtualModule } from './virtualModulePlugin.ts';
5
+
6
+ export const SANITIZE_CONFIG_MODULE_ID = 'virtual:storybook-astro/sanitize-config';
7
+
8
+ export function sanitizeConfigPlugin(options?: SanitizationOptions): Plugin {
9
+ return createVirtualModule({
10
+ pluginName: 'storybook-astro:sanitize-config',
11
+ virtualModuleId: SANITIZE_CONFIG_MODULE_ID,
12
+ load() {
13
+ const serializedConfig = serializeSanitizationOptions(options);
14
+
15
+ return `export default ${serializedConfig};`;
16
+ }
17
+ });
18
+ }
@@ -1,9 +1,6 @@
1
1
  import type { PluginOption } from 'vite';
2
2
  import { describe, expect, test } from 'vitest';
3
- import {
4
- STORYBOOK_ASTRO_SERVER_AUTH_CONFIG_VIRTUAL_MODULE_ID,
5
- storybookAstroServerAuthConfigVirtualModulePlugin
6
- } from './storybookAstroServerAuthConfigVirtualModulePlugin.ts';
3
+ import { SERVER_AUTH_MODULE_ID, serverAuthPlugin } from './serverAuthPlugin.ts';
7
4
 
8
5
  function getPlugin(pluginOption: PluginOption) {
9
6
  if (Array.isArray(pluginOption)) {
@@ -34,19 +31,19 @@ function getHookHandler<T extends (...args: unknown[]) => unknown>(hook: unknown
34
31
  throw new Error('Expected hook to be a function or an object with a handler function.');
35
32
  }
36
33
 
37
- describe('storybookAstroServerAuthConfigVirtualModulePlugin', () => {
34
+ describe('serverAuthPlugin', () => {
38
35
  test('normalizes auth token and header values', async () => {
39
36
  const plugin = getPlugin(
40
- storybookAstroServerAuthConfigVirtualModulePlugin({
37
+ serverAuthPlugin({
41
38
  authToken: ' test-token ',
42
39
  authHeader: ' X-Storybook-Token '
43
40
  })
44
41
  );
45
42
  const resolveId = getHookHandler<(id: string) => string | undefined>(plugin.resolveId);
46
43
  const load = getHookHandler<(id: string) => Promise<string | undefined>>(plugin.load);
47
- const resolvedId = resolveId(STORYBOOK_ASTRO_SERVER_AUTH_CONFIG_VIRTUAL_MODULE_ID);
44
+ const resolvedId = resolveId(SERVER_AUTH_MODULE_ID);
48
45
 
49
- expect(resolvedId).toBe(`\0${STORYBOOK_ASTRO_SERVER_AUTH_CONFIG_VIRTUAL_MODULE_ID}`);
46
+ expect(resolvedId).toBe(`\0${SERVER_AUTH_MODULE_ID}`);
50
47
  await expect(load(resolvedId!)).resolves.toBe(
51
48
  'export const storybookAstroServerAuthToken = "test-token";\n' +
52
49
  'export const storybookAstroServerAuthHeader = "x-storybook-token";'
@@ -55,14 +52,14 @@ describe('storybookAstroServerAuthConfigVirtualModulePlugin', () => {
55
52
 
56
53
  test('falls back to authorization header and undefined token', async () => {
57
54
  const plugin = getPlugin(
58
- storybookAstroServerAuthConfigVirtualModulePlugin({
55
+ serverAuthPlugin({
59
56
  authToken: ' '
60
57
  })
61
58
  );
62
59
  const load = getHookHandler<(id: string) => Promise<string | undefined>>(plugin.load);
63
60
 
64
61
  await expect(
65
- load(`\0${STORYBOOK_ASTRO_SERVER_AUTH_CONFIG_VIRTUAL_MODULE_ID}`)
62
+ load(`\0${SERVER_AUTH_MODULE_ID}`)
66
63
  ).resolves.toBe(
67
64
  'export const storybookAstroServerAuthToken = undefined;\n' +
68
65
  'export const storybookAstroServerAuthHeader = "authorization";'
@@ -1,19 +1,16 @@
1
1
  import type { Plugin } from 'vite';
2
2
  import type { ServerBuildOptions } from '../types.ts';
3
- import { createVirtualModulePlugin } from './createVirtualModulePlugin.ts';
3
+ import { createVirtualModule } from './virtualModulePlugin.ts';
4
4
 
5
- export const STORYBOOK_ASTRO_SERVER_AUTH_CONFIG_VIRTUAL_MODULE_ID =
6
- 'virtual:storybook-astro-server-auth-config';
5
+ export const SERVER_AUTH_MODULE_ID = 'virtual:storybook-astro/server-auth';
7
6
 
8
- export function storybookAstroServerAuthConfigVirtualModulePlugin(
9
- options?: ServerBuildOptions
10
- ): Plugin {
7
+ export function serverAuthPlugin(options?: ServerBuildOptions): Plugin {
11
8
  const authToken = normalizeOptionalString(options?.authToken);
12
9
  const authHeader = normalizeAuthHeader(options?.authHeader);
13
10
 
14
- return createVirtualModulePlugin({
15
- pluginName: 'storybook-astro:virtual-server-auth-config',
16
- virtualModuleId: STORYBOOK_ASTRO_SERVER_AUTH_CONFIG_VIRTUAL_MODULE_ID,
11
+ return createVirtualModule({
12
+ pluginName: 'storybook-astro:server-auth',
13
+ virtualModuleId: SERVER_AUTH_MODULE_ID,
17
14
  load() {
18
15
  return [
19
16
  `export const storybookAstroServerAuthToken = ${
@@ -0,0 +1,109 @@
1
+ import { relative } from 'node:path';
2
+ import type { Integration } from '../integrations/index.ts';
3
+ import { resolveRulesConfigFilePath } from '../rules-options.ts';
4
+ import type { FrameworkOptions } from '../types.ts';
5
+ import { createVirtualModule } from './virtualModulePlugin.ts';
6
+
7
+ export const SERVER_RUNTIME_MODULE_ID = 'virtual:storybook-astro/server-runtime';
8
+ const integrationsModuleId = '@storybook-astro/framework/integrations';
9
+
10
+ function getIntegrationFactoryName(integration: Integration): string {
11
+ return integration.factoryName ?? integration.name;
12
+ }
13
+
14
+ /** Produces the virtual module that hands the standalone render server its build-time config. */
15
+ export function serverRuntimePlugin(options: {
16
+ integrations?: FrameworkOptions['integrations'];
17
+ storyRules?: FrameworkOptions['storyRules'];
18
+ resolveFrom: string;
19
+ snapshotDirName: string;
20
+ componentPathMap: Record<string, string>;
21
+ staticModuleMap: Record<string, string>;
22
+ staticCssMap: Record<string, string[]>;
23
+ trackedSpecifiers: string[];
24
+ }) {
25
+ return createVirtualModule({
26
+ pluginName: 'storybook-astro:server-runtime',
27
+ virtualModuleId: SERVER_RUNTIME_MODULE_ID,
28
+ load() {
29
+ const storyRulesConfigFilePath = resolveRulesConfigFilePath(options.storyRules, options.resolveFrom);
30
+ const storyRulesConfigRelativePath = storyRulesConfigFilePath
31
+ ? relative(options.resolveFrom, storyRulesConfigFilePath).replace(/\\/g, '/')
32
+ : undefined;
33
+ const integrations = options.integrations ?? [];
34
+ // Keep the generated module small: one config object for plain data,
35
+ // one integrations export for real factory calls.
36
+ const runtimeConfig = {
37
+ snapshotDirName: options.snapshotDirName,
38
+ storyRulesConfigRelativePath,
39
+ componentPathMap: options.componentPathMap,
40
+ staticModuleMap: options.staticModuleMap,
41
+ staticCssMap: options.staticCssMap,
42
+ trackedSpecifiers: options.trackedSpecifiers
43
+ };
44
+
45
+ return [
46
+ createIntegrationImports(integrations),
47
+ `export const runtimeConfig = ${serializeValue(runtimeConfig)};`,
48
+ `export const integrations = [${createIntegrationFactoryCalls(integrations)}];`
49
+ ].join('\n');
50
+ }
51
+ });
52
+ }
53
+
54
+ /** Imports only the integration factories used by this runtime bundle. */
55
+ function createIntegrationImports(integrations: Integration[]) {
56
+ const factoryNames = Array.from(new Set(integrations.map(getIntegrationFactoryName)));
57
+
58
+ if (factoryNames.length === 0) {
59
+ return '';
60
+ }
61
+
62
+ return `import { ${factoryNames.join(', ')} } from ${JSON.stringify(integrationsModuleId)};`;
63
+ }
64
+
65
+ /** Recreates the configured integration list inside the generated runtime module. */
66
+ function createIntegrationFactoryCalls(integrations: Integration[]) {
67
+ return integrations
68
+ .map((integration) => `${getIntegrationFactoryName(integration)}(${serializeValue(integration.options)})`)
69
+ .join(', ');
70
+ }
71
+
72
+ /** Serializes plain runtime config values into executable module source. */
73
+ function serializeValue(value: unknown): string {
74
+ if (value === undefined) {
75
+ return 'undefined';
76
+ }
77
+
78
+ if (value === null) {
79
+ return 'null';
80
+ }
81
+
82
+ if (typeof value === 'string') {
83
+ return JSON.stringify(value);
84
+ }
85
+
86
+ if (typeof value === 'number' || typeof value === 'boolean') {
87
+ return JSON.stringify(value);
88
+ }
89
+
90
+ if (value instanceof RegExp) {
91
+ return value.toString();
92
+ }
93
+
94
+ if (Array.isArray(value)) {
95
+ return `[${value.map((entry) => serializeValue(entry)).join(', ')}]`;
96
+ }
97
+
98
+ if (isRecord(value)) {
99
+ return `{${Object.entries(value)
100
+ .map(([key, entryValue]) => `${JSON.stringify(key)}: ${serializeValue(entryValue)}`)
101
+ .join(', ')}}`;
102
+ }
103
+
104
+ throw new Error('Unable to serialize Storybook Astro server runtime configuration.');
105
+ }
106
+
107
+ function isRecord(value: unknown): value is Record<string, unknown> {
108
+ return typeof value === 'object' && value !== null;
109
+ }
@@ -1,18 +1,17 @@
1
1
  import type { Plugin } from 'vite';
2
2
  import type { StoryRulesOptions } from '../rules-options.ts';
3
3
  import { resolveRulesConfigFilePath } from '../rules-options.ts';
4
- import { createVirtualModulePlugin } from './createVirtualModulePlugin.ts';
4
+ import { createVirtualModule } from './virtualModulePlugin.ts';
5
5
 
6
- export const STORYBOOK_ASTRO_STORY_RULES_CONFIG_VIRTUAL_MODULE_ID =
7
- 'virtual:storybook-astro-story-rules-config';
6
+ export const STORY_RULES_MODULE_ID = 'virtual:storybook-astro/story-rules';
8
7
 
9
- export function storybookAstroStoryRulesConfigVirtualModulePlugin(
8
+ export function storyRulesPlugin(
10
9
  options?: StoryRulesOptions,
11
10
  resolveFrom = process.cwd()
12
11
  ): Plugin {
13
- return createVirtualModulePlugin({
14
- pluginName: 'storybook-astro:virtual-story-rules-config',
15
- virtualModuleId: STORYBOOK_ASTRO_STORY_RULES_CONFIG_VIRTUAL_MODULE_ID,
12
+ return createVirtualModule({
13
+ pluginName: 'storybook-astro:story-rules',
14
+ virtualModuleId: STORY_RULES_MODULE_ID,
16
15
  load() {
17
16
  const configFilePath = resolveRulesConfigFilePath(options, resolveFrom);
18
17
 
@@ -1,6 +1,6 @@
1
1
  import type { PluginOption } from 'vite';
2
2
  import { describe, expect, test, vi } from 'vitest';
3
- import { createVirtualModulePlugin } from './createVirtualModulePlugin.ts';
3
+ import { createVirtualModule } from './virtualModulePlugin.ts';
4
4
 
5
5
  function getPlugin(pluginOption: PluginOption) {
6
6
  if (Array.isArray(pluginOption)) {
@@ -31,9 +31,9 @@ function getHookHandler<T extends (...args: unknown[]) => unknown>(hook: unknown
31
31
  throw new Error('Expected hook to be a function or an object with a handler function.');
32
32
  }
33
33
 
34
- describe('createVirtualModulePlugin', () => {
34
+ describe('createVirtualModule', () => {
35
35
  test('resolves configured virtual module id with a null-byte prefix', () => {
36
- const pluginOption = createVirtualModulePlugin({
36
+ const pluginOption = createVirtualModule({
37
37
  pluginName: 'test:virtual-module',
38
38
  virtualModuleId: 'virtual:test-module',
39
39
  load: () => 'export default true;'
@@ -48,7 +48,7 @@ describe('createVirtualModulePlugin', () => {
48
48
 
49
49
  test('loads module content only for the resolved virtual module id', async () => {
50
50
  const load = vi.fn(() => 'export const message = "hello";');
51
- const pluginOption = createVirtualModulePlugin({
51
+ const pluginOption = createVirtualModule({
52
52
  pluginName: 'test:virtual-module',
53
53
  virtualModuleId: 'virtual:test-module',
54
54
  load
@@ -67,7 +67,7 @@ describe('createVirtualModulePlugin', () => {
67
67
  });
68
68
 
69
69
  test('supports asynchronous virtual module loaders', async () => {
70
- const pluginOption = createVirtualModulePlugin({
70
+ const pluginOption = createVirtualModule({
71
71
  pluginName: 'test:virtual-module',
72
72
  virtualModuleId: 'virtual:test-module',
73
73
  load: async () => 'export default "async";'
@@ -1,12 +1,12 @@
1
1
  import type { Plugin } from 'vite';
2
2
 
3
- type CreateVirtualModulePluginOptions = {
3
+ type CreateVirtualModuleOptions = {
4
4
  pluginName: string;
5
5
  virtualModuleId: string;
6
6
  load: (id: string) => string | Promise<string> | undefined;
7
7
  };
8
8
 
9
- export function createVirtualModulePlugin(options: CreateVirtualModulePluginOptions): Plugin {
9
+ export function createVirtualModule(options: CreateVirtualModuleOptions): Plugin {
10
10
  const resolvedVirtualModuleId = `\0${options.virtualModuleId}`;
11
11
 
12
12
  return {