@storybook-astro/framework 1.0.3 → 1.1.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.
- package/dist/{chunk-KSDXET2L.js → chunk-4HECE7IW.js} +477 -61
- package/dist/chunk-4HECE7IW.js.map +1 -0
- package/dist/{chunk-7GHEQUPV.js → chunk-POHTFYST.js} +46 -8
- package/dist/chunk-POHTFYST.js.map +1 -0
- package/dist/chunk-T7NWIO5S.js +220 -0
- package/dist/chunk-T7NWIO5S.js.map +1 -0
- package/dist/{chunk-C5OH4VBR.js → chunk-V76WSNSP.js} +124 -47
- package/dist/chunk-V76WSNSP.js.map +1 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/middleware.js +57 -39
- package/dist/middleware.js.map +1 -1
- package/dist/node/index.d.ts +10 -0
- package/dist/node/index.js +10 -0
- package/dist/node/index.js.map +1 -0
- package/dist/preset.d.ts +1 -1
- package/dist/preset.js +3 -3
- package/dist/testing.js +12 -64
- package/dist/testing.js.map +1 -1
- package/dist/{types-CHTsRtA7.d.ts → types-Cvor6Tyi.d.ts} +21 -5
- package/dist/{viteStorybookAstroMiddlewarePlugin-NP2E52IC.js → viteStorybookAstroMiddlewarePlugin-2EFKTECT.js} +2 -2
- package/dist/vitest/global-setup.js +42 -0
- package/dist/vitest/global-setup.js.map +1 -0
- package/dist/vitest/index.js +20 -3
- package/dist/vitest/index.js.map +1 -1
- package/package.json +11 -3
- package/src/index.ts +21 -1
- package/src/lib/sanitization.ts +104 -0
- package/src/middleware.ts +76 -44
- package/src/node/index.ts +7 -0
- package/src/preset.ts +86 -16
- package/src/renderer/renderer-dev.ts +82 -0
- package/src/renderer/renderer-server.test.ts +101 -0
- package/src/renderer/renderer-server.ts +135 -0
- package/src/renderer/renderer-static.ts +62 -0
- package/src/rules.test.ts +89 -18
- package/src/rules.ts +67 -18
- package/src/server/index.ts +111 -0
- package/src/testing/renderer-daemon.ts +10 -1
- package/src/types.ts +25 -5
- package/src/virtual.d.ts +37 -0
- package/src/vite/astroFilesVirtualModulePlugin.ts +36 -0
- package/src/vite/createVirtualModulePlugin.ts +3 -3
- package/src/vite/storybookAstroRulesConfigVirtualModulePlugin.ts +37 -0
- package/src/vite/storybookAstroSanitizationConfigVirtualModulePlugin.ts +21 -0
- package/src/vite/storybookAstroServerAuthConfigVirtualModulePlugin.test.ts +71 -0
- package/src/vite/storybookAstroServerAuthConfigVirtualModulePlugin.ts +42 -0
- package/src/vitePluginAstroBuildPrerender.ts +50 -51
- package/src/vitePluginAstroBuildServer.ts +289 -0
- package/src/vitePluginAstroIntegrationOptsFallback.ts +25 -0
- package/src/vitePluginAstroToolbarFallback.ts +38 -0
- package/src/viteStorybookAstroMiddlewarePlugin.ts +40 -8
- package/src/viteStorybookAstroRendererPlugin.ts +45 -0
- package/src/vitest/config.ts +45 -4
- package/src/vitest/global-setup.ts +45 -0
- package/dist/chunk-7GHEQUPV.js.map +0 -1
- package/dist/chunk-C5OH4VBR.js.map +0 -1
- package/dist/chunk-KSDXET2L.js.map +0 -1
- package/dist/middleware.d.ts +0 -26
- package/src/msw-helpers.ts +0 -1
- package/src/msw.ts +0 -58
- /package/dist/{viteStorybookAstroMiddlewarePlugin-NP2E52IC.js.map → viteStorybookAstroMiddlewarePlugin-2EFKTECT.js.map} +0 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import type { Dirent } from 'node:fs';
|
|
2
|
+
import { readdir } from 'node:fs/promises';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { build, type Rollup } from 'vite';
|
|
6
|
+
import type { FrameworkOptions } from './types.ts';
|
|
7
|
+
import { mergeWithAstroConfig } from './vitePluginAstro.ts';
|
|
8
|
+
import { viteAstroContainerRenderersPlugin } from './viteAstroContainerRenderersPlugin.ts';
|
|
9
|
+
import { astroFilesVirtualModulePlugin } from './vite/astroFilesVirtualModulePlugin.ts';
|
|
10
|
+
import { storybookAstroStoryRulesConfigVirtualModulePlugin } from './vite/storybookAstroRulesConfigVirtualModulePlugin.ts';
|
|
11
|
+
import { storybookAstroSanitizationConfigVirtualModulePlugin } from './vite/storybookAstroSanitizationConfigVirtualModulePlugin.ts';
|
|
12
|
+
import { storybookAstroServerAuthConfigVirtualModulePlugin } from './vite/storybookAstroServerAuthConfigVirtualModulePlugin.ts';
|
|
13
|
+
|
|
14
|
+
const moduleRoot = resolve(dirname(fileURLToPath(import.meta.url)), '.');
|
|
15
|
+
// packageRoot works regardless of whether this file is running from src/ or dist/
|
|
16
|
+
const packageRoot = resolve(moduleRoot, '..');
|
|
17
|
+
|
|
18
|
+
export function vitePluginAstroBuildServer(options: FrameworkOptions) {
|
|
19
|
+
const integrations = options.integrations ?? [];
|
|
20
|
+
const resolveFrom = options.resolveFrom ?? process.cwd();
|
|
21
|
+
const storiesMap = new Map<string, Set<string>>();
|
|
22
|
+
const trackedSpecifiers = collectTrackedSpecifiers(integrations);
|
|
23
|
+
const staticEntrypointRefs = new Map<string, string>();
|
|
24
|
+
const componentEntrypointRefs = new Map<string, string>();
|
|
25
|
+
let storybookStaticOutDir = resolve(resolveFrom, 'storybook-static');
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
name: 'storybook-astro:build-server',
|
|
29
|
+
apply: 'build',
|
|
30
|
+
enforce: 'post',
|
|
31
|
+
|
|
32
|
+
configResolved(config: { build: { outDir?: string } }) {
|
|
33
|
+
storybookStaticOutDir = resolve(resolveFrom, config.build.outDir ?? 'storybook-static');
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
resolveId(id: string, importer?: string) {
|
|
37
|
+
if (id.endsWith('.astro') && importer) {
|
|
38
|
+
const absoluteAstroPath = resolve(dirname(importer), id);
|
|
39
|
+
|
|
40
|
+
if (!storiesMap.has(absoluteAstroPath)) {
|
|
41
|
+
storiesMap.set(absoluteAstroPath, new Set());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
storiesMap.get(absoluteAstroPath)?.add(importer);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (id.startsWith('virtual:astro-static-module/')) {
|
|
48
|
+
return `\0${id}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (id.startsWith('virtual:astro-component-module/')) {
|
|
52
|
+
return `\0${id}`;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
load(id: string) {
|
|
57
|
+
if (id.startsWith('\0virtual:astro-static-module/')) {
|
|
58
|
+
const encodedSpecifier = id.replace('\0virtual:astro-static-module/', '');
|
|
59
|
+
const specifier = decodeURIComponent(encodedSpecifier);
|
|
60
|
+
|
|
61
|
+
if (isClientEntrypoint(specifier)) {
|
|
62
|
+
return [`export { default } from '${specifier}';`, `export * from '${specifier}';`].join('\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return [`import '${specifier}';`, 'export default undefined;'].join('\n');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (id.startsWith('\0virtual:astro-component-module/')) {
|
|
69
|
+
const encodedSpecifier = id.replace('\0virtual:astro-component-module/', '');
|
|
70
|
+
const specifier = decodeURIComponent(encodedSpecifier);
|
|
71
|
+
|
|
72
|
+
return [`export { default } from '${specifier}';`, `export * from '${specifier}';`].join('\n');
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
async buildStart(this: Rollup.PluginContext) {
|
|
77
|
+
integrations.forEach((integration) => {
|
|
78
|
+
const entrypoint = integration.renderer.client?.entrypoint;
|
|
79
|
+
|
|
80
|
+
if (entrypoint) {
|
|
81
|
+
this.addWatchFile(entrypoint);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
trackedSpecifiers.forEach((specifier) => {
|
|
86
|
+
const fileReferenceId = this.emitFile({
|
|
87
|
+
type: 'chunk',
|
|
88
|
+
id: toStaticVirtualId(specifier)
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
staticEntrypointRefs.set(specifier, fileReferenceId);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const srcRoot = resolve(resolveFrom, 'src/components');
|
|
95
|
+
const specifiers = await collectHydratableSourceModules(srcRoot);
|
|
96
|
+
|
|
97
|
+
specifiers.forEach((specifier) => {
|
|
98
|
+
const fileReferenceId = this.emitFile({
|
|
99
|
+
type: 'chunk',
|
|
100
|
+
id: toComponentVirtualId(specifier)
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
componentEntrypointRefs.set(specifier, fileReferenceId);
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
async writeBundle(this: Rollup.PluginContext) {
|
|
108
|
+
const astroComponents = Array.from(storiesMap.keys());
|
|
109
|
+
const staticModuleMap = buildStaticModuleMap(
|
|
110
|
+
this,
|
|
111
|
+
staticEntrypointRefs,
|
|
112
|
+
componentEntrypointRefs
|
|
113
|
+
);
|
|
114
|
+
const serverOutDir = resolve(dirname(storybookStaticOutDir), 'storybook-server');
|
|
115
|
+
|
|
116
|
+
await buildAstroServer({
|
|
117
|
+
astroComponents,
|
|
118
|
+
integrations,
|
|
119
|
+
sanitization: options.sanitization,
|
|
120
|
+
storyRules: options.storyRules,
|
|
121
|
+
server: options.server,
|
|
122
|
+
outDir: serverOutDir,
|
|
123
|
+
staticModuleMap,
|
|
124
|
+
resolveFrom
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function buildAstroServer(options: {
|
|
131
|
+
astroComponents: string[];
|
|
132
|
+
integrations: FrameworkOptions['integrations'];
|
|
133
|
+
sanitization?: FrameworkOptions['sanitization'];
|
|
134
|
+
storyRules?: FrameworkOptions['storyRules'];
|
|
135
|
+
server?: FrameworkOptions['server'];
|
|
136
|
+
outDir: string;
|
|
137
|
+
staticModuleMap: Record<string, string>;
|
|
138
|
+
resolveFrom: string;
|
|
139
|
+
}) {
|
|
140
|
+
const buildConfig = {
|
|
141
|
+
root: resolve(packageRoot, 'src/server'),
|
|
142
|
+
ssr: {
|
|
143
|
+
noExternal: /(@astrojs\/.+|react|react-dom)/
|
|
144
|
+
},
|
|
145
|
+
build: {
|
|
146
|
+
ssr: true,
|
|
147
|
+
outDir: options.outDir,
|
|
148
|
+
emptyOutDir: true,
|
|
149
|
+
sourcemap: true,
|
|
150
|
+
manifest: false,
|
|
151
|
+
rollupOptions: {
|
|
152
|
+
input: resolve(packageRoot, 'src/server/index.ts'),
|
|
153
|
+
treeshake: true
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
plugins: [
|
|
157
|
+
astroFilesVirtualModulePlugin(options.astroComponents),
|
|
158
|
+
storybookAstroSanitizationConfigVirtualModulePlugin(options.sanitization),
|
|
159
|
+
storybookAstroStoryRulesConfigVirtualModulePlugin(options.storyRules, options.resolveFrom),
|
|
160
|
+
storybookAstroServerAuthConfigVirtualModulePlugin(options.server),
|
|
161
|
+
viteAstroContainerRenderersPlugin(options.integrations, {
|
|
162
|
+
mode: 'production',
|
|
163
|
+
staticModuleMap: options.staticModuleMap
|
|
164
|
+
})
|
|
165
|
+
]
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const finalConfig = await mergeWithAstroConfig(
|
|
169
|
+
buildConfig,
|
|
170
|
+
options.integrations,
|
|
171
|
+
options.resolveFrom,
|
|
172
|
+
'production',
|
|
173
|
+
'build'
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
await build(finalConfig);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function collectTrackedSpecifiers(integrations: FrameworkOptions['integrations']) {
|
|
180
|
+
const specifiers = new Set<string>(['astro:scripts/page.js', 'astro:scripts/before-hydration.js']);
|
|
181
|
+
|
|
182
|
+
integrations.forEach((integration) => {
|
|
183
|
+
const entrypoint = integration.renderer.client?.entrypoint;
|
|
184
|
+
|
|
185
|
+
if (entrypoint) {
|
|
186
|
+
specifiers.add(entrypoint);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
return specifiers;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function buildStaticModuleMap(
|
|
194
|
+
pluginContext: Rollup.PluginContext,
|
|
195
|
+
staticEntrypointRefs: Map<string, string>,
|
|
196
|
+
componentEntrypointRefs: Map<string, string>
|
|
197
|
+
) {
|
|
198
|
+
const map: Record<string, string> = {};
|
|
199
|
+
|
|
200
|
+
staticEntrypointRefs.forEach((fileReferenceId, specifier) => {
|
|
201
|
+
const fileName = pluginContext.getFileName(fileReferenceId);
|
|
202
|
+
|
|
203
|
+
if (fileName) {
|
|
204
|
+
map[specifier] = toPublicPath(fileName);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
componentEntrypointRefs.forEach((fileReferenceId, specifier) => {
|
|
209
|
+
const fileName = pluginContext.getFileName(fileReferenceId);
|
|
210
|
+
|
|
211
|
+
if (fileName) {
|
|
212
|
+
map[specifier] = toPublicPath(fileName);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return map;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function toStaticVirtualId(specifier: string) {
|
|
220
|
+
return `virtual:astro-static-module/${encodeURIComponent(specifier)}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function toComponentVirtualId(specifier: string) {
|
|
224
|
+
return `virtual:astro-component-module/${encodeURIComponent(specifier)}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function isClientEntrypoint(specifier: string) {
|
|
228
|
+
return specifier.startsWith('@astrojs/') && specifier.endsWith('/client.js');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function toPublicPath(fileName: string) {
|
|
232
|
+
return `./${fileName}`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function collectHydratableSourceModules(srcRoot: string): Promise<string[]> {
|
|
236
|
+
const modules: string[] = [];
|
|
237
|
+
|
|
238
|
+
async function walk(directory: string) {
|
|
239
|
+
let entries: Dirent[];
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
243
|
+
} catch {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
await Promise.all(
|
|
248
|
+
entries.map(async (entry) => {
|
|
249
|
+
const absolutePath = resolve(directory, entry.name);
|
|
250
|
+
|
|
251
|
+
if (entry.isDirectory()) {
|
|
252
|
+
await walk(absolutePath);
|
|
253
|
+
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!entry.isFile()) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const normalizedPath = absolutePath.replace(/\\/g, '/');
|
|
262
|
+
|
|
263
|
+
if (!isHydratableSourceFile(normalizedPath)) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (isNonHydratableSourceFile(normalizedPath)) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
modules.push(normalizedPath);
|
|
272
|
+
})
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
await walk(srcRoot);
|
|
277
|
+
|
|
278
|
+
return modules;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function isHydratableSourceFile(input: string) {
|
|
282
|
+
return /\.(jsx|tsx|vue|svelte|js|ts)$/.test(input);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function isNonHydratableSourceFile(input: string) {
|
|
286
|
+
return /\.stories\.[jt]sx?$|\.stories\.vue$|\.stories\.svelte$|\.(spec|test)\.[jt]sx?$/.test(
|
|
287
|
+
input
|
|
288
|
+
);
|
|
289
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
const OPTS_STUB = 'export default {}';
|
|
4
|
+
|
|
5
|
+
const VIRTUAL_IDS = ['astro:react:opts', 'astro:preact:opts'];
|
|
6
|
+
|
|
7
|
+
export function vitePluginAstroIntegrationOptsFallback(): Plugin {
|
|
8
|
+
const resolvedIds = new Map(VIRTUAL_IDS.map((id) => [id, `\0${id}`]));
|
|
9
|
+
const resolvedIdSet = new Set(resolvedIds.values());
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
name: 'storybook-astro-integration-opts-fallback',
|
|
13
|
+
enforce: 'pre',
|
|
14
|
+
|
|
15
|
+
resolveId(id) {
|
|
16
|
+
return resolvedIds.get(id);
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
load(id) {
|
|
20
|
+
if (resolvedIdSet.has(id)) {
|
|
21
|
+
return { code: OPTS_STUB };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
const TOOLBAR_INTERNAL_STUB = `
|
|
4
|
+
export const loadDevToolbarApps = async () => [];
|
|
5
|
+
`;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Provides a fallback stub for Astro's dev toolbar virtual module.
|
|
9
|
+
*
|
|
10
|
+
* Astro's `astro/dist/runtime/client/dev-toolbar/entrypoint.js` imports
|
|
11
|
+
* from `astro:toolbar:internal`, a virtual module normally provided by
|
|
12
|
+
* Astro's own `vite-plugin-dev-toolbar` Vite plugin. In the Storybook
|
|
13
|
+
* context that plugin is not active, causing esbuild to fail during
|
|
14
|
+
* dependency optimisation when it encounters the unresolvable import.
|
|
15
|
+
*
|
|
16
|
+
* Storybook doesn't use Astro's dev toolbar, so a no-op stub is safe.
|
|
17
|
+
*/
|
|
18
|
+
export function vitePluginAstroToolbarFallback(): Plugin {
|
|
19
|
+
const VIRTUAL_ID = 'astro:toolbar:internal';
|
|
20
|
+
const RESOLVED_ID = '\0' + VIRTUAL_ID;
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
name: 'storybook-astro-toolbar-fallback',
|
|
24
|
+
enforce: 'pre',
|
|
25
|
+
|
|
26
|
+
resolveId(id) {
|
|
27
|
+
if (id === VIRTUAL_ID) {
|
|
28
|
+
return RESOLVED_ID;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
load(id) {
|
|
33
|
+
if (id === RESOLVED_ID) {
|
|
34
|
+
return { code: TOOLBAR_INTERNAL_STUB };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import
|
|
3
|
+
import type { ServerResponse } from 'node:http';
|
|
4
|
+
import { createServer, createLogger, type Connect, type PluginOption, type ViteDevServer } from 'vite';
|
|
4
5
|
import type { RenderRequestMessage, RenderResponseMessage } from '@storybook-astro/renderer/types';
|
|
5
6
|
import type { FrameworkOptions } from './types.ts';
|
|
6
7
|
import type { Integration } from './integrations/index.ts';
|
|
7
8
|
import { importAstroConfig } from './importAstroConfig.ts';
|
|
8
9
|
import { viteAstroContainerRenderersPlugin } from './viteAstroContainerRenderersPlugin.ts';
|
|
9
10
|
import { vitePluginAstroFontsFallback } from './vitePluginAstroFontsFallback.ts';
|
|
11
|
+
import { vitePluginAstroIntegrationOptsFallback } from './vitePluginAstroIntegrationOptsFallback.ts';
|
|
10
12
|
import { vitePluginAstroVueFallback } from './vitePluginAstroVueFallback.ts';
|
|
11
13
|
import { vitePluginAstroRoutesFallback } from './vitePluginAstroRoutesFallback.ts';
|
|
12
14
|
import { ssrLoadModuleWithFsFallback } from './lib/ssr-load-module-with-fs-fallback.ts';
|
|
@@ -22,7 +24,7 @@ export async function vitePluginStorybookAstroMiddleware(options: FrameworkOptio
|
|
|
22
24
|
const vitePlugin = {
|
|
23
25
|
name: 'storybook-astro-middleware-plugin',
|
|
24
26
|
async configureServer(server) {
|
|
25
|
-
viteServer = await createViteServer(options.integrations, resolveFrom);
|
|
27
|
+
viteServer = await createViteServer(options.integrations ?? [], resolveFrom);
|
|
26
28
|
const storyRulesConfigFilePath = resolveRulesConfigFilePath(options.storyRules, resolveFrom);
|
|
27
29
|
|
|
28
30
|
const filePath = fileURLToPath(new URL('./middleware', import.meta.url));
|
|
@@ -31,7 +33,6 @@ export async function vitePluginStorybookAstroMiddleware(options: FrameworkOptio
|
|
|
31
33
|
});
|
|
32
34
|
|
|
33
35
|
const createHandler = () => middleware.handlerFactory(options.integrations ?? [], {
|
|
34
|
-
mode: 'development',
|
|
35
36
|
sanitization: options.sanitization,
|
|
36
37
|
rulesConfigFilePath: storyRulesConfigFilePath,
|
|
37
38
|
resolveRulesConfigModule: () =>
|
|
@@ -84,15 +85,15 @@ export async function vitePluginStorybookAstroMiddleware(options: FrameworkOptio
|
|
|
84
85
|
// Create asset serving plugin (only active in dev when viteServer exists)
|
|
85
86
|
const assetServingPlugin = {
|
|
86
87
|
name: 'storybook-astro-assets',
|
|
87
|
-
configureServer(server) {
|
|
88
|
-
server.middlewares.use('/_image', (req, res, next) => {
|
|
88
|
+
configureServer(server: ViteDevServer) {
|
|
89
|
+
server.middlewares.use('/_image', (req: Connect.IncomingMessage, res: ServerResponse, next: Connect.NextFunction) => {
|
|
89
90
|
if (!viteServer) {
|
|
90
91
|
next();
|
|
91
92
|
|
|
92
93
|
return;
|
|
93
94
|
}
|
|
94
95
|
// Forward the request to the Astro vite server
|
|
95
|
-
viteServer.middlewares.handle(req, res, (err) => {
|
|
96
|
+
viteServer.middlewares.handle(req, res, (err?: unknown) => {
|
|
96
97
|
if (err) {
|
|
97
98
|
console.error('Asset serving error:', err);
|
|
98
99
|
next();
|
|
@@ -122,8 +123,33 @@ export async function vitePluginStorybookAstroMiddleware(options: FrameworkOptio
|
|
|
122
123
|
};
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Creates a Vite logger that silences known benign warnings emitted by Astro's
|
|
128
|
+
* Vite plugin in the SSR server context:
|
|
129
|
+
* - "Missing pages directory" — Storybook and test contexts have no src/pages.
|
|
130
|
+
* - "points to missing source files" — Sourcemap gaps in the `entities` package.
|
|
131
|
+
*/
|
|
132
|
+
function createSsrServerLogger() {
|
|
133
|
+
const logger = createLogger();
|
|
134
|
+
const originalWarn = logger.warn.bind(logger);
|
|
135
|
+
|
|
136
|
+
logger.warn = (msg, options) => {
|
|
137
|
+
if (
|
|
138
|
+
msg.includes('Missing pages directory') ||
|
|
139
|
+
msg.includes('points to missing source files') ||
|
|
140
|
+
msg.includes('Failed to load source map for')
|
|
141
|
+
) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
originalWarn(msg, options);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return logger;
|
|
149
|
+
}
|
|
150
|
+
|
|
125
151
|
export async function createViteServer(integrations: Integration[], resolveFrom = process.cwd()) {
|
|
126
|
-
const { getViteConfig } = await importAstroConfig(resolveFrom);
|
|
152
|
+
const { getViteConfig, passthroughImageService } = await importAstroConfig(resolveFrom);
|
|
127
153
|
const safeIntegrations = integrations ?? [];
|
|
128
154
|
const projectAstroResolutionPlugin = createProjectAstroResolutionPlugin(resolveFrom);
|
|
129
155
|
|
|
@@ -133,17 +159,23 @@ export async function createViteServer(integrations: Integration[], resolveFrom
|
|
|
133
159
|
configFile: false,
|
|
134
160
|
integrations: await Promise.all(
|
|
135
161
|
safeIntegrations.map((integration) => integration.loadIntegration(resolveFrom))
|
|
136
|
-
)
|
|
162
|
+
),
|
|
163
|
+
// Use the passthrough image service so nested components that use <Image>
|
|
164
|
+
// from astro:assets render as plain <img> tags without triggering image
|
|
165
|
+
// optimization (which fails in the Storybook SSR context).
|
|
166
|
+
image: { service: passthroughImageService() }
|
|
137
167
|
}
|
|
138
168
|
)({ mode: 'development', command: 'serve' });
|
|
139
169
|
|
|
140
170
|
const viteServer = await createServer({
|
|
141
171
|
configFile: false,
|
|
142
172
|
...config,
|
|
173
|
+
customLogger: createSsrServerLogger(),
|
|
143
174
|
plugins: [
|
|
144
175
|
projectAstroResolutionPlugin,
|
|
145
176
|
// Fallbacks must come first to intercept before Astro's plugins
|
|
146
177
|
vitePluginAstroFontsFallback(),
|
|
178
|
+
vitePluginAstroIntegrationOptsFallback(),
|
|
147
179
|
vitePluginAstroVueFallback(),
|
|
148
180
|
vitePluginAstroRoutesFallback(),
|
|
149
181
|
...(config.plugins?.filter(Boolean) ?? []),
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { RenderMode, ServerBuildOptions } from './types.ts';
|
|
2
|
+
import { createVirtualModulePlugin } from './vite/createVirtualModulePlugin.ts';
|
|
3
|
+
|
|
4
|
+
const packageName = '@storybook-astro/framework';
|
|
5
|
+
|
|
6
|
+
export function viteStorybookAstroRendererPlugin(options: {
|
|
7
|
+
mode: 'development' | 'production';
|
|
8
|
+
renderMode?: RenderMode;
|
|
9
|
+
server?: ServerBuildOptions;
|
|
10
|
+
}) {
|
|
11
|
+
const pluginName = 'storybook-astro:renderer-module';
|
|
12
|
+
const virtualModuleId = 'virtual:storybook-astro-renderer';
|
|
13
|
+
const isProduction = options.mode === 'production';
|
|
14
|
+
const isStaticMode = options.renderMode === 'static';
|
|
15
|
+
|
|
16
|
+
return createVirtualModulePlugin({
|
|
17
|
+
pluginName,
|
|
18
|
+
virtualModuleId,
|
|
19
|
+
load() {
|
|
20
|
+
if (!isProduction) {
|
|
21
|
+
return `export * from '${packageName}/renderer/renderer-dev.ts';`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (isStaticMode) {
|
|
25
|
+
return `export * from '${packageName}/renderer/renderer-static.ts';`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return [
|
|
29
|
+
`import { createServerRenderer } from '${packageName}/renderer/renderer-server.ts';`,
|
|
30
|
+
`const renderer = createServerRenderer(${JSON.stringify(
|
|
31
|
+
{
|
|
32
|
+
serverUrl: options.server?.serverUrl,
|
|
33
|
+
authToken: options.server?.authToken,
|
|
34
|
+
authHeader: options.server?.authHeader
|
|
35
|
+
},
|
|
36
|
+
null,
|
|
37
|
+
2
|
|
38
|
+
)});`,
|
|
39
|
+
'export const render = renderer.render;',
|
|
40
|
+
'export const init = renderer.init;',
|
|
41
|
+
'export const applyStyles = renderer.applyStyles;'
|
|
42
|
+
].join('\n');
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
package/src/vitest/config.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { defineConfig as defineVitestConfig } from 'vitest/config';
|
|
2
|
+
import { createLogger } from 'vite';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
2
4
|
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import type { InlineConfig, PluginOption } from 'vite';
|
|
4
6
|
import type { Integration } from '../integrations/base.ts';
|
|
@@ -7,9 +9,35 @@ import { vitePluginAstroComponentMarker } from '../vitePluginAstroComponentMarke
|
|
|
7
9
|
import { registerTestingIntegrationsForRoot } from '../testing/integration-config.ts';
|
|
8
10
|
import { cjsInteropPlugin, vitestPatchForSolidJs } from './vite-plugins.ts';
|
|
9
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Creates a Vite logger that suppresses known benign warnings in the test context:
|
|
14
|
+
* - "Missing pages directory" — Astro warns when no src/pages exists, but component
|
|
15
|
+
* tests don't use pages so this is always safe to ignore.
|
|
16
|
+
* - "points to missing source files" — Sourcemap warnings from the `entities` package
|
|
17
|
+
* which ships without source files. Not actionable.
|
|
18
|
+
*/
|
|
19
|
+
function createTestLogger() {
|
|
20
|
+
const logger = createLogger();
|
|
21
|
+
const originalWarn = logger.warn.bind(logger);
|
|
22
|
+
|
|
23
|
+
logger.warn = (msg, options) => {
|
|
24
|
+
if (
|
|
25
|
+
msg.includes('Missing pages directory') ||
|
|
26
|
+
msg.includes('points to missing source files') ||
|
|
27
|
+
msg.includes('Failed to load source map for')
|
|
28
|
+
) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
originalWarn(msg, options);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return logger;
|
|
36
|
+
}
|
|
37
|
+
|
|
10
38
|
// Type definition omits 'test' to allow Vitest-specific config options
|
|
11
39
|
// Vite 8 type definitions conflict with Vitest config when used in monorepo
|
|
12
|
-
|
|
40
|
+
|
|
13
41
|
export type TestingDefineConfig = Omit<InlineConfig, 'plugins' | 'test'> & {
|
|
14
42
|
integrations?: Integration[];
|
|
15
43
|
plugins?: PluginOption[];
|
|
@@ -51,7 +79,13 @@ export function defineConfig(options: TestingDefineConfig) {
|
|
|
51
79
|
|
|
52
80
|
registerTestingIntegrationsForRoot(root, integrations);
|
|
53
81
|
|
|
54
|
-
|
|
82
|
+
// In the workspace, import.meta.url points to src/vitest/config.ts so global-setup.ts exists.
|
|
83
|
+
// In a compiled tarball install, import.meta.url points to dist/vitest/config.js so we fall
|
|
84
|
+
// back to global-setup.js which is the tsup-compiled output.
|
|
85
|
+
const globalSetupTsPath = fileURLToPath(new URL('./global-setup.ts', import.meta.url));
|
|
86
|
+
const globalSetupFilePath = existsSync(globalSetupTsPath)
|
|
87
|
+
? globalSetupTsPath
|
|
88
|
+
: fileURLToPath(new URL('./global-setup.js', import.meta.url));
|
|
55
89
|
const testConfig = {
|
|
56
90
|
...rest.test,
|
|
57
91
|
globalSetup: normalizeGlobalSetup(rest.test?.globalSetup, globalSetupFilePath)
|
|
@@ -59,7 +93,7 @@ export function defineConfig(options: TestingDefineConfig) {
|
|
|
59
93
|
|
|
60
94
|
// Cast to any to work around Vite 8 type conflicts in monorepo environments
|
|
61
95
|
// where multiple Vite versions exist in node_modules
|
|
62
|
-
|
|
96
|
+
|
|
63
97
|
const vitestConfig = defineVitestConfig({
|
|
64
98
|
...rest,
|
|
65
99
|
root,
|
|
@@ -87,9 +121,16 @@ export function defineConfig(options: TestingDefineConfig) {
|
|
|
87
121
|
})
|
|
88
122
|
);
|
|
89
123
|
|
|
124
|
+
const testLogger = createTestLogger();
|
|
125
|
+
|
|
90
126
|
return async ({ mode: viteMode, command }: { mode: string; command: 'build' | 'serve' }) => {
|
|
91
127
|
const astroConfigFactory = await astroConfigFactoryPromise;
|
|
128
|
+
const config = await astroConfigFactory({ mode: viteMode, command });
|
|
129
|
+
|
|
130
|
+
// Inject the logger — this overrides any logger Astro may have set,
|
|
131
|
+
// which is intentional since we only filter benign test-context noise.
|
|
132
|
+
config.customLogger = testLogger;
|
|
92
133
|
|
|
93
|
-
return
|
|
134
|
+
return config;
|
|
94
135
|
};
|
|
95
136
|
}
|
|
@@ -3,7 +3,50 @@ import {
|
|
|
3
3
|
startTestingRendererDaemon
|
|
4
4
|
} from '../testing/renderer-daemon.ts';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Patterns for warnings that are always benign in the test context and should
|
|
8
|
+
* be silenced so they don't pollute test output.
|
|
9
|
+
*
|
|
10
|
+
* - "Missing pages directory" — Astro emits this when the project root has no
|
|
11
|
+
* src/pages directory. Component tests never have pages.
|
|
12
|
+
* - "points to missing source files" — Sourcemap gaps in the `entities` package;
|
|
13
|
+
* a third-party packaging issue, not actionable.
|
|
14
|
+
*/
|
|
15
|
+
const SUPPRESSED_WARNING_PATTERNS = [
|
|
16
|
+
'Missing pages directory',
|
|
17
|
+
'points to missing source files',
|
|
18
|
+
'Failed to load source map for'
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function shouldSuppress(chunk: Buffer | string): boolean {
|
|
22
|
+
const msg = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk);
|
|
23
|
+
|
|
24
|
+
return SUPPRESSED_WARNING_PATTERNS.some((pattern) => msg.includes(pattern));
|
|
25
|
+
}
|
|
26
|
+
|
|
6
27
|
export default async function globalSetup() {
|
|
28
|
+
// Intercept stderr before starting the daemon so that Astro's own logger
|
|
29
|
+
// (which bypasses Vite's customLogger) doesn't leak benign noise into output.
|
|
30
|
+
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
31
|
+
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
(process.stderr as any).write = function (
|
|
34
|
+
chunk: Buffer | string,
|
|
35
|
+
encodingOrCb?: BufferEncoding | ((err?: Error | null) => void),
|
|
36
|
+
cb?: (err?: Error | null) => void
|
|
37
|
+
): boolean {
|
|
38
|
+
if (shouldSuppress(chunk)) {
|
|
39
|
+
const done = typeof encodingOrCb === 'function' ? encodingOrCb : cb;
|
|
40
|
+
|
|
41
|
+
done?.();
|
|
42
|
+
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
return (originalWrite as any)(chunk, encodingOrCb, cb);
|
|
48
|
+
};
|
|
49
|
+
|
|
7
50
|
const daemon = await startTestingRendererDaemon();
|
|
8
51
|
|
|
9
52
|
// Workers discover the shared renderer via env instead of creating their own SSR stack.
|
|
@@ -12,5 +55,7 @@ export default async function globalSetup() {
|
|
|
12
55
|
return async () => {
|
|
13
56
|
await daemon.close();
|
|
14
57
|
delete process.env[TESTING_RENDERER_DAEMON_URL_ENV];
|
|
58
|
+
// Restore stderr so post-teardown output is unaffected.
|
|
59
|
+
process.stderr.write = originalWrite;
|
|
15
60
|
};
|
|
16
61
|
}
|