@storybook-astro/framework 0.1.0-beta.9 → 1.0.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/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-KSDXET2L.js +660 -0
- package/dist/chunk-KSDXET2L.js.map +1 -0
- package/dist/chunk-PJEDXZVN.js +240 -0
- package/dist/chunk-PJEDXZVN.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 +47 -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-KTGNRGDJ.js +0 -561
- package/dist/chunk-KTGNRGDJ.js.map +0 -1
package/src/testing.ts
CHANGED
|
@@ -1,254 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Testing utilities for @storybook-astro/framework
|
|
3
|
-
*
|
|
4
|
-
* Provides test helpers for validating Storybook stories in Vitest,
|
|
5
|
-
* and Vite plugins needed for the test environment.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```ts
|
|
9
|
-
* // In a test file:
|
|
10
|
-
* import { composeStories } from '@storybook-astro/framework';
|
|
11
|
-
* import { testStoryRenders, testStoryComposition } from '@storybook-astro/framework/testing';
|
|
12
|
-
* import * as stories from './Card.stories.jsx';
|
|
13
|
-
*
|
|
14
|
-
* const { Default } = composeStories(stories);
|
|
15
|
-
* testStoryComposition('Default', Default);
|
|
16
|
-
* testStoryRenders('Card Default', Default);
|
|
17
|
-
* ```
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```ts
|
|
21
|
-
* // In vitest.config.ts:
|
|
22
|
-
* import { cjsInteropPlugin } from '@storybook-astro/framework/testing';
|
|
23
|
-
* import { defineConfig } from 'vitest/config';
|
|
24
|
-
*
|
|
25
|
-
* export default defineConfig({
|
|
26
|
-
* plugins: [cjsInteropPlugin()],
|
|
27
|
-
* // ...
|
|
28
|
-
* });
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
// eslint-disable-next-line n/no-extraneous-import
|
|
33
|
-
import { test, expect } from 'vitest';
|
|
34
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
35
|
-
import { join } from 'node:path';
|
|
36
|
-
import type { Plugin } from 'vite';
|
|
37
|
-
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
// Test helpers
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Registers a Vitest test that validates a composed story can render.
|
|
44
|
-
*
|
|
45
|
-
* The test will:
|
|
46
|
-
* - Pass if the component renders successfully in Storybook
|
|
47
|
-
* - Pass if the Storybook runtime is not available (expected in Vitest)
|
|
48
|
-
* - Pass if a framework hits an SSR-only limitation (e.g. Solid)
|
|
49
|
-
* - Fail if the component has a broken framework integration or missing renderer
|
|
50
|
-
*
|
|
51
|
-
* @param storyName - Display name for the test
|
|
52
|
-
* @param story - A composed story returned by `composeStories`
|
|
53
|
-
*/
|
|
54
|
-
export function testStoryRenders(storyName: string, story: any) {
|
|
55
|
-
test(`${storyName} renders in Storybook`, async () => {
|
|
56
|
-
expect(story).toBeDefined();
|
|
57
|
-
expect(typeof story).toBe('function');
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
// First try calling the story directly - this will use our custom render function
|
|
61
|
-
// which can detect broken framework integrations immediately
|
|
62
|
-
const directResult = story();
|
|
63
|
-
|
|
64
|
-
// If direct call succeeds, try the full Storybook run method
|
|
65
|
-
const result = await story.run?.() || directResult;
|
|
66
|
-
|
|
67
|
-
// If we get here, the component should have rendered successfully
|
|
68
|
-
expect(result).toBeDefined();
|
|
69
|
-
|
|
70
|
-
// For Astro components, check that we have component and args
|
|
71
|
-
if (result.component) {
|
|
72
|
-
expect(result.component).toBeDefined();
|
|
73
|
-
expect(result.args).toBeDefined();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
console.warn(`✓ ${storyName} rendered successfully`);
|
|
77
|
-
|
|
78
|
-
} catch (error: any) {
|
|
79
|
-
const errorMessage = error.message;
|
|
80
|
-
|
|
81
|
-
// Check if this is an expected error when Storybook is not running
|
|
82
|
-
if (errorMessage.includes('renderToCanvas is not a function')) {
|
|
83
|
-
// This indicates the component is properly configured but Storybook runtime isn't available
|
|
84
|
-
// This is acceptable for Astro components that work in Storybook
|
|
85
|
-
console.warn(`✓ ${storyName} is properly configured (Storybook runtime not available)`);
|
|
86
|
-
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// SSR limitation: some framework components (e.g. Solid) are compiled in
|
|
91
|
-
// SSR mode for the test environment. Client-only APIs are unavailable
|
|
92
|
-
// during story.run(), but the component works in Storybook's browser.
|
|
93
|
-
if (errorMessage.includes('Client-only API called on the server side')) {
|
|
94
|
-
console.warn(`✓ ${storyName} is properly configured (SSR-only test limitation)`);
|
|
95
|
-
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Check for renderer not found errors (indicates broken integration)
|
|
100
|
-
if (errorMessage.includes('Renderer') && errorMessage.includes('not found')) {
|
|
101
|
-
console.error(`✗ ${storyName} failed: ${errorMessage}`);
|
|
102
|
-
throw new Error(`${storyName} has a broken framework integration: ${errorMessage}`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Check for missing renderer parameter
|
|
106
|
-
if (errorMessage.includes('no renderer is specified')) {
|
|
107
|
-
console.error(`✗ ${storyName} failed: ${errorMessage}`);
|
|
108
|
-
throw new Error(`${storyName} is missing renderer parameter: ${errorMessage}`);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Any other error indicates a real problem with the component
|
|
112
|
-
console.error(`✗ ${storyName} failed with unexpected error:`, error);
|
|
113
|
-
throw new Error(`${storyName} failed to render: ${errorMessage}`);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Registers a Vitest test that checks basic story composition.
|
|
120
|
-
*
|
|
121
|
-
* Validates that the story can be imported, composed, and has the
|
|
122
|
-
* expected name. Optionally checks that args match expected values.
|
|
123
|
-
*
|
|
124
|
-
* @param storyName - Expected `story.storyName` value
|
|
125
|
-
* @param story - A composed story returned by `composeStories`
|
|
126
|
-
* @param expectedArgs - Optional args to assert with `toEqual`
|
|
127
|
-
*/
|
|
128
|
-
export function testStoryComposition(storyName: string, story: any, expectedArgs?: any) {
|
|
129
|
-
test(`${storyName} can be composed`, () => {
|
|
130
|
-
expect(story).toBeDefined();
|
|
131
|
-
expect(typeof story).toBe('function');
|
|
132
|
-
expect(story.storyName).toBe(storyName);
|
|
133
|
-
|
|
134
|
-
if (expectedArgs) {
|
|
135
|
-
expect(story.args).toEqual(expectedArgs);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ---------------------------------------------------------------------------
|
|
141
|
-
// Vite plugins for testing
|
|
142
|
-
// ---------------------------------------------------------------------------
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Vite plugin that wraps CJS modules with ESM-compatible shims.
|
|
146
|
-
*
|
|
147
|
-
* Vite 6's ESM module runner cannot evaluate raw CommonJS modules that use
|
|
148
|
-
* `module.exports` or `exports`. This plugin detects CJS modules in
|
|
149
|
-
* node_modules during transform and wraps them so they work in ESM context.
|
|
150
|
-
*
|
|
151
|
-
* Use this in your `vitest.config.ts` plugins array when testing with
|
|
152
|
-
* Astro 6 and Vite 6+.
|
|
153
|
-
*
|
|
154
|
-
* @example
|
|
155
|
-
* ```ts
|
|
156
|
-
* import { cjsInteropPlugin } from '@storybook-astro/framework/testing';
|
|
157
|
-
*
|
|
158
|
-
* export default defineConfig({
|
|
159
|
-
* plugins: [cjsInteropPlugin()],
|
|
160
|
-
* });
|
|
161
|
-
* ```
|
|
162
|
-
*/
|
|
163
|
-
export function cjsInteropPlugin(): Plugin {
|
|
164
|
-
return {
|
|
165
|
-
name: 'cjs-esm-interop',
|
|
166
|
-
enforce: 'pre',
|
|
167
|
-
resolveId(id) {
|
|
168
|
-
// When Vite resolves a bare import in SSR/test context, redirect
|
|
169
|
-
// packages that have ESM entry points to those entries instead of
|
|
170
|
-
// their CJS "main" or "require" entries.
|
|
171
|
-
if (id.startsWith('.') || id.startsWith('/') || id.startsWith('\0') || id.includes('node_modules')) {return;}
|
|
172
|
-
|
|
173
|
-
// Find the package's node_modules directory
|
|
174
|
-
const parts = id.split('/');
|
|
175
|
-
const pkgName = id.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
176
|
-
const subpath = parts.slice(pkgName.split('/').length).join('/');
|
|
177
|
-
|
|
178
|
-
// Only redirect the main entry (no subpath or common subpaths)
|
|
179
|
-
if (subpath && !['server-renderer', 'server', 'client'].includes(subpath)) {return;}
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
// Find the package.json
|
|
183
|
-
const nmDir = join(process.cwd(), 'node_modules', pkgName);
|
|
184
|
-
const pkgJsonPath = join(nmDir, 'package.json');
|
|
185
|
-
|
|
186
|
-
if (!existsSync(pkgJsonPath)) {return;}
|
|
187
|
-
|
|
188
|
-
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
189
|
-
|
|
190
|
-
// Check for ESM entry in exports map
|
|
191
|
-
const exportKey = subpath ? `./${subpath}` : '.';
|
|
192
|
-
const exportEntry = pkgJson.exports?.[exportKey];
|
|
193
|
-
|
|
194
|
-
if (exportEntry) {
|
|
195
|
-
const importEntry = exportEntry.import;
|
|
196
|
-
|
|
197
|
-
if (importEntry) {
|
|
198
|
-
const esmPath = typeof importEntry === 'string'
|
|
199
|
-
? importEntry
|
|
200
|
-
: importEntry.default || importEntry.node;
|
|
201
|
-
|
|
202
|
-
if (esmPath) {
|
|
203
|
-
const resolved = join(nmDir, esmPath);
|
|
204
|
-
|
|
205
|
-
if (existsSync(resolved)) {
|
|
206
|
-
return resolved;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Fallback: check the "module" field
|
|
213
|
-
if (!subpath && pkgJson.module) {
|
|
214
|
-
const resolved = join(nmDir, pkgJson.module);
|
|
215
|
-
|
|
216
|
-
if (existsSync(resolved)) {
|
|
217
|
-
return resolved;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
} catch {
|
|
221
|
-
// Ignore resolution errors
|
|
222
|
-
}
|
|
223
|
-
},
|
|
224
|
-
transform(code, id) {
|
|
225
|
-
// Only transform node_modules files
|
|
226
|
-
if (!id.includes('node_modules')) {return;}
|
|
227
|
-
// Skip virtual modules
|
|
228
|
-
if (id.startsWith('\0')) {return;}
|
|
229
|
-
// Skip files that already use ESM exports
|
|
230
|
-
if (/\bexport\s+(default|const|let|var|function|class|\{|\*)/.test(code)) {return;}
|
|
231
|
-
// Only wrap files that use CJS patterns
|
|
232
|
-
if (!code.includes('module.exports') && !code.includes('exports.')) {return;}
|
|
233
|
-
|
|
234
|
-
const dirPath = id.substring(0, id.lastIndexOf('/'));
|
|
235
|
-
const fileName = id;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return {
|
|
239
|
-
code: [
|
|
240
|
-
'import { createRequire as __createRequire } from "module";',
|
|
241
|
-
`var __require = __createRequire("file://${dirPath}/");`,
|
|
242
|
-
'var module = { exports: {} };',
|
|
243
|
-
'var exports = module.exports;',
|
|
244
|
-
'function require(id) { return __require(id); }',
|
|
245
|
-
`var __dirname = ${JSON.stringify(dirPath)};`,
|
|
246
|
-
`var __filename = ${JSON.stringify(fileName)};`,
|
|
247
|
-
code,
|
|
248
|
-
'export default module.exports;',
|
|
249
|
-
].join('\n'),
|
|
250
|
-
map: null,
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
}
|
|
1
|
+
export * from './testing/index.ts';
|
package/src/types.ts
CHANGED
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
import type { CompatibleString, Options } from 'storybook/internal/types';
|
|
2
2
|
import type { InlineConfig } from 'vite';
|
|
3
3
|
import type { Integration } from './integrations/index.ts';
|
|
4
|
+
import type { SanitizationOptions } from './lib/sanitization.ts';
|
|
5
|
+
import type { StoryRulesOptions } from './rules-options.ts';
|
|
4
6
|
|
|
5
7
|
type FrameworkName = CompatibleString<'@storybook-astro/framework'>;
|
|
6
8
|
|
|
7
|
-
export type { Integration };
|
|
9
|
+
export type { Integration, SanitizationOptions, StoryRulesOptions };
|
|
10
|
+
|
|
11
|
+
export type RenderStoryInput = {
|
|
12
|
+
id: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
8
17
|
export type FrameworkOptions = {
|
|
9
|
-
integrations
|
|
18
|
+
integrations?: Integration[];
|
|
19
|
+
sanitization?: SanitizationOptions;
|
|
20
|
+
storyRules?: StoryRulesOptions;
|
|
21
|
+
resolveFrom?: string;
|
|
10
22
|
};
|
|
11
23
|
|
|
12
24
|
type StorybookConfigFramework = {
|
|
13
25
|
framework: {
|
|
14
26
|
name: FrameworkName;
|
|
15
|
-
options
|
|
27
|
+
options?: FrameworkOptions;
|
|
16
28
|
};
|
|
17
29
|
};
|
|
18
30
|
|
|
@@ -21,5 +33,5 @@ export type StorybookConfig = StorybookConfigFramework;
|
|
|
21
33
|
type ViteFinal = (config: InlineConfig, options: Options) => InlineConfig | Promise<InlineConfig>;
|
|
22
34
|
|
|
23
35
|
export type StorybookConfigVite = {
|
|
24
|
-
|
|
36
|
+
viteFinal?: ViteFinal;
|
|
25
37
|
};
|
package/src/virtual.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ declare module 'virtual:astro-container-renderers' {
|
|
|
2
2
|
import type { experimental_AstroContainer as AstroContainer } from 'astro/container';
|
|
3
3
|
|
|
4
4
|
export function addRenderers(container: AstroContainer): void;
|
|
5
|
+
export function resolveClientModules(specifier: string): string | undefined;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
|
-
declare module 'virtual:storybook-renderer-fallback' {}
|
|
8
|
+
declare module 'virtual:storybook-renderer-fallback' {}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { PluginOption } from 'vite';
|
|
2
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
3
|
+
import { createVirtualModulePlugin } from './createVirtualModulePlugin.ts';
|
|
4
|
+
|
|
5
|
+
function getPlugin(pluginOption: PluginOption) {
|
|
6
|
+
if (Array.isArray(pluginOption)) {
|
|
7
|
+
throw new Error('Expected a single plugin object, but got a plugin array.');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (!pluginOption || typeof pluginOption !== 'object') {
|
|
11
|
+
throw new Error('Expected plugin option to be an object.');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return pluginOption;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getHookHandler<T extends (...args: unknown[]) => unknown>(hook: unknown): T {
|
|
18
|
+
if (typeof hook === 'function') {
|
|
19
|
+
return hook as T;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (
|
|
23
|
+
typeof hook === 'object' &&
|
|
24
|
+
hook !== null &&
|
|
25
|
+
'handler' in hook &&
|
|
26
|
+
typeof (hook as { handler?: unknown }).handler === 'function'
|
|
27
|
+
) {
|
|
28
|
+
return (hook as { handler: T }).handler;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
throw new Error('Expected hook to be a function or an object with a handler function.');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('createVirtualModulePlugin', () => {
|
|
35
|
+
test('resolves configured virtual module id with a null-byte prefix', () => {
|
|
36
|
+
const pluginOption = createVirtualModulePlugin({
|
|
37
|
+
pluginName: 'test:virtual-module',
|
|
38
|
+
virtualModuleId: 'virtual:test-module',
|
|
39
|
+
load: () => 'export default true;'
|
|
40
|
+
});
|
|
41
|
+
const plugin = getPlugin(pluginOption);
|
|
42
|
+
const resolveId = getHookHandler<(id: string) => string | undefined>(plugin.resolveId);
|
|
43
|
+
|
|
44
|
+
expect(plugin.name).toBe('test:virtual-module');
|
|
45
|
+
expect(resolveId('virtual:test-module')).toBe('\0virtual:test-module');
|
|
46
|
+
expect(resolveId('virtual:other-module')).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('loads module content only for the resolved virtual module id', async () => {
|
|
50
|
+
const load = vi.fn(() => 'export const message = "hello";');
|
|
51
|
+
const pluginOption = createVirtualModulePlugin({
|
|
52
|
+
pluginName: 'test:virtual-module',
|
|
53
|
+
virtualModuleId: 'virtual:test-module',
|
|
54
|
+
load
|
|
55
|
+
});
|
|
56
|
+
const plugin = getPlugin(pluginOption);
|
|
57
|
+
const loadModule = getHookHandler<(id: string) => Promise<string | undefined>>(plugin.load);
|
|
58
|
+
|
|
59
|
+
const result = await loadModule('\0virtual:test-module');
|
|
60
|
+
|
|
61
|
+
expect(result).toBe('export const message = "hello";');
|
|
62
|
+
expect(load).toHaveBeenCalledTimes(1);
|
|
63
|
+
expect(load).toHaveBeenCalledWith('\0virtual:test-module');
|
|
64
|
+
|
|
65
|
+
await expect(loadModule('virtual:test-module')).resolves.toBeUndefined();
|
|
66
|
+
expect(load).toHaveBeenCalledTimes(1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('supports asynchronous virtual module loaders', async () => {
|
|
70
|
+
const pluginOption = createVirtualModulePlugin({
|
|
71
|
+
pluginName: 'test:virtual-module',
|
|
72
|
+
virtualModuleId: 'virtual:test-module',
|
|
73
|
+
load: async () => 'export default "async";'
|
|
74
|
+
});
|
|
75
|
+
const plugin = getPlugin(pluginOption);
|
|
76
|
+
const loadModule = getHookHandler<(id: string) => Promise<string | undefined>>(plugin.load);
|
|
77
|
+
|
|
78
|
+
await expect(loadModule('\0virtual:test-module')).resolves.toBe('export default "async";');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PluginOption } from 'vite';
|
|
2
|
+
|
|
3
|
+
type CreateVirtualModulePluginOptions = {
|
|
4
|
+
pluginName: string;
|
|
5
|
+
virtualModuleId: string;
|
|
6
|
+
load: (id: string) => string | Promise<string> | undefined;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function createVirtualModulePlugin(options: CreateVirtualModulePluginOptions): PluginOption {
|
|
10
|
+
const resolvedVirtualModuleId = `\0${options.virtualModuleId}`;
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
name: options.pluginName,
|
|
14
|
+
resolveId(id) {
|
|
15
|
+
if (id === options.virtualModuleId) {
|
|
16
|
+
return resolvedVirtualModuleId;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
async load(id) {
|
|
20
|
+
if (id === resolvedVirtualModuleId) {
|
|
21
|
+
return options.load(id);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
} satisfies PluginOption;
|
|
25
|
+
}
|
|
@@ -1,35 +1,69 @@
|
|
|
1
1
|
import type { Integration } from './integrations/index.ts';
|
|
2
|
+
import { createVirtualModulePlugin } from './vite/createVirtualModulePlugin.ts';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
type PluginOptions = {
|
|
5
|
+
mode?: 'development' | 'production';
|
|
6
|
+
staticModuleMap?: Record<string, string>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function viteAstroContainerRenderersPlugin(
|
|
10
|
+
integrations: Integration[],
|
|
11
|
+
options: PluginOptions = {}
|
|
12
|
+
) {
|
|
4
13
|
const safeIntegrations = integrations ?? [];
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
const mode = options.mode ?? 'development';
|
|
15
|
+
const staticModuleMap = options.staticModuleMap ?? {};
|
|
16
|
+
|
|
17
|
+
return createVirtualModulePlugin({
|
|
18
|
+
pluginName: 'storybook-astro:container-renderers',
|
|
19
|
+
virtualModuleId: 'virtual:astro-container-renderers',
|
|
20
|
+
load() {
|
|
21
|
+
const importStatements = buildImportStatements(safeIntegrations);
|
|
22
|
+
const clientResolvers =
|
|
23
|
+
mode === 'development'
|
|
24
|
+
? safeIntegrations
|
|
25
|
+
.filter((integration) => typeof integration.resolveClient === 'function')
|
|
26
|
+
.map((integration) =>
|
|
27
|
+
integration.resolveClient.toString().replace(/^resolveClient/, 'function')
|
|
28
|
+
)
|
|
29
|
+
.join(',\n')
|
|
30
|
+
: '';
|
|
31
|
+
|
|
32
|
+
return `
|
|
33
|
+
${importStatements}
|
|
34
|
+
|
|
35
|
+
export function addRenderers(container) {
|
|
36
|
+
${safeIntegrations.map((integration) => buildServerRenderer(integration) + '\n' + buildClientRenderer(integration)).join('\n')}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const staticClientModules = ${JSON.stringify(staticModuleMap, null, 2)};
|
|
40
|
+
|
|
41
|
+
const clientModulesResolvers = [
|
|
42
|
+
${clientResolvers}
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
export function resolveClientModules(specifier) {
|
|
46
|
+
if (Object.hasOwn(staticClientModules, specifier)) {
|
|
47
|
+
return staticClientModules[specifier];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const normalizedSpecifier = specifier.replace(/\\\\/g, '/').replace(/\\?.*$/, '');
|
|
51
|
+
|
|
52
|
+
if (Object.hasOwn(staticClientModules, normalizedSpecifier)) {
|
|
53
|
+
return staticClientModules[normalizedSpecifier];
|
|
26
54
|
}
|
|
27
|
-
`;
|
|
28
55
|
|
|
29
|
-
|
|
30
|
-
|
|
56
|
+
for (const resolver of clientModulesResolvers) {
|
|
57
|
+
const resolution = resolver(specifier);
|
|
58
|
+
|
|
59
|
+
if (resolution) {
|
|
60
|
+
return resolution;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
31
65
|
}
|
|
32
|
-
};
|
|
66
|
+
});
|
|
33
67
|
}
|
|
34
68
|
|
|
35
69
|
function buildImportStatements(integrations: Integration[]) {
|
package/src/vitePluginAstro.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mergeConfig, type InlineConfig } from 'vite';
|
|
2
2
|
import type { Integration } from './integrations/index.ts';
|
|
3
|
+
import { importAstroConfig } from './importAstroConfig.ts';
|
|
3
4
|
|
|
4
5
|
const ASTRO_PLUGINS_THAT_ARE_SUPPOSEDLY_NOT_NEEDED_IN_STORYBOOK = [
|
|
5
6
|
'@astro/plugin-actions',
|
|
@@ -27,8 +28,14 @@ const ASTRO_PLUGINS_THAT_ARE_SUPPOSEDLY_NOT_NEEDED_IN_STORYBOOK = [
|
|
|
27
28
|
'astro:vite-plugin-file-url'
|
|
28
29
|
];
|
|
29
30
|
|
|
30
|
-
export async function mergeWithAstroConfig(
|
|
31
|
-
|
|
31
|
+
export async function mergeWithAstroConfig(
|
|
32
|
+
config: InlineConfig,
|
|
33
|
+
integrations: Integration[] = [],
|
|
34
|
+
resolveFrom = process.cwd(),
|
|
35
|
+
mode = 'development',
|
|
36
|
+
command: 'build' | 'serve' = 'serve'
|
|
37
|
+
) {
|
|
38
|
+
const { getViteConfig } = await importAstroConfig(resolveFrom);
|
|
32
39
|
const safeIntegrations = integrations ?? [];
|
|
33
40
|
|
|
34
41
|
const astroConfig = await getViteConfig(
|
|
@@ -36,12 +43,12 @@ export async function mergeWithAstroConfig(config: InlineConfig, integrations: I
|
|
|
36
43
|
{
|
|
37
44
|
configFile: false,
|
|
38
45
|
integrations: await Promise.all(
|
|
39
|
-
safeIntegrations.map((integration) => integration.loadIntegration())
|
|
46
|
+
safeIntegrations.map((integration) => integration.loadIntegration(resolveFrom))
|
|
40
47
|
)
|
|
41
48
|
}
|
|
42
49
|
)({
|
|
43
|
-
mode
|
|
44
|
-
command
|
|
50
|
+
mode,
|
|
51
|
+
command
|
|
45
52
|
});
|
|
46
53
|
|
|
47
54
|
const filteredPlugins = astroConfig
|