@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.
- package/dist/{base-IRZo3zgK.d.ts → base-DT67T5pi.d.ts} +1 -0
- package/dist/{chunk-T7NWIO5S.js → chunk-2EABPTOY.js} +5 -5
- package/dist/{chunk-PJEDXZVN.js → chunk-7YBE4TTI.js} +2 -1
- package/dist/chunk-7YBE4TTI.js.map +1 -0
- package/dist/{chunk-POHTFYST.js → chunk-AYYMNFI6.js} +104 -6
- package/dist/chunk-AYYMNFI6.js.map +1 -0
- package/dist/{chunk-OUEDTRBO.js → chunk-B454DGX6.js} +259 -67
- package/dist/chunk-B454DGX6.js.map +1 -0
- package/dist/chunk-B5HHF6FC.js +116 -0
- package/dist/chunk-B5HHF6FC.js.map +1 -0
- package/dist/chunk-CU57AJUW.js +1402 -0
- package/dist/chunk-CU57AJUW.js.map +1 -0
- package/dist/{chunk-DNGQBPT7.js → chunk-PUTCAN6X.js} +5 -2
- package/dist/{chunk-DNGQBPT7.js.map → chunk-PUTCAN6X.js.map} +1 -1
- package/dist/{chunk-4SWPVM6R.js → chunk-WUTCMEF5.js} +2 -2
- package/dist/index.d.ts +17 -7
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/integrations/index.d.ts +2 -1
- package/dist/integrations/index.js +1 -1
- package/dist/middleware.js +18 -131
- package/dist/middleware.js.map +1 -1
- package/dist/{types-C-jan6Px.d.ts → preset-BvgHg2of.d.ts} +8 -11
- package/dist/preset.d.ts +2 -10
- package/dist/preset.js +5 -4
- package/dist/renderer/renderer-dev.js +62 -0
- package/dist/renderer/renderer-dev.js.map +1 -0
- package/dist/renderer/renderer-server.js +92 -0
- package/dist/renderer/renderer-server.js.map +1 -0
- package/dist/renderer/renderer-static.js +54 -0
- package/dist/renderer/renderer-static.js.map +1 -0
- package/dist/testing.js +12 -11
- package/dist/testing.js.map +1 -1
- package/dist/{viteStorybookAstroMiddlewarePlugin-2EFKTECT.js → viteStorybookAstroMiddlewarePlugin-UB6ZLJ4B.js} +4 -3
- package/dist/vitest/global-setup.js +6 -5
- package/dist/vitest/global-setup.js.map +1 -1
- package/dist/vitest/index.d.ts +1 -1
- package/dist/vitest/index.js +3 -3
- package/package.json +14 -43
- package/src/astroImageService.ts +57 -0
- package/src/astroRenderHandler.ts +205 -0
- package/src/importAstroConfig.ts +1 -1
- package/src/index.ts +2 -0
- package/src/integrations/alpine.ts +1 -0
- package/src/integrations/base.ts +6 -0
- package/src/lib/revive-dates.test.ts +106 -0
- package/src/lib/revive-dates.ts +51 -0
- package/src/middleware.ts +29 -200
- package/src/module-mocks.ts +153 -5
- package/src/preset.ts +38 -8
- package/src/productionRenderRuntime.ts +187 -0
- package/src/rules.test.ts +52 -4
- package/src/rules.ts +54 -7
- package/src/server/index.ts +101 -31
- package/src/storyRulesRuntime.ts +34 -0
- package/src/storySsrVite.ts +240 -0
- package/src/types.ts +0 -9
- package/src/virtual.d.ts +17 -3
- package/src/vite/{astroFilesVirtualModulePlugin.ts → astroFilesPlugin.ts} +4 -4
- package/src/vite/sanitizeConfigPlugin.ts +18 -0
- package/src/vite/{storybookAstroServerAuthConfigVirtualModulePlugin.test.ts → serverAuthPlugin.test.ts} +7 -10
- package/src/vite/{storybookAstroServerAuthConfigVirtualModulePlugin.ts → serverAuthPlugin.ts} +6 -9
- package/src/vite/serverRuntimePlugin.ts +109 -0
- package/src/vite/{storybookAstroRulesConfigVirtualModulePlugin.ts → storyRulesPlugin.ts} +6 -7
- package/src/vite/{createVirtualModulePlugin.test.ts → virtualModulePlugin.test.ts} +5 -5
- package/src/vite/{createVirtualModulePlugin.ts → virtualModulePlugin.ts} +2 -2
- package/src/viteAstroContainerRenderersPlugin.ts +72 -2
- package/src/vitePluginAstroBuildPrerender.ts +75 -646
- package/src/vitePluginAstroBuildServer.ts +217 -165
- package/src/vitePluginAstroBuildShared.test.ts +87 -0
- package/src/vitePluginAstroBuildShared.ts +465 -0
- package/src/vitePluginStoryModuleMocks.ts +29 -0
- package/src/viteStorybookAstroMiddlewarePlugin.ts +8 -0
- package/src/viteStorybookAstroRendererPlugin.ts +13 -6
- package/src/viteStorybookRendererFallbackPlugin.ts +2 -2
- package/dist/chunk-OUEDTRBO.js.map +0 -1
- package/dist/chunk-PBISP7PA.js +0 -1137
- package/dist/chunk-PBISP7PA.js.map +0 -1
- package/dist/chunk-PJEDXZVN.js.map +0 -1
- package/dist/chunk-POHTFYST.js.map +0 -1
- package/dist/node/index.d.ts +0 -10
- package/dist/node/index.js +0 -10
- package/dist/node/index.js.map +0 -1
- package/src/vite/storybookAstroSanitizationConfigVirtualModulePlugin.ts +0 -21
- /package/dist/{chunk-T7NWIO5S.js.map → chunk-2EABPTOY.js.map} +0 -0
- /package/dist/{chunk-4SWPVM6R.js.map → chunk-WUTCMEF5.js.map} +0 -0
- /package/dist/{viteStorybookAstroMiddlewarePlugin-2EFKTECT.js.map → viteStorybookAstroMiddlewarePlugin-UB6ZLJ4B.js.map} +0 -0
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { Dirent } from 'node:fs';
|
|
3
|
-
import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
2
|
import { resolve } from 'node:path';
|
|
5
|
-
import type {
|
|
6
|
-
import { createServer, mergeConfig, type Plugin, type Rollup } from 'vite';
|
|
7
|
-
import { importAstroConfig } from './importAstroConfig.ts';
|
|
3
|
+
import type { Plugin, Rollup } from 'vite';
|
|
8
4
|
import type { Integration } from './integrations/index.ts';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
import {
|
|
6
|
+
buildStaticModuleMap,
|
|
7
|
+
emitHydratedComponentEntriesFromAstroFile,
|
|
8
|
+
collectTrackedSpecifiers,
|
|
9
|
+
emitBuildEntrypoints,
|
|
10
|
+
loadVirtualBuildModule,
|
|
11
|
+
resolveVirtualBuildModuleId,
|
|
12
|
+
stripQuery
|
|
13
|
+
} from './vitePluginAstroBuildShared.ts';
|
|
14
|
+
import {
|
|
15
|
+
createProductionRenderRuntime,
|
|
16
|
+
renderProductionStoryToHtml,
|
|
17
|
+
type ProductionStoryEntry
|
|
18
|
+
} from './productionRenderRuntime.ts';
|
|
13
19
|
import { resolveRulesConfigFilePath } from './rules-options.ts';
|
|
14
|
-
import { selectStoryRules, withStoryRuleCleanups } from './rules.ts';
|
|
15
20
|
import type { FrameworkOptions } from './types.ts';
|
|
16
|
-
import { vitePluginAstroFontsFallback } from './vitePluginAstroFontsFallback.ts';
|
|
17
|
-
import { vitePluginAstroIntegrationOptsFallback } from './vitePluginAstroIntegrationOptsFallback.ts';
|
|
18
|
-
import { vitePluginAstroRoutesFallback } from './vitePluginAstroRoutesFallback.ts';
|
|
19
|
-
import { vitePluginAstroVueFallback } from './vitePluginAstroVueFallback.ts';
|
|
20
21
|
|
|
21
22
|
const PRERENDERED_STORIES_FILE = 'astro-prerendered-stories.json';
|
|
22
23
|
|
|
@@ -35,28 +36,6 @@ type StoryIndex = {
|
|
|
35
36
|
>;
|
|
36
37
|
};
|
|
37
38
|
|
|
38
|
-
type StoryEntry = {
|
|
39
|
-
id: string;
|
|
40
|
-
importPath: string;
|
|
41
|
-
exportName: string;
|
|
42
|
-
title?: string;
|
|
43
|
-
name?: string;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
type AstroCreateResult = {
|
|
47
|
-
createAstro?: (...args: unknown[]) => unknown;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
type AstroComponentFactory = ((
|
|
51
|
-
result: AstroCreateResult,
|
|
52
|
-
props: unknown,
|
|
53
|
-
slots: unknown
|
|
54
|
-
) => unknown) & {
|
|
55
|
-
isAstroComponentFactory?: boolean;
|
|
56
|
-
moduleId?: string;
|
|
57
|
-
propagation?: unknown;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
39
|
export function vitePluginAstroBuildPrerender(options: FrameworkOptions): Plugin {
|
|
61
40
|
const integrations = options.integrations ?? [];
|
|
62
41
|
const resolveFrom = options.resolveFrom ?? process.cwd();
|
|
@@ -75,100 +54,56 @@ export function vitePluginAstroBuildPrerender(options: FrameworkOptions): Plugin
|
|
|
75
54
|
outDir = resolve(resolveFrom, config.build.outDir ?? 'storybook-static');
|
|
76
55
|
},
|
|
77
56
|
|
|
78
|
-
resolveId(id: string) {
|
|
79
|
-
|
|
80
|
-
return `\0${id}`;
|
|
81
|
-
}
|
|
57
|
+
async resolveId(this: Rollup.PluginContext, id: string, importer?: string) {
|
|
58
|
+
const importerPath = stripQuery(importer);
|
|
82
59
|
|
|
83
|
-
if (
|
|
84
|
-
|
|
60
|
+
if (importerPath?.endsWith('.astro')) {
|
|
61
|
+
await emitHydratedComponentEntriesFromAstroFile({
|
|
62
|
+
pluginContext: this,
|
|
63
|
+
astroFilePath: importerPath,
|
|
64
|
+
resolveFrom,
|
|
65
|
+
componentEntrypointRefs
|
|
66
|
+
});
|
|
85
67
|
}
|
|
68
|
+
|
|
69
|
+
return resolveVirtualBuildModuleId(id);
|
|
86
70
|
},
|
|
87
71
|
|
|
88
72
|
load(id: string) {
|
|
89
|
-
|
|
90
|
-
const encodedSpecifier = id.replace('\0virtual:astro-static-module/', '');
|
|
91
|
-
const specifier = decodeURIComponent(encodedSpecifier);
|
|
92
|
-
|
|
93
|
-
if (isClientEntrypoint(specifier)) {
|
|
94
|
-
return [`export { default } from '${specifier}';`, `export * from '${specifier}';`].join('\n');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return [`import '${specifier}';`, 'export default undefined;'].join('\n');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (id.startsWith('\0virtual:astro-component-module/')) {
|
|
101
|
-
const withoutPrefix = id.replace('\0virtual:astro-component-module/', '');
|
|
102
|
-
// Strip the ?component-wrapper query appended by toComponentVirtualId
|
|
103
|
-
const encodedSpecifier = withoutPrefix.replace(/\?.*$/, '');
|
|
104
|
-
const specifier = decodeURIComponent(encodedSpecifier);
|
|
105
|
-
|
|
106
|
-
return [`export { default } from '${specifier}';`, `export * from '${specifier}';`].join('\n');
|
|
107
|
-
}
|
|
73
|
+
return loadVirtualBuildModule(id);
|
|
108
74
|
},
|
|
109
75
|
|
|
110
76
|
async buildStart(this: Rollup.PluginContext) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
trackedSpecifiers.forEach((specifier) => {
|
|
120
|
-
const fileReferenceId = this.emitFile({
|
|
121
|
-
type: 'chunk',
|
|
122
|
-
id: toStaticVirtualId(specifier)
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
staticEntrypointRefs.set(specifier, fileReferenceId);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const componentRootPaths = [
|
|
129
|
-
resolve(resolveFrom, 'src/components'),
|
|
130
|
-
...(options.renderMode === 'static' && options.componentRoots
|
|
131
|
-
? options.componentRoots.map((root) => resolve(resolveFrom, root))
|
|
132
|
-
: [])
|
|
133
|
-
];
|
|
134
|
-
const specifierArrays = await Promise.all(
|
|
135
|
-
componentRootPaths.map((root) => collectHydratableSourceModules(root))
|
|
136
|
-
);
|
|
137
|
-
const specifiers = specifierArrays.flat();
|
|
138
|
-
|
|
139
|
-
specifiers.forEach((specifier) => {
|
|
140
|
-
// .svelte and .vue files must be emitted as direct chunks so their
|
|
141
|
-
// native Vite compile plugins process them correctly. The virtual
|
|
142
|
-
// module wrapper exposes a JS re-export stub; vite-plugin-svelte and
|
|
143
|
-
// @vitejs/plugin-vue strip the query string before checking the
|
|
144
|
-
// extension, so they still try to compile the stub as framework source.
|
|
145
|
-
const chunkId = /\.(svelte|vue)$/.test(specifier)
|
|
146
|
-
? specifier
|
|
147
|
-
: toComponentVirtualId(specifier);
|
|
148
|
-
|
|
149
|
-
const fileReferenceId = this.emitFile({ type: 'chunk', id: chunkId });
|
|
150
|
-
|
|
151
|
-
componentEntrypointRefs.set(specifier, fileReferenceId);
|
|
77
|
+
await emitBuildEntrypoints({
|
|
78
|
+
pluginContext: this,
|
|
79
|
+
integrations,
|
|
80
|
+
resolveFrom,
|
|
81
|
+
trackedSpecifiers,
|
|
82
|
+
staticEntrypointRefs
|
|
152
83
|
});
|
|
153
84
|
},
|
|
154
85
|
|
|
155
|
-
async writeBundle(
|
|
86
|
+
async writeBundle(
|
|
87
|
+
this: Rollup.PluginContext,
|
|
88
|
+
_outputOptions: Rollup.NormalizedOutputOptions,
|
|
89
|
+
bundle: Rollup.OutputBundle
|
|
90
|
+
) {
|
|
156
91
|
const staticModuleMap = buildStaticModuleMap(
|
|
157
92
|
this,
|
|
158
93
|
staticEntrypointRefs,
|
|
159
94
|
componentEntrypointRefs
|
|
160
95
|
);
|
|
161
96
|
|
|
162
|
-
const
|
|
97
|
+
const astroStories = await collectAstroStories(outDir);
|
|
163
98
|
|
|
164
|
-
if (
|
|
99
|
+
if (astroStories.length === 0) {
|
|
165
100
|
await writePrerenderedStoriesFile(outDir, {});
|
|
166
101
|
|
|
167
102
|
return;
|
|
168
103
|
}
|
|
169
104
|
|
|
170
|
-
const prerenderedStories = await
|
|
171
|
-
|
|
105
|
+
const prerenderedStories = await prerenderAstroStories({
|
|
106
|
+
astroStories,
|
|
172
107
|
integrations,
|
|
173
108
|
sanitization: options.sanitization,
|
|
174
109
|
storyRulesConfigFilePath,
|
|
@@ -183,13 +118,15 @@ export function vitePluginAstroBuildPrerender(options: FrameworkOptions): Plugin
|
|
|
183
118
|
};
|
|
184
119
|
}
|
|
185
120
|
|
|
121
|
+
/** Writes the prerendered Astro story payload consumed by the static renderer. */
|
|
186
122
|
async function writePrerenderedStoriesFile(outDir: string, payload: Record<string, string>) {
|
|
187
123
|
await mkdir(outDir, { recursive: true });
|
|
188
124
|
await writeFile(resolve(outDir, PRERENDERED_STORIES_FILE), JSON.stringify(payload), 'utf-8');
|
|
189
125
|
}
|
|
190
126
|
|
|
191
|
-
|
|
192
|
-
|
|
127
|
+
/** Renders Astro stories during the static build and stores the HTML by story id. */
|
|
128
|
+
async function prerenderAstroStories(options: {
|
|
129
|
+
astroStories: ProductionStoryEntry[];
|
|
193
130
|
integrations: Integration[];
|
|
194
131
|
sanitization?: FrameworkOptions['sanitization'];
|
|
195
132
|
storyRulesConfigFilePath?: string;
|
|
@@ -198,114 +135,24 @@ async function prerenderStories(options: {
|
|
|
198
135
|
resolveFrom: string;
|
|
199
136
|
bundle: Rollup.OutputBundle;
|
|
200
137
|
}) {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
options.
|
|
204
|
-
options.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
options.
|
|
208
|
-
|
|
209
|
-
options.resolveFrom
|
|
210
|
-
);
|
|
211
|
-
const rulesConfigModule = await loadRulesConfigModule(viteServer, options.storyRulesConfigFilePath);
|
|
138
|
+
const runtime = await createProductionRenderRuntime({
|
|
139
|
+
integrations: options.integrations,
|
|
140
|
+
sanitization: options.sanitization,
|
|
141
|
+
storyRulesConfigFilePath: options.storyRulesConfigFilePath,
|
|
142
|
+
staticModuleMap: options.staticModuleMap,
|
|
143
|
+
trackedSpecifiers: options.trackedSpecifiers,
|
|
144
|
+
resolveFrom: options.resolveFrom
|
|
145
|
+
});
|
|
212
146
|
const assetPathMap = buildAssetPathMap(options.bundle);
|
|
213
147
|
|
|
214
|
-
// Inject a passthrough image service before the container renders any
|
|
215
|
-
// components. The `image: { service: passthroughImageService() }` config
|
|
216
|
-
// passed to Astro above is not sufficient on Astro 6: at render time
|
|
217
|
-
// `getConfiguredImageService()` still dynamically imports
|
|
218
|
-
// "virtual:image-service", which fails in Vite 7's module runner with
|
|
219
|
-
// `InvalidImageService`. Pre-populating globalThis.astroAsset.imageService
|
|
220
|
-
// short-circuits that dynamic import. See `lib/passthrough-image-service.ts`.
|
|
221
|
-
installPassthroughImageService();
|
|
222
|
-
|
|
223
148
|
try {
|
|
224
|
-
// Load AstroContainer through the SSR module graph so that internal
|
|
225
|
-
// classes (SlotString, HTMLString) share the same module instance as the
|
|
226
|
-
// Astro components loaded via ssrLoadModule below. Cross-module instanceof
|
|
227
|
-
// checks fail when AstroContainer is imported statically (Node.js context)
|
|
228
|
-
// and components are loaded via Vite SSR (separate module graph), which
|
|
229
|
-
// causes slot HTML to be escaped character-by-character instead of being
|
|
230
|
-
// passed through as raw HTML.
|
|
231
|
-
const containerModule = await viteServer.ssrLoadModule('astro/container');
|
|
232
|
-
const AstroContainerRuntime = containerModule.experimental_AstroContainer as typeof AstroContainer;
|
|
233
|
-
|
|
234
|
-
const container = await AstroContainerRuntime.create({
|
|
235
|
-
resolve: async (specifier) => {
|
|
236
|
-
const mockedModule = resolveStoryModuleMock(specifier);
|
|
237
|
-
|
|
238
|
-
if (mockedModule) {
|
|
239
|
-
return mockedModule;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const resolution = resolveClientModule(specifier);
|
|
243
|
-
|
|
244
|
-
if (resolution) {
|
|
245
|
-
return resolution;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return specifier;
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
await addContainerRenderers(container, options.integrations, resolveClientModule, viteServer);
|
|
253
|
-
|
|
254
149
|
const output: Record<string, string> = {};
|
|
255
150
|
|
|
256
|
-
for (const story of options.
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
id: story.id,
|
|
262
|
-
title: story.title,
|
|
263
|
-
name: story.name
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
if (selectedRules.moduleMocks.size > 0) {
|
|
268
|
-
viteServer.moduleGraph.invalidateAll();
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const html = await withStoryRuleCleanups(selectedRules.cleanups, async () => {
|
|
272
|
-
return withStoryModuleMocks(selectedRules.moduleMocks, async () => {
|
|
273
|
-
const modulePath = resolveImportPath(story.importPath, options.resolveFrom);
|
|
274
|
-
const storyModule = await viteServer.ssrLoadModule(modulePath);
|
|
275
|
-
const meta = isRecord(storyModule.default) ? storyModule.default : {};
|
|
276
|
-
const storyExport = isRecord(storyModule[story.exportName])
|
|
277
|
-
? storyModule[story.exportName]
|
|
278
|
-
: {};
|
|
279
|
-
|
|
280
|
-
if (typeof meta.component !== 'function') {
|
|
281
|
-
throw new Error(
|
|
282
|
-
`Unable to prerender story "${story.id}". Missing default export component in ${story.importPath}.`
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (storyExport.component && storyExport.component !== meta.component) {
|
|
287
|
-
return undefined;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const mergedArgs = mergeStoryArgs(toRecord(meta.args), toRecord(storyExport.args));
|
|
291
|
-
const { args, slots } = separateSlots(mergedArgs);
|
|
292
|
-
const processedArgs = await processImageMetadata(args);
|
|
293
|
-
const sanitizedPayload = sanitizeRenderPayload(
|
|
294
|
-
{
|
|
295
|
-
args: processedArgs,
|
|
296
|
-
slots
|
|
297
|
-
},
|
|
298
|
-
sanitizationOptions
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
return container.renderToString(
|
|
302
|
-
patchCreateAstroCompat(meta.component) as Parameters<typeof container.renderToString>[0],
|
|
303
|
-
{
|
|
304
|
-
props: sanitizedPayload.args,
|
|
305
|
-
slots: sanitizedPayload.slots
|
|
306
|
-
}
|
|
307
|
-
);
|
|
308
|
-
});
|
|
151
|
+
for (const story of options.astroStories) {
|
|
152
|
+
const html = await renderProductionStoryToHtml({
|
|
153
|
+
story,
|
|
154
|
+
runtime,
|
|
155
|
+
resolveFrom: options.resolveFrom
|
|
309
156
|
});
|
|
310
157
|
|
|
311
158
|
if (html !== undefined) {
|
|
@@ -315,176 +162,29 @@ async function prerenderStories(options: {
|
|
|
315
162
|
|
|
316
163
|
return output;
|
|
317
164
|
} finally {
|
|
318
|
-
await
|
|
165
|
+
await runtime.close();
|
|
319
166
|
}
|
|
320
167
|
}
|
|
321
168
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
trackedSpecifiers: Set<string>,
|
|
325
|
-
resolveFrom: string
|
|
326
|
-
) {
|
|
327
|
-
const { getViteConfig, passthroughImageService } = await importAstroConfig(resolveFrom);
|
|
328
|
-
const astroConfig = await getViteConfig(
|
|
329
|
-
{ root: resolveFrom },
|
|
330
|
-
{
|
|
331
|
-
configFile: false,
|
|
332
|
-
integrations: await Promise.all(
|
|
333
|
-
integrations.map((integration) => integration.loadIntegration(resolveFrom))
|
|
334
|
-
),
|
|
335
|
-
// Use the passthrough image service so nested components that use <Image>
|
|
336
|
-
// from astro:assets render as plain <img> tags without triggering image
|
|
337
|
-
// optimization (which fails in the Storybook SSR context).
|
|
338
|
-
image: { service: passthroughImageService() }
|
|
339
|
-
}
|
|
340
|
-
)({
|
|
341
|
-
mode: 'production',
|
|
342
|
-
command: 'serve'
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
const config = mergeConfig(astroConfig, {
|
|
346
|
-
appType: 'custom',
|
|
347
|
-
server: {
|
|
348
|
-
middlewareMode: true
|
|
349
|
-
},
|
|
350
|
-
ssr: {
|
|
351
|
-
// Force Astro runtime modules to be loaded through Vite's SSR transform
|
|
352
|
-
// pipeline rather than being externalized via Node.js native import().
|
|
353
|
-
// Without this, the AstroContainer (loaded via ssrLoadModule) and the
|
|
354
|
-
// component rendering pipeline may resolve internal classes like
|
|
355
|
-
// SlotString/HTMLString from separate module instances, causing
|
|
356
|
-
// instanceof checks to fail and slot HTML to be escaped.
|
|
357
|
-
noExternal: /^astro(\/.+)?$/
|
|
358
|
-
},
|
|
359
|
-
plugins: [
|
|
360
|
-
createProjectAstroResolutionPlugin(resolveFrom),
|
|
361
|
-
vitePluginAstroFontsFallback(),
|
|
362
|
-
vitePluginAstroIntegrationOptsFallback(),
|
|
363
|
-
vitePluginAstroVueFallback(),
|
|
364
|
-
vitePluginAstroRoutesFallback(),
|
|
365
|
-
{
|
|
366
|
-
name: 'storybook-astro:static-prerender-ssr-stubs',
|
|
367
|
-
resolveId(id: string) {
|
|
368
|
-
if (trackedSpecifiers.has(id)) {
|
|
369
|
-
return `\0storybook-astro-static-prerender-stub:${encodeURIComponent(id)}`;
|
|
370
|
-
}
|
|
371
|
-
},
|
|
372
|
-
load(id: string) {
|
|
373
|
-
if (id.startsWith('\0storybook-astro-static-prerender-stub:')) {
|
|
374
|
-
return 'export default undefined;';
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
]
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
return createServer(config);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
async function loadRulesConfigModule(
|
|
385
|
-
viteServer: Awaited<ReturnType<typeof createStorySsrServer>>,
|
|
386
|
-
configFilePath?: string
|
|
387
|
-
) {
|
|
388
|
-
if (!configFilePath) {
|
|
389
|
-
return undefined;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
try {
|
|
393
|
-
return await ssrLoadModuleWithFsFallback(viteServer, configFilePath, {
|
|
394
|
-
fixStacktrace: true
|
|
395
|
-
});
|
|
396
|
-
} catch (error) {
|
|
397
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
398
|
-
|
|
399
|
-
throw new Error(
|
|
400
|
-
`Unable to load framework.options.storyRules config module at ${configFilePath}: ${reason}`
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
async function addContainerRenderers(
|
|
406
|
-
container: Awaited<ReturnType<typeof AstroContainer.create>>,
|
|
407
|
-
integrations: Integration[],
|
|
408
|
-
resolveClientModule: (specifier: string) => string | undefined,
|
|
409
|
-
viteServer: Awaited<ReturnType<typeof createStorySsrServer>>
|
|
410
|
-
) {
|
|
411
|
-
for (const integration of integrations) {
|
|
412
|
-
const serverRenderer = integration.renderer.server;
|
|
413
|
-
|
|
414
|
-
if (serverRenderer) {
|
|
415
|
-
const serverRendererModule = await viteServer.ssrLoadModule(serverRenderer.entrypoint);
|
|
416
|
-
const renderer = serverRendererModule.default ?? serverRendererModule;
|
|
417
|
-
|
|
418
|
-
if (integration.name === 'solid' && isRecord(renderer)) {
|
|
419
|
-
container.addServerRenderer({
|
|
420
|
-
name: serverRenderer.name,
|
|
421
|
-
renderer: {
|
|
422
|
-
...renderer,
|
|
423
|
-
name: serverRenderer.name
|
|
424
|
-
} as Parameters<typeof container.addServerRenderer>[0]['renderer']
|
|
425
|
-
});
|
|
426
|
-
} else {
|
|
427
|
-
container.addServerRenderer({
|
|
428
|
-
name: serverRenderer.name,
|
|
429
|
-
renderer
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
const clientRenderer = integration.renderer.client;
|
|
435
|
-
|
|
436
|
-
if (clientRenderer) {
|
|
437
|
-
const resolvedEntrypoint =
|
|
438
|
-
resolveClientModule(clientRenderer.entrypoint) ?? clientRenderer.entrypoint;
|
|
439
|
-
|
|
440
|
-
container.addClientRenderer({
|
|
441
|
-
name: clientRenderer.name,
|
|
442
|
-
entrypoint: resolvedEntrypoint
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function createClientModuleResolver(
|
|
449
|
-
integrations: Integration[],
|
|
450
|
-
staticModuleMap: Record<string, string>
|
|
451
|
-
) {
|
|
452
|
-
return function resolveClientModule(specifier: string) {
|
|
453
|
-
if (Object.hasOwn(staticModuleMap, specifier)) {
|
|
454
|
-
return staticModuleMap[specifier];
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const normalizedSpecifier = specifier.replace(/\\/g, '/').replace(/\?.*$/, '');
|
|
458
|
-
|
|
459
|
-
if (Object.hasOwn(staticModuleMap, normalizedSpecifier)) {
|
|
460
|
-
return staticModuleMap[normalizedSpecifier];
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
for (const integration of integrations) {
|
|
464
|
-
const resolution = integration.resolveClient(specifier);
|
|
465
|
-
|
|
466
|
-
if (resolution) {
|
|
467
|
-
return resolution;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
async function collectAstroStories(outDir: string): Promise<StoryEntry[]> {
|
|
169
|
+
/** Reads the built Storybook index and keeps only Astro stories that can be prerendered. */
|
|
170
|
+
async function collectAstroStories(outDir: string): Promise<ProductionStoryEntry[]> {
|
|
474
171
|
const indexFile = resolve(outDir, 'index.json');
|
|
475
172
|
const indexRaw = await readFile(indexFile, 'utf-8');
|
|
476
173
|
const indexJson = JSON.parse(indexRaw) as StoryIndex;
|
|
477
174
|
|
|
175
|
+
// Static prerender only owns Astro stories. Framework-rendered stories stay
|
|
176
|
+
// with Storybook's normal preview pipeline and are not pre-rendered here.
|
|
478
177
|
return Object.values(indexJson.entries ?? {})
|
|
479
178
|
.filter((entry) => entry.type === 'story' && entry.componentPath?.endsWith('.astro'))
|
|
480
179
|
.map((entry) => {
|
|
481
|
-
if (!entry.id || !entry.importPath || !entry.exportName) {
|
|
180
|
+
if (!entry.id || !entry.importPath || !entry.exportName || !entry.componentPath) {
|
|
482
181
|
throw new Error(`Encountered an invalid Storybook index entry in ${indexFile}.`);
|
|
483
182
|
}
|
|
484
183
|
|
|
485
184
|
return {
|
|
486
185
|
id: entry.id,
|
|
487
186
|
importPath: entry.importPath,
|
|
187
|
+
componentPath: entry.componentPath,
|
|
488
188
|
exportName: entry.exportName,
|
|
489
189
|
title: entry.title,
|
|
490
190
|
name: entry.name
|
|
@@ -492,113 +192,7 @@ async function collectAstroStories(outDir: string): Promise<StoryEntry[]> {
|
|
|
492
192
|
});
|
|
493
193
|
}
|
|
494
194
|
|
|
495
|
-
|
|
496
|
-
metaArgs: Record<string, unknown> | undefined,
|
|
497
|
-
storyArgs: Record<string, unknown> | undefined
|
|
498
|
-
) {
|
|
499
|
-
return {
|
|
500
|
-
...(metaArgs ?? {}),
|
|
501
|
-
...(storyArgs ?? {})
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function separateSlots(inputArgs: Record<string, unknown>) {
|
|
506
|
-
const args = { ...inputArgs };
|
|
507
|
-
const slotsCandidate = args.slots;
|
|
508
|
-
|
|
509
|
-
delete args.slots;
|
|
510
|
-
|
|
511
|
-
if (!isRecord(slotsCandidate)) {
|
|
512
|
-
return {
|
|
513
|
-
args,
|
|
514
|
-
slots: {}
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
return {
|
|
519
|
-
args,
|
|
520
|
-
slots: slotsCandidate as Record<string, string>
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
function resolveImportPath(importPath: string, resolveFrom: string) {
|
|
525
|
-
if (importPath.startsWith('./')) {
|
|
526
|
-
return resolve(resolveFrom, importPath.slice(2));
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
return resolve(resolveFrom, importPath);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
533
|
-
return typeof value === 'object' && value !== null;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function toRecord(value: unknown): Record<string, unknown> | undefined {
|
|
537
|
-
if (!isRecord(value)) {
|
|
538
|
-
return undefined;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
return value;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
function collectTrackedSpecifiers(integrations: Integration[]) {
|
|
545
|
-
const specifiers = new Set<string>(['astro:scripts/page.js', 'astro:scripts/before-hydration.js']);
|
|
546
|
-
|
|
547
|
-
integrations.forEach((integration) => {
|
|
548
|
-
const entrypoint = integration.renderer.client?.entrypoint;
|
|
549
|
-
|
|
550
|
-
if (entrypoint) {
|
|
551
|
-
specifiers.add(entrypoint);
|
|
552
|
-
}
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
return specifiers;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
function buildStaticModuleMap(
|
|
559
|
-
pluginContext: Rollup.PluginContext,
|
|
560
|
-
staticEntrypointRefs: Map<string, string>,
|
|
561
|
-
componentEntrypointRefs: Map<string, string>
|
|
562
|
-
) {
|
|
563
|
-
const map: Record<string, string> = {};
|
|
564
|
-
|
|
565
|
-
staticEntrypointRefs.forEach((fileReferenceId, specifier) => {
|
|
566
|
-
const fileName = pluginContext.getFileName(fileReferenceId);
|
|
567
|
-
|
|
568
|
-
if (fileName) {
|
|
569
|
-
map[specifier] = toPublicPath(fileName);
|
|
570
|
-
}
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
componentEntrypointRefs.forEach((fileReferenceId, specifier) => {
|
|
574
|
-
const fileName = pluginContext.getFileName(fileReferenceId);
|
|
575
|
-
|
|
576
|
-
if (fileName) {
|
|
577
|
-
map[specifier] = toPublicPath(fileName);
|
|
578
|
-
}
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
return map;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
function toStaticVirtualId(specifier: string) {
|
|
585
|
-
return `virtual:astro-static-module/${encodeURIComponent(specifier)}`;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
function toComponentVirtualId(specifier: string) {
|
|
589
|
-
// Append a non-extension suffix so framework compile plugins (e.g. vite-plugin-svelte)
|
|
590
|
-
// don't match the virtual module ID by extension and try to compile the JS re-export stub.
|
|
591
|
-
return `virtual:astro-component-module/${encodeURIComponent(specifier)}?component-wrapper`;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
function isClientEntrypoint(specifier: string) {
|
|
595
|
-
return specifier.startsWith('@astrojs/') && specifier.endsWith('/client.js');
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
function toPublicPath(fileName: string) {
|
|
599
|
-
return `./${fileName}`;
|
|
600
|
-
}
|
|
601
|
-
|
|
195
|
+
/** Builds lookup tables that map original asset paths to emitted static asset URLs. */
|
|
602
196
|
function buildAssetPathMap(bundle: Rollup.OutputBundle): Map<string, string> {
|
|
603
197
|
const exactMap = new Map<string, string>();
|
|
604
198
|
const stemMap = new Map<string, string>();
|
|
@@ -632,7 +226,11 @@ function buildAssetPathMap(bundle: Rollup.OutputBundle): Map<string, string> {
|
|
|
632
226
|
return { exactMap, stemMap } as unknown as Map<string, string>;
|
|
633
227
|
}
|
|
634
228
|
|
|
635
|
-
|
|
229
|
+
/** Rewrites dev-only /@fs/ asset URLs in prerendered HTML to emitted build asset paths. */
|
|
230
|
+
function rewriteAssetPaths(
|
|
231
|
+
html: string,
|
|
232
|
+
assetPathMap: ReturnType<typeof buildAssetPathMap>
|
|
233
|
+
): string {
|
|
636
234
|
const { exactMap, stemMap } = assetPathMap as unknown as {
|
|
637
235
|
exactMap: Map<string, string>;
|
|
638
236
|
stemMap: Map<string, string>;
|
|
@@ -642,11 +240,8 @@ function rewriteAssetPaths(html: string, assetPathMap: ReturnType<typeof buildAs
|
|
|
642
240
|
return html;
|
|
643
241
|
}
|
|
644
242
|
|
|
645
|
-
//
|
|
646
|
-
//
|
|
647
|
-
// builds these are emitted as /_astro/name.hash.ext output assets.
|
|
648
|
-
// The character class deliberately excludes only quotes (the attribute
|
|
649
|
-
// delimiters) so that paths containing spaces are captured in full.
|
|
243
|
+
// Prerendering happens through a Vite SSR server, so image/style URLs can
|
|
244
|
+
// still point at dev-only /@fs/ paths. Rewrite them to the emitted assets.
|
|
650
245
|
return html.replace(/\/@fs\/[^"']+/g, (match) => {
|
|
651
246
|
const pathOnly = match.replace(/\?.*$/, '');
|
|
652
247
|
const fsPath = pathOnly.slice('/@fs'.length);
|
|
@@ -672,169 +267,3 @@ function rewriteAssetPaths(html: string, assetPathMap: ReturnType<typeof buildAs
|
|
|
672
267
|
return match;
|
|
673
268
|
});
|
|
674
269
|
}
|
|
675
|
-
|
|
676
|
-
async function collectHydratableSourceModules(srcRoot: string): Promise<string[]> {
|
|
677
|
-
const modules: string[] = [];
|
|
678
|
-
|
|
679
|
-
async function walk(directory: string) {
|
|
680
|
-
let entries: Dirent[];
|
|
681
|
-
|
|
682
|
-
try {
|
|
683
|
-
entries = await readdir(directory, { withFileTypes: true });
|
|
684
|
-
} catch {
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
await Promise.all(
|
|
689
|
-
entries.map(async (entry) => {
|
|
690
|
-
const absolutePath = resolve(directory, entry.name);
|
|
691
|
-
|
|
692
|
-
if (entry.isDirectory()) {
|
|
693
|
-
await walk(absolutePath);
|
|
694
|
-
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
if (!entry.isFile()) {
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
const normalizedPath = absolutePath.replace(/\\/g, '/');
|
|
703
|
-
|
|
704
|
-
if (!isHydratableSourceFile(normalizedPath)) {
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
if (isNonHydratableSourceFile(normalizedPath)) {
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
modules.push(normalizedPath);
|
|
713
|
-
})
|
|
714
|
-
);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
await walk(srcRoot);
|
|
718
|
-
|
|
719
|
-
return modules;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
function isHydratableSourceFile(input: string) {
|
|
723
|
-
// Only framework component extensions — plain .js/.ts are utilities/data
|
|
724
|
-
// files that are not hydratable client components and must not be emitted
|
|
725
|
-
// as entry chunks (they may lack a default export, causing a build error).
|
|
726
|
-
return /\.(jsx|tsx|vue|svelte)$/.test(input);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
function isNonHydratableSourceFile(input: string) {
|
|
730
|
-
return /\.stories\.[jt]sx?$|\.stories\.vue$|\.stories\.svelte$|\.(spec|test)\.[jt]sx?$/.test(
|
|
731
|
-
input
|
|
732
|
-
);
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
function patchCreateAstroCompat(component: unknown): AstroComponentFactory {
|
|
736
|
-
if (typeof component !== 'function') {
|
|
737
|
-
throw new Error('Expected Astro component factory to be a function.');
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
const originalComponent = component as AstroComponentFactory;
|
|
741
|
-
const wrapped = ((result: AstroCreateResult, props: unknown, slots: unknown) => {
|
|
742
|
-
if (result && typeof result.createAstro === 'function') {
|
|
743
|
-
const originalCreateAstro = result.createAstro;
|
|
744
|
-
const runtimeExpectsAstroGlobal = originalCreateAstro.length >= 3;
|
|
745
|
-
|
|
746
|
-
result.createAstro = (...args: unknown[]) => {
|
|
747
|
-
if (args.length === 3 && !runtimeExpectsAstroGlobal) {
|
|
748
|
-
return originalCreateAstro(args[1], args[2]);
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
return originalCreateAstro(...args);
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
return originalComponent(result, props, slots);
|
|
756
|
-
}) as AstroComponentFactory;
|
|
757
|
-
|
|
758
|
-
wrapped.isAstroComponentFactory = originalComponent.isAstroComponentFactory;
|
|
759
|
-
wrapped.moduleId = originalComponent.moduleId;
|
|
760
|
-
wrapped.propagation = originalComponent.propagation;
|
|
761
|
-
|
|
762
|
-
return wrapped;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
async function processImageMetadata(
|
|
766
|
-
args: Record<string, unknown>
|
|
767
|
-
): Promise<Record<string, unknown>> {
|
|
768
|
-
const processed: Record<string, unknown> = {};
|
|
769
|
-
|
|
770
|
-
for (const [key, value] of Object.entries(args)) {
|
|
771
|
-
if (isImageMetadata(value)) {
|
|
772
|
-
// Keep ImageMetadata as a plain object — Astro's image service checks
|
|
773
|
-
// isESMImportedImage (typeof src === 'object') and skips the /@fs/ string
|
|
774
|
-
// validation that throws LocalImageUsedWrongly. Converting to a URL string
|
|
775
|
-
// causes that error when the string starts with /@fs/.
|
|
776
|
-
processed[key] = value;
|
|
777
|
-
|
|
778
|
-
continue;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
if (Array.isArray(value)) {
|
|
782
|
-
processed[key] = await Promise.all(
|
|
783
|
-
value.map(async (item) => {
|
|
784
|
-
if (isImageMetadata(item)) {
|
|
785
|
-
return item;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
if (isRecord(item)) {
|
|
789
|
-
return processImageMetadata(item);
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
return item;
|
|
793
|
-
})
|
|
794
|
-
);
|
|
795
|
-
|
|
796
|
-
continue;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
if (isRecord(value)) {
|
|
800
|
-
processed[key] = await processImageMetadata(value);
|
|
801
|
-
|
|
802
|
-
continue;
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
processed[key] = value;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
return processed;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
function isImageMetadata(value: unknown): value is Record<string, unknown> {
|
|
812
|
-
return (
|
|
813
|
-
isRecord(value) &&
|
|
814
|
-
typeof value.src === 'string' &&
|
|
815
|
-
('width' in value || 'height' in value || 'format' in value)
|
|
816
|
-
);
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
function createProjectAstroResolutionPlugin(resolveFrom: string): Plugin {
|
|
821
|
-
const require = createRequire(import.meta.url);
|
|
822
|
-
|
|
823
|
-
return {
|
|
824
|
-
name: 'storybook-astro:resolve-project-astro-prerender',
|
|
825
|
-
enforce: 'pre',
|
|
826
|
-
resolveId(id: string) {
|
|
827
|
-
if (id !== 'astro' && !id.startsWith('astro/')) {
|
|
828
|
-
return null;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
try {
|
|
832
|
-
return require.resolve(id, {
|
|
833
|
-
paths: [resolveFrom]
|
|
834
|
-
});
|
|
835
|
-
} catch {
|
|
836
|
-
return null;
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
};
|
|
840
|
-
}
|