@storybook-astro/framework 0.1.0-beta.8 → 1.0.0
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.
- package/README.md +38 -0
- package/dist/base-IRZo3zgK.d.ts +23 -0
- package/dist/chunk-4SWPVM6R.js +96 -0
- package/dist/chunk-4SWPVM6R.js.map +1 -0
- package/dist/chunk-5EF25G5S.js +69 -0
- package/dist/chunk-5EF25G5S.js.map +1 -0
- package/dist/chunk-7GHEQUPV.js +439 -0
- package/dist/chunk-7GHEQUPV.js.map +1 -0
- package/dist/chunk-C5OH4VBR.js +492 -0
- package/dist/chunk-C5OH4VBR.js.map +1 -0
- package/dist/chunk-DNGQBPT7.js +15 -0
- package/dist/chunk-DNGQBPT7.js.map +1 -0
- package/dist/chunk-E4LB75JN.js +89 -0
- package/dist/chunk-E4LB75JN.js.map +1 -0
- package/dist/chunk-PJEDXZVN.js +240 -0
- package/dist/chunk-PJEDXZVN.js.map +1 -0
- package/dist/chunk-UK43WNEA.js +657 -0
- package/dist/chunk-UK43WNEA.js.map +1 -0
- package/dist/dist-HJOEPVRQ.js +15574 -0
- package/dist/dist-HJOEPVRQ.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +13 -64
- package/dist/index.js.map +1 -1
- package/dist/integrations/index.d.ts +138 -0
- package/dist/integrations/index.js +8 -196
- package/dist/integrations/index.js.map +1 -1
- package/dist/middleware.d.ts +26 -0
- package/dist/middleware.js +179 -0
- package/dist/middleware.js.map +1 -0
- package/dist/portable-stories-BvdaQigq.d.ts +83 -0
- package/dist/preset.d.ts +14 -0
- package/dist/preset.js +5 -1
- package/dist/testing.d.ts +27 -0
- package/dist/testing.js +324 -15539
- package/dist/testing.js.map +1 -1
- package/dist/types-CHTsRtA7.d.ts +42 -0
- package/dist/viteStorybookAstroMiddlewarePlugin-NP2E52IC.js +11 -0
- package/dist/viteStorybookAstroMiddlewarePlugin-NP2E52IC.js.map +1 -0
- package/dist/vitest/index.d.ts +19 -0
- package/dist/vitest/index.js +229 -0
- package/dist/vitest/index.js.map +1 -0
- package/package.json +31 -17
- package/src/importAstroConfig.ts +11 -0
- package/src/index.ts +20 -6
- package/src/integrations/alpine.ts +5 -2
- package/src/integrations/base.ts +2 -2
- package/src/integrations/moduleResolver.ts +43 -0
- package/src/integrations/preact.ts +5 -2
- package/src/integrations/react.ts +5 -2
- package/src/integrations/solid.ts +5 -2
- package/src/integrations/svelte.ts +5 -2
- package/src/integrations/vue.ts +5 -2
- package/src/lib/sanitization.test.ts +232 -0
- package/src/lib/sanitization.ts +338 -0
- package/src/lib/ssr-load-module-with-fs-fallback.ts +29 -0
- package/src/middleware.test.ts +48 -0
- package/src/middleware.ts +204 -96
- package/src/module-mocks.ts +16 -0
- package/src/msw-helpers.ts +1 -0
- package/src/msw.ts +58 -0
- package/src/preset.ts +38 -3
- package/src/rules-options.test.ts +71 -0
- package/src/rules-options.ts +87 -0
- package/src/rules.test.ts +183 -0
- package/src/rules.ts +314 -0
- package/src/testing/astro-runtime.ts +219 -0
- package/src/testing/component-utils.ts +32 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/integration-config.ts +121 -0
- package/src/testing/project-root.ts +185 -0
- package/src/testing/renderer-daemon.ts +269 -0
- package/src/testing/story-composition.ts +33 -0
- package/src/testing/types.ts +14 -0
- package/src/testing/working-directory.ts +28 -0
- package/src/testing.ts +1 -254
- package/src/types.ts +16 -4
- package/src/virtual.d.ts +2 -1
- package/src/vite/createVirtualModulePlugin.test.ts +80 -0
- package/src/vite/createVirtualModulePlugin.ts +25 -0
- package/src/viteAstroContainerRenderersPlugin.ts +60 -26
- package/src/vitePluginAstro.ts +12 -5
- package/src/vitePluginAstroBuildPrerender.ts +665 -204
- package/src/vitePluginAstroRoutesFallback.ts +37 -0
- package/src/vitePluginAstroVueFallback.ts +47 -0
- package/src/viteStorybookAstroMiddlewarePlugin.ts +88 -12
- package/src/viteStorybookRendererFallbackPlugin.ts +13 -23
- package/src/vitest/config.ts +95 -0
- package/src/vitest/global-setup.ts +16 -0
- package/src/vitest/index.ts +2 -0
- package/src/vitest/vite-plugins.ts +187 -0
- package/dist/chunk-SAOPE6SA.js +0 -557
- package/dist/chunk-SAOPE6SA.js.map +0 -1
|
@@ -1,253 +1,714 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import type { Dirent } from 'node:fs';
|
|
3
|
+
import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
|
|
6
|
+
import { createServer, mergeConfig, type Plugin, type Rollup } from 'vite';
|
|
7
|
+
import { importAstroConfig } from './importAstroConfig.ts';
|
|
5
8
|
import type { Integration } from './integrations/index.ts';
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
9
|
+
import { ssrLoadModuleWithFsFallback } from './lib/ssr-load-module-with-fs-fallback.ts';
|
|
10
|
+
import { resolveSanitizationOptions, sanitizeRenderPayload } from './lib/sanitization.ts';
|
|
11
|
+
import { resolveStoryModuleMock, withStoryModuleMocks } from './module-mocks.ts';
|
|
12
|
+
import { applyMswHandlers } from './msw.ts';
|
|
13
|
+
import { resolveRulesConfigFilePath } from './rules-options.ts';
|
|
14
|
+
import { selectStoryRules } from './rules.ts';
|
|
15
|
+
import type { FrameworkOptions } from './types.ts';
|
|
16
|
+
import { vitePluginAstroFontsFallback } from './vitePluginAstroFontsFallback.ts';
|
|
17
|
+
import { vitePluginAstroRoutesFallback } from './vitePluginAstroRoutesFallback.ts';
|
|
18
|
+
import { vitePluginAstroVueFallback } from './vitePluginAstroVueFallback.ts';
|
|
19
|
+
|
|
20
|
+
const PRERENDERED_STORIES_FILE = 'astro-prerendered-stories.json';
|
|
21
|
+
|
|
22
|
+
type StoryIndex = {
|
|
23
|
+
entries?: Record<
|
|
24
|
+
string,
|
|
25
|
+
{
|
|
26
|
+
type?: string;
|
|
27
|
+
id?: string;
|
|
28
|
+
importPath?: string;
|
|
29
|
+
exportName?: string;
|
|
30
|
+
componentPath?: string;
|
|
31
|
+
title?: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
}
|
|
34
|
+
>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type StoryEntry = {
|
|
38
|
+
id: string;
|
|
39
|
+
importPath: string;
|
|
40
|
+
exportName: string;
|
|
41
|
+
title?: string;
|
|
42
|
+
name?: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type AstroCreateResult = {
|
|
46
|
+
createAstro?: (...args: unknown[]) => unknown;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type AstroComponentFactory = ((
|
|
50
|
+
result: AstroCreateResult,
|
|
51
|
+
props: unknown,
|
|
52
|
+
slots: unknown
|
|
53
|
+
) => unknown) & {
|
|
54
|
+
isAstroComponentFactory?: boolean;
|
|
55
|
+
moduleId?: string;
|
|
56
|
+
propagation?: unknown;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export function vitePluginAstroBuildPrerender(options: FrameworkOptions): Plugin {
|
|
60
|
+
const integrations = options.integrations ?? [];
|
|
61
|
+
const resolveFrom = options.resolveFrom ?? process.cwd();
|
|
62
|
+
const storyRulesConfigFilePath = resolveRulesConfigFilePath(options.storyRules, resolveFrom);
|
|
63
|
+
const trackedSpecifiers = collectTrackedSpecifiers(integrations);
|
|
64
|
+
const staticEntrypointRefs = new Map<string, string>();
|
|
65
|
+
const componentEntrypointRefs = new Map<string, string>();
|
|
66
|
+
let outDir = resolve(resolveFrom, 'storybook-static');
|
|
37
67
|
|
|
38
68
|
return {
|
|
39
|
-
name: 'storybook-astro
|
|
69
|
+
name: 'storybook-astro:build-prerender',
|
|
40
70
|
apply: 'build',
|
|
41
71
|
enforce: 'post',
|
|
42
72
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
73
|
+
configResolved(config) {
|
|
74
|
+
outDir = resolve(resolveFrom, config.build.outDir ?? 'storybook-static');
|
|
75
|
+
},
|
|
46
76
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
77
|
+
resolveId(id: string) {
|
|
78
|
+
if (id.startsWith('virtual:astro-static-module/')) {
|
|
79
|
+
return `\0${id}`;
|
|
80
|
+
}
|
|
51
81
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
console.warn(
|
|
55
|
-
'[storybook-astro] Failed to create pre-render server:',
|
|
56
|
-
err instanceof Error ? err.message : err
|
|
57
|
-
);
|
|
82
|
+
if (id.startsWith('virtual:astro-component-module/')) {
|
|
83
|
+
return `\0${id}`;
|
|
58
84
|
}
|
|
59
85
|
},
|
|
60
86
|
|
|
61
|
-
|
|
62
|
-
if (
|
|
87
|
+
load(id: string) {
|
|
88
|
+
if (id.startsWith('\0virtual:astro-static-module/')) {
|
|
89
|
+
const encodedSpecifier = id.replace('\0virtual:astro-static-module/', '');
|
|
90
|
+
const specifier = decodeURIComponent(encodedSpecifier);
|
|
63
91
|
|
|
64
|
-
|
|
65
|
-
|
|
92
|
+
if (isClientEntrypoint(specifier)) {
|
|
93
|
+
return [`export { default } from '${specifier}';`, `export * from '${specifier}';`].join('\n');
|
|
94
|
+
}
|
|
66
95
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const astroImport = findFirstAstroImport(ast);
|
|
96
|
+
return [`import '${specifier}';`, 'export default undefined;'].join('\n');
|
|
97
|
+
}
|
|
70
98
|
|
|
71
|
-
if (
|
|
99
|
+
if (id.startsWith('\0virtual:astro-component-module/')) {
|
|
100
|
+
const encodedSpecifier = id.replace('\0virtual:astro-component-module/', '');
|
|
101
|
+
const specifier = decodeURIComponent(encodedSpecifier);
|
|
72
102
|
|
|
73
|
-
|
|
74
|
-
|
|
103
|
+
return [`export { default } from '${specifier}';`, `export * from '${specifier}';`].join('\n');
|
|
104
|
+
}
|
|
105
|
+
},
|
|
75
106
|
|
|
76
|
-
|
|
77
|
-
|
|
107
|
+
async buildStart(this: Rollup.PluginContext) {
|
|
108
|
+
integrations.forEach((integration) => {
|
|
109
|
+
const entrypoint = integration.renderer.client?.entrypoint;
|
|
78
110
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
111
|
+
if (entrypoint) {
|
|
112
|
+
this.addWatchFile(entrypoint);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
82
115
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
err instanceof Error ? err.message : err
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
116
|
+
trackedSpecifiers.forEach((specifier) => {
|
|
117
|
+
const fileReferenceId = this.emitFile({
|
|
118
|
+
type: 'chunk',
|
|
119
|
+
id: toStaticVirtualId(specifier)
|
|
120
|
+
});
|
|
93
121
|
|
|
94
|
-
|
|
122
|
+
staticEntrypointRefs.set(specifier, fileReferenceId);
|
|
123
|
+
});
|
|
95
124
|
|
|
96
|
-
|
|
97
|
-
|
|
125
|
+
const srcRoot = resolve(resolveFrom, 'src/components');
|
|
126
|
+
const specifiers = await collectHydratableSourceModules(srcRoot);
|
|
127
|
+
|
|
128
|
+
specifiers.forEach((specifier) => {
|
|
129
|
+
const fileReferenceId = this.emitFile({
|
|
130
|
+
type: 'chunk',
|
|
131
|
+
id: toComponentVirtualId(specifier)
|
|
132
|
+
});
|
|
98
133
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
134
|
+
componentEntrypointRefs.set(specifier, fileReferenceId);
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
async writeBundle(this: Rollup.PluginContext) {
|
|
139
|
+
const staticModuleMap = buildStaticModuleMap(
|
|
140
|
+
this,
|
|
141
|
+
staticEntrypointRefs,
|
|
142
|
+
componentEntrypointRefs
|
|
106
143
|
);
|
|
107
144
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
145
|
+
const stories = await collectAstroStories(outDir);
|
|
146
|
+
|
|
147
|
+
if (stories.length === 0) {
|
|
148
|
+
await writePrerenderedStoriesFile(outDir, {});
|
|
149
|
+
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const prerenderedStories = await prerenderStories({
|
|
154
|
+
stories,
|
|
155
|
+
integrations,
|
|
156
|
+
sanitization: options.sanitization,
|
|
157
|
+
storyRulesConfigFilePath,
|
|
158
|
+
staticModuleMap,
|
|
159
|
+
trackedSpecifiers,
|
|
160
|
+
resolveFrom
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
await writePrerenderedStoriesFile(outDir, prerenderedStories);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function writePrerenderedStoriesFile(outDir: string, payload: Record<string, string>) {
|
|
169
|
+
await mkdir(outDir, { recursive: true });
|
|
170
|
+
await writeFile(resolve(outDir, PRERENDERED_STORIES_FILE), JSON.stringify(payload), 'utf-8');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function prerenderStories(options: {
|
|
174
|
+
stories: StoryEntry[];
|
|
175
|
+
integrations: Integration[];
|
|
176
|
+
sanitization?: FrameworkOptions['sanitization'];
|
|
177
|
+
storyRulesConfigFilePath?: string;
|
|
178
|
+
staticModuleMap: Record<string, string>;
|
|
179
|
+
trackedSpecifiers: Set<string>;
|
|
180
|
+
resolveFrom: string;
|
|
181
|
+
}) {
|
|
182
|
+
const sanitizationOptions = resolveSanitizationOptions(options.sanitization ?? undefined);
|
|
183
|
+
const resolveClientModule = createClientModuleResolver(
|
|
184
|
+
options.integrations,
|
|
185
|
+
options.staticModuleMap
|
|
186
|
+
);
|
|
187
|
+
const viteServer = await createStorySsrServer(
|
|
188
|
+
options.integrations,
|
|
189
|
+
options.trackedSpecifiers,
|
|
190
|
+
options.resolveFrom
|
|
191
|
+
);
|
|
192
|
+
const rulesConfigModule = await loadRulesConfigModule(viteServer, options.storyRulesConfigFilePath);
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const container = await AstroContainer.create({
|
|
196
|
+
resolve: async (specifier) => {
|
|
197
|
+
const mockedModule = resolveStoryModuleMock(specifier);
|
|
198
|
+
|
|
199
|
+
if (mockedModule) {
|
|
200
|
+
return mockedModule;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const resolution = resolveClientModule(specifier);
|
|
204
|
+
|
|
205
|
+
if (resolution) {
|
|
206
|
+
return resolution;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return specifier;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await addContainerRenderers(container, options.integrations, resolveClientModule, viteServer);
|
|
214
|
+
|
|
215
|
+
const output: Record<string, string> = {};
|
|
216
|
+
|
|
217
|
+
for (const story of options.stories) {
|
|
218
|
+
const selectedRules = await selectStoryRules({
|
|
219
|
+
configModule: rulesConfigModule,
|
|
220
|
+
configFilePath: options.storyRulesConfigFilePath,
|
|
221
|
+
mode: 'production',
|
|
222
|
+
story: {
|
|
223
|
+
id: story.id,
|
|
224
|
+
title: story.title,
|
|
225
|
+
name: story.name
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
await applyMswHandlers(selectedRules.mswHandlers);
|
|
230
|
+
|
|
231
|
+
if (selectedRules.moduleMocks.size > 0) {
|
|
232
|
+
viteServer.moduleGraph.invalidateAll();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const html = await withStoryModuleMocks(selectedRules.moduleMocks, async () => {
|
|
236
|
+
const modulePath = resolveImportPath(story.importPath, options.resolveFrom);
|
|
237
|
+
const storyModule = await viteServer.ssrLoadModule(modulePath);
|
|
238
|
+
const meta = isRecord(storyModule.default) ? storyModule.default : {};
|
|
239
|
+
const storyExport = isRecord(storyModule[story.exportName]) ? storyModule[story.exportName] : {};
|
|
240
|
+
|
|
241
|
+
if (typeof meta.component !== 'function') {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`Unable to prerender story "${story.id}". Missing default export component in ${story.importPath}.`
|
|
139
244
|
);
|
|
140
245
|
}
|
|
246
|
+
|
|
247
|
+
if (storyExport.component && storyExport.component !== meta.component) {
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const mergedArgs = mergeStoryArgs(toRecord(meta.args), toRecord(storyExport.args));
|
|
252
|
+
const { args, slots } = separateSlots(mergedArgs);
|
|
253
|
+
const processedArgs = await processImageMetadata(args);
|
|
254
|
+
const sanitizedPayload = sanitizeRenderPayload(
|
|
255
|
+
{
|
|
256
|
+
args: processedArgs,
|
|
257
|
+
slots
|
|
258
|
+
},
|
|
259
|
+
sanitizationOptions
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
return container.renderToString(patchCreateAstroCompat(meta.component), {
|
|
263
|
+
props: sanitizedPayload.args,
|
|
264
|
+
slots: sanitizedPayload.slots
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (html !== undefined) {
|
|
269
|
+
output[story.id] = html;
|
|
141
270
|
}
|
|
271
|
+
}
|
|
142
272
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const injections = Object.entries(prerendered).map(
|
|
149
|
-
([name, html]) =>
|
|
150
|
-
`if (typeof ${name} !== 'undefined' && ${name} && typeof ${name} === 'object') {\n` +
|
|
151
|
-
` ${name}.parameters = Object.assign({}, ${name}.parameters, ` +
|
|
152
|
-
`{ __astroPrerendered: ${JSON.stringify(html)} });\n` +
|
|
153
|
-
`}`
|
|
154
|
-
);
|
|
273
|
+
return output;
|
|
274
|
+
} finally {
|
|
275
|
+
await viteServer.close();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
155
278
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
279
|
+
async function createStorySsrServer(
|
|
280
|
+
integrations: Integration[],
|
|
281
|
+
trackedSpecifiers: Set<string>,
|
|
282
|
+
resolveFrom: string
|
|
283
|
+
) {
|
|
284
|
+
const { getViteConfig } = await importAstroConfig(resolveFrom);
|
|
285
|
+
const astroConfig = await getViteConfig(
|
|
286
|
+
{ root: resolveFrom },
|
|
287
|
+
{
|
|
288
|
+
configFile: false,
|
|
289
|
+
integrations: await Promise.all(
|
|
290
|
+
integrations.map((integration) => integration.loadIntegration(resolveFrom))
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
)({
|
|
294
|
+
mode: 'production',
|
|
295
|
+
command: 'serve'
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const config = mergeConfig(astroConfig, {
|
|
299
|
+
appType: 'custom',
|
|
300
|
+
server: {
|
|
301
|
+
middlewareMode: true
|
|
163
302
|
},
|
|
303
|
+
plugins: [
|
|
304
|
+
createProjectAstroResolutionPlugin(resolveFrom),
|
|
305
|
+
vitePluginAstroFontsFallback(),
|
|
306
|
+
vitePluginAstroVueFallback(),
|
|
307
|
+
vitePluginAstroRoutesFallback(),
|
|
308
|
+
{
|
|
309
|
+
name: 'storybook-astro:static-prerender-ssr-stubs',
|
|
310
|
+
resolveId(id: string) {
|
|
311
|
+
if (trackedSpecifiers.has(id)) {
|
|
312
|
+
return `\0storybook-astro-static-prerender-stub:${encodeURIComponent(id)}`;
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
load(id: string) {
|
|
316
|
+
if (id.startsWith('\0storybook-astro-static-prerender-stub:')) {
|
|
317
|
+
return 'export default undefined;';
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
return createServer(config);
|
|
325
|
+
}
|
|
164
326
|
|
|
165
|
-
|
|
166
|
-
|
|
327
|
+
async function loadRulesConfigModule(
|
|
328
|
+
viteServer: Awaited<ReturnType<typeof createStorySsrServer>>,
|
|
329
|
+
configFilePath?: string
|
|
330
|
+
) {
|
|
331
|
+
if (!configFilePath) {
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
167
334
|
|
|
168
|
-
|
|
169
|
-
|
|
335
|
+
try {
|
|
336
|
+
return await ssrLoadModuleWithFsFallback(viteServer, configFilePath, {
|
|
337
|
+
fixStacktrace: true
|
|
338
|
+
});
|
|
339
|
+
} catch (error) {
|
|
340
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
170
341
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
342
|
+
throw new Error(
|
|
343
|
+
`Unable to load framework.options.storyRules config module at ${configFilePath}: ${reason}`
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
174
347
|
|
|
175
|
-
|
|
176
|
-
|
|
348
|
+
async function addContainerRenderers(
|
|
349
|
+
container: Awaited<ReturnType<typeof AstroContainer.create>>,
|
|
350
|
+
integrations: Integration[],
|
|
351
|
+
resolveClientModule: (specifier: string) => string | undefined,
|
|
352
|
+
viteServer: Awaited<ReturnType<typeof createStorySsrServer>>
|
|
353
|
+
) {
|
|
354
|
+
for (const integration of integrations) {
|
|
355
|
+
const serverRenderer = integration.renderer.server;
|
|
356
|
+
|
|
357
|
+
if (serverRenderer) {
|
|
358
|
+
const serverRendererModule = await viteServer.ssrLoadModule(serverRenderer.entrypoint);
|
|
359
|
+
const renderer = serverRendererModule.default ?? serverRendererModule;
|
|
360
|
+
|
|
361
|
+
if (integration.name === 'solid' && isRecord(renderer)) {
|
|
362
|
+
container.addServerRenderer({
|
|
363
|
+
name: serverRenderer.name,
|
|
364
|
+
renderer: {
|
|
365
|
+
...renderer,
|
|
366
|
+
name: serverRenderer.name
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
} else {
|
|
370
|
+
container.addServerRenderer({
|
|
371
|
+
name: serverRenderer.name,
|
|
372
|
+
renderer
|
|
373
|
+
});
|
|
177
374
|
}
|
|
375
|
+
}
|
|
178
376
|
|
|
179
|
-
|
|
180
|
-
|
|
377
|
+
const clientRenderer = integration.renderer.client;
|
|
378
|
+
|
|
379
|
+
if (clientRenderer) {
|
|
380
|
+
const resolvedEntrypoint =
|
|
381
|
+
resolveClientModule(clientRenderer.entrypoint) ?? clientRenderer.entrypoint;
|
|
382
|
+
|
|
383
|
+
container.addClientRenderer({
|
|
384
|
+
name: clientRenderer.name,
|
|
385
|
+
entrypoint: resolvedEntrypoint
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function createClientModuleResolver(
|
|
392
|
+
integrations: Integration[],
|
|
393
|
+
staticModuleMap: Record<string, string>
|
|
394
|
+
) {
|
|
395
|
+
return function resolveClientModule(specifier: string) {
|
|
396
|
+
if (Object.hasOwn(staticModuleMap, specifier)) {
|
|
397
|
+
return staticModuleMap[specifier];
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const normalizedSpecifier = specifier.replace(/\\/g, '/').replace(/\?.*$/, '');
|
|
401
|
+
|
|
402
|
+
if (Object.hasOwn(staticModuleMap, normalizedSpecifier)) {
|
|
403
|
+
return staticModuleMap[normalizedSpecifier];
|
|
404
|
+
}
|
|
181
405
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
406
|
+
for (const integration of integrations) {
|
|
407
|
+
const resolution = integration.resolveClient(specifier);
|
|
408
|
+
|
|
409
|
+
if (resolution) {
|
|
410
|
+
return resolution;
|
|
187
411
|
}
|
|
188
412
|
}
|
|
189
413
|
};
|
|
190
414
|
}
|
|
191
415
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
197
|
-
ast: any
|
|
198
|
-
): { local: string; source: string } | null {
|
|
199
|
-
for (const node of ast.body) {
|
|
200
|
-
if (
|
|
201
|
-
node.type === 'ImportDeclaration' &&
|
|
202
|
-
typeof node.source.value === 'string' &&
|
|
203
|
-
node.source.value.endsWith('.astro')
|
|
204
|
-
) {
|
|
205
|
-
const defaultSpecifier = node.specifiers?.find(
|
|
206
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
207
|
-
(s: any) => s.type === 'ImportDefaultSpecifier'
|
|
208
|
-
);
|
|
416
|
+
async function collectAstroStories(outDir: string): Promise<StoryEntry[]> {
|
|
417
|
+
const indexFile = resolve(outDir, 'index.json');
|
|
418
|
+
const indexRaw = await readFile(indexFile, 'utf-8');
|
|
419
|
+
const indexJson = JSON.parse(indexRaw) as StoryIndex;
|
|
209
420
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
};
|
|
421
|
+
return Object.values(indexJson.entries ?? {})
|
|
422
|
+
.filter((entry) => entry.type === 'story' && entry.componentPath?.endsWith('.astro'))
|
|
423
|
+
.map((entry) => {
|
|
424
|
+
if (!entry.id || !entry.importPath || !entry.exportName) {
|
|
425
|
+
throw new Error(`Encountered an invalid Storybook index entry in ${indexFile}.`);
|
|
215
426
|
}
|
|
216
|
-
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
id: entry.id,
|
|
430
|
+
importPath: entry.importPath,
|
|
431
|
+
exportName: entry.exportName,
|
|
432
|
+
title: entry.title,
|
|
433
|
+
name: entry.name
|
|
434
|
+
};
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function mergeStoryArgs(
|
|
439
|
+
metaArgs: Record<string, unknown> | undefined,
|
|
440
|
+
storyArgs: Record<string, unknown> | undefined
|
|
441
|
+
) {
|
|
442
|
+
return {
|
|
443
|
+
...(metaArgs ?? {}),
|
|
444
|
+
...(storyArgs ?? {})
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function separateSlots(inputArgs: Record<string, unknown>) {
|
|
449
|
+
const args = { ...inputArgs };
|
|
450
|
+
const slotsCandidate = args.slots;
|
|
451
|
+
|
|
452
|
+
delete args.slots;
|
|
453
|
+
|
|
454
|
+
if (!isRecord(slotsCandidate)) {
|
|
455
|
+
return {
|
|
456
|
+
args,
|
|
457
|
+
slots: {}
|
|
458
|
+
};
|
|
217
459
|
}
|
|
218
|
-
|
|
219
|
-
return
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
args,
|
|
463
|
+
slots: slotsCandidate as Record<string, string>
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function resolveImportPath(importPath: string, resolveFrom: string) {
|
|
468
|
+
if (importPath.startsWith('./')) {
|
|
469
|
+
return resolve(resolveFrom, importPath.slice(2));
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return resolve(resolveFrom, importPath);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
476
|
+
return typeof value === 'object' && value !== null;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function toRecord(value: unknown): Record<string, unknown> | undefined {
|
|
480
|
+
if (!isRecord(value)) {
|
|
481
|
+
return undefined;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return value;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function collectTrackedSpecifiers(integrations: Integration[]) {
|
|
488
|
+
const specifiers = new Set<string>(['astro:scripts/page.js', 'astro:scripts/before-hydration.js']);
|
|
489
|
+
|
|
490
|
+
integrations.forEach((integration) => {
|
|
491
|
+
const entrypoint = integration.renderer.client?.entrypoint;
|
|
492
|
+
|
|
493
|
+
if (entrypoint) {
|
|
494
|
+
specifiers.add(entrypoint);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
return specifiers;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function buildStaticModuleMap(
|
|
502
|
+
pluginContext: Rollup.PluginContext,
|
|
503
|
+
staticEntrypointRefs: Map<string, string>,
|
|
504
|
+
componentEntrypointRefs: Map<string, string>
|
|
505
|
+
) {
|
|
506
|
+
const map: Record<string, string> = {};
|
|
507
|
+
|
|
508
|
+
staticEntrypointRefs.forEach((fileReferenceId, specifier) => {
|
|
509
|
+
const fileName = pluginContext.getFileName(fileReferenceId);
|
|
510
|
+
|
|
511
|
+
if (fileName) {
|
|
512
|
+
map[specifier] = toPublicPath(fileName);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
componentEntrypointRefs.forEach((fileReferenceId, specifier) => {
|
|
517
|
+
const fileName = pluginContext.getFileName(fileReferenceId);
|
|
518
|
+
|
|
519
|
+
if (fileName) {
|
|
520
|
+
map[specifier] = toPublicPath(fileName);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
return map;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function toStaticVirtualId(specifier: string) {
|
|
528
|
+
return `virtual:astro-static-module/${encodeURIComponent(specifier)}`;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function toComponentVirtualId(specifier: string) {
|
|
532
|
+
return `virtual:astro-component-module/${encodeURIComponent(specifier)}`;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function isClientEntrypoint(specifier: string) {
|
|
536
|
+
return specifier.startsWith('@astrojs/') && specifier.endsWith('/client.js');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function toPublicPath(fileName: string) {
|
|
540
|
+
return `./${fileName}`;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
async function collectHydratableSourceModules(srcRoot: string): Promise<string[]> {
|
|
544
|
+
const modules: string[] = [];
|
|
545
|
+
|
|
546
|
+
async function walk(directory: string) {
|
|
547
|
+
let entries: Dirent[];
|
|
239
548
|
|
|
240
549
|
try {
|
|
241
|
-
|
|
242
|
-
const name = basename(pathOnly);
|
|
243
|
-
const refId = ctx.emitFile({ type: 'asset', name, source });
|
|
244
|
-
const placeholder = `__ASTRO_PRERENDER_ASSET_${refId}__`;
|
|
245
|
-
|
|
246
|
-
refIds.set(placeholder, refId);
|
|
247
|
-
|
|
248
|
-
return placeholder;
|
|
550
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
249
551
|
} catch {
|
|
250
|
-
return
|
|
552
|
+
return;
|
|
251
553
|
}
|
|
252
|
-
|
|
554
|
+
|
|
555
|
+
await Promise.all(
|
|
556
|
+
entries.map(async (entry) => {
|
|
557
|
+
const absolutePath = resolve(directory, entry.name);
|
|
558
|
+
|
|
559
|
+
if (entry.isDirectory()) {
|
|
560
|
+
await walk(absolutePath);
|
|
561
|
+
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (!entry.isFile()) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const normalizedPath = absolutePath.replace(/\\/g, '/');
|
|
570
|
+
|
|
571
|
+
if (!isHydratableSourceFile(normalizedPath)) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (isNonHydratableSourceFile(normalizedPath)) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
modules.push(normalizedPath);
|
|
580
|
+
})
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
await walk(srcRoot);
|
|
585
|
+
|
|
586
|
+
return modules;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function isHydratableSourceFile(input: string) {
|
|
590
|
+
return /\.(jsx|tsx|vue|svelte|js|ts)$/.test(input);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function isNonHydratableSourceFile(input: string) {
|
|
594
|
+
return /\.stories\.[jt]sx?$|\.stories\.vue$|\.stories\.svelte$|\.(spec|test)\.[jt]sx?$/.test(
|
|
595
|
+
input
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function patchCreateAstroCompat(component: unknown): AstroComponentFactory {
|
|
600
|
+
if (typeof component !== 'function') {
|
|
601
|
+
throw new Error('Expected Astro component factory to be a function.');
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const originalComponent = component as AstroComponentFactory;
|
|
605
|
+
const wrapped = ((result: AstroCreateResult, props: unknown, slots: unknown) => {
|
|
606
|
+
if (result && typeof result.createAstro === 'function') {
|
|
607
|
+
const originalCreateAstro = result.createAstro;
|
|
608
|
+
const runtimeExpectsAstroGlobal = originalCreateAstro.length >= 3;
|
|
609
|
+
|
|
610
|
+
result.createAstro = (...args: unknown[]) => {
|
|
611
|
+
if (args.length === 3 && !runtimeExpectsAstroGlobal) {
|
|
612
|
+
return originalCreateAstro(args[1], args[2]);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
return originalCreateAstro(...args);
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return originalComponent(result, props, slots);
|
|
620
|
+
}) as AstroComponentFactory;
|
|
621
|
+
|
|
622
|
+
wrapped.isAstroComponentFactory = originalComponent.isAstroComponentFactory;
|
|
623
|
+
wrapped.moduleId = originalComponent.moduleId;
|
|
624
|
+
wrapped.propagation = originalComponent.propagation;
|
|
625
|
+
|
|
626
|
+
return wrapped;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
async function processImageMetadata(
|
|
630
|
+
args: Record<string, unknown>
|
|
631
|
+
): Promise<Record<string, unknown>> {
|
|
632
|
+
const processed: Record<string, unknown> = {};
|
|
633
|
+
|
|
634
|
+
for (const [key, value] of Object.entries(args)) {
|
|
635
|
+
if (isImageMetadata(value)) {
|
|
636
|
+
processed[key] = convertImageMetadataToUrl(value);
|
|
637
|
+
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (Array.isArray(value)) {
|
|
642
|
+
processed[key] = await Promise.all(
|
|
643
|
+
value.map(async (item) => {
|
|
644
|
+
if (isImageMetadata(item)) {
|
|
645
|
+
return convertImageMetadataToUrl(item);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (isRecord(item)) {
|
|
649
|
+
return processImageMetadata(item);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return item;
|
|
653
|
+
})
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (isRecord(value)) {
|
|
660
|
+
processed[key] = await processImageMetadata(value);
|
|
661
|
+
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
processed[key] = value;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return processed;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function isImageMetadata(value: unknown): value is Record<string, unknown> {
|
|
672
|
+
return (
|
|
673
|
+
isRecord(value) &&
|
|
674
|
+
typeof value.src === 'string' &&
|
|
675
|
+
('width' in value || 'height' in value || 'format' in value)
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function convertImageMetadataToUrl(imageMetadata: Record<string, unknown>): string {
|
|
680
|
+
const src = imageMetadata.src;
|
|
681
|
+
const fsPath = imageMetadata.fsPath;
|
|
682
|
+
|
|
683
|
+
if (typeof src === 'string') {
|
|
684
|
+
return src;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (typeof fsPath === 'string') {
|
|
688
|
+
return fsPath;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return String(imageMetadata);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function createProjectAstroResolutionPlugin(resolveFrom: string): Plugin {
|
|
695
|
+
const require = createRequire(import.meta.url);
|
|
696
|
+
|
|
697
|
+
return {
|
|
698
|
+
name: 'storybook-astro:resolve-project-astro-prerender',
|
|
699
|
+
enforce: 'pre',
|
|
700
|
+
resolveId(id: string) {
|
|
701
|
+
if (id !== 'astro' && !id.startsWith('astro/')) {
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
try {
|
|
706
|
+
return require.resolve(id, {
|
|
707
|
+
paths: [resolveFrom]
|
|
708
|
+
});
|
|
709
|
+
} catch {
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
};
|
|
253
714
|
}
|