@storybook-astro/framework 1.3.0 → 1.4.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/{chunk-N3WTUD2A.js → chunk-A4DQ67HA.js} +53 -2
- package/dist/chunk-A4DQ67HA.js.map +1 -0
- package/dist/{chunk-2EABPTOY.js → chunk-BV6V2Z4X.js} +2 -2
- package/dist/{chunk-AYYMNFI6.js → chunk-VZXGPM6P.js} +218 -34
- package/dist/chunk-VZXGPM6P.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/middleware.js +10 -2
- package/dist/middleware.js.map +1 -1
- package/dist/node/index.d.ts +1 -1
- package/dist/preset.d.ts +1 -1
- package/dist/preset.js +39 -21
- package/dist/preset.js.map +1 -1
- package/dist/testing.js +3 -3
- package/dist/{types-BCpJLSTo.d.ts → types--SvYP5Ri.d.ts} +55 -0
- package/dist/{viteStorybookAstroMiddlewarePlugin-UB6ZLJ4B.js → viteStorybookAstroMiddlewarePlugin-246I5D3Y.js} +2 -2
- package/dist/vitest/global-setup.js +2 -2
- package/package.json +3 -2
- package/src/lib/resolve-aliased-island.test.ts +150 -0
- package/src/lib/resolve-aliased-island.ts +97 -0
- package/src/loadUserAstroConfig.ts +59 -0
- package/src/middleware.ts +15 -0
- package/src/productionRenderRuntime.ts +6 -2
- package/src/storySsrVite.ts +15 -2
- package/src/types.ts +9 -1
- package/src/vitePluginAstro.ts +10 -3
- package/src/vitePluginAstroBuildPrerender.ts +4 -1
- package/src/vitePluginAstroBuildShared.test.ts +25 -6
- package/src/vitePluginAstroBuildShared.ts +25 -12
- package/src/vitePluginAstroFonts.test.ts +153 -0
- package/src/vitePluginAstroFonts.ts +302 -0
- package/src/viteStorybookAstroMiddlewarePlugin.ts +20 -8
- package/dist/chunk-AYYMNFI6.js.map +0 -1
- package/dist/chunk-N3WTUD2A.js.map +0 -1
- package/src/vitePluginAstroFontsFallback.ts +0 -69
- /package/dist/{chunk-2EABPTOY.js.map → chunk-BV6V2Z4X.js.map} +0 -0
- /package/dist/{viteStorybookAstroMiddlewarePlugin-UB6ZLJ4B.js.map → viteStorybookAstroMiddlewarePlugin-246I5D3Y.js.map} +0 -0
|
@@ -14,6 +14,54 @@ type StoryRulesOptions = string | {
|
|
|
14
14
|
configFile: string;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
interface StorybookFontProvider {
|
|
18
|
+
name: string;
|
|
19
|
+
init?: (context: {
|
|
20
|
+
storage: FontStorage;
|
|
21
|
+
root: URL;
|
|
22
|
+
}) => Promise<void> | void;
|
|
23
|
+
resolveFont: (options: {
|
|
24
|
+
familyName: string;
|
|
25
|
+
weights: string[];
|
|
26
|
+
styles: string[];
|
|
27
|
+
subsets: string[];
|
|
28
|
+
formats: string[];
|
|
29
|
+
}) => Promise<{
|
|
30
|
+
fonts: FontFaceData[];
|
|
31
|
+
} | undefined> | {
|
|
32
|
+
fonts: FontFaceData[];
|
|
33
|
+
} | undefined;
|
|
34
|
+
}
|
|
35
|
+
interface FontFaceData {
|
|
36
|
+
src: Array<{
|
|
37
|
+
url?: string;
|
|
38
|
+
name?: string;
|
|
39
|
+
format?: string;
|
|
40
|
+
tech?: string;
|
|
41
|
+
}>;
|
|
42
|
+
weight?: string | number | [number, number];
|
|
43
|
+
style?: string;
|
|
44
|
+
display?: string;
|
|
45
|
+
unicodeRange?: string[];
|
|
46
|
+
featureSettings?: string;
|
|
47
|
+
variationSettings?: string;
|
|
48
|
+
}
|
|
49
|
+
interface StorybookFontFamily {
|
|
50
|
+
name: string;
|
|
51
|
+
cssVariable: string;
|
|
52
|
+
provider: StorybookFontProvider;
|
|
53
|
+
weights?: Array<string | number>;
|
|
54
|
+
styles?: string[];
|
|
55
|
+
subsets?: string[];
|
|
56
|
+
formats?: string[];
|
|
57
|
+
fallbacks?: string[];
|
|
58
|
+
display?: string;
|
|
59
|
+
}
|
|
60
|
+
interface FontStorage {
|
|
61
|
+
getItem: <T = unknown>(key: string, init?: () => Promise<T> | T) => Promise<T | null>;
|
|
62
|
+
setItem: (key: string, value: unknown) => Promise<void> | void;
|
|
63
|
+
}
|
|
64
|
+
|
|
17
65
|
type FrameworkName = CompatibleString<'@storybook-astro/framework'>;
|
|
18
66
|
|
|
19
67
|
type RenderMode = 'server' | 'static';
|
|
@@ -31,6 +79,13 @@ type BaseFrameworkOptions = {
|
|
|
31
79
|
integrations?: Integration[];
|
|
32
80
|
sanitization?: SanitizationOptions;
|
|
33
81
|
resolveFrom?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Astro font families to resolve and inject as @font-face CSS during story
|
|
84
|
+
* rendering. Pass the same array you have in your `astro.config.ts` under
|
|
85
|
+
* `fonts:`. Currently honored in development; static/server builds fall
|
|
86
|
+
* back to no-op stubs.
|
|
87
|
+
*/
|
|
88
|
+
fonts?: StorybookFontFamily[];
|
|
34
89
|
};
|
|
35
90
|
type ServerFrameworkOptions = BaseFrameworkOptions & {
|
|
36
91
|
renderMode?: 'server';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createViteServer,
|
|
3
3
|
vitePluginStorybookAstroMiddleware
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VZXGPM6P.js";
|
|
5
5
|
import "./chunk-PUTCAN6X.js";
|
|
6
6
|
import "./chunk-B5HHF6FC.js";
|
|
7
7
|
import "./chunk-G3PMV62Z.js";
|
|
@@ -9,4 +9,4 @@ export {
|
|
|
9
9
|
createViteServer,
|
|
10
10
|
vitePluginStorybookAstroMiddleware
|
|
11
11
|
};
|
|
12
|
-
//# sourceMappingURL=viteStorybookAstroMiddlewarePlugin-
|
|
12
|
+
//# sourceMappingURL=viteStorybookAstroMiddlewarePlugin-246I5D3Y.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TESTING_RENDERER_DAEMON_URL_ENV,
|
|
3
3
|
startTestingRendererDaemon
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-BV6V2Z4X.js";
|
|
5
5
|
import "../chunk-WUTCMEF5.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-VZXGPM6P.js";
|
|
7
7
|
import "../chunk-PUTCAN6X.js";
|
|
8
8
|
import "../chunk-7YBE4TTI.js";
|
|
9
9
|
import "../chunk-B5HHF6FC.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storybook-astro/framework",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0-canary.2",
|
|
4
4
|
"description": "Community-supported Storybook framework for Astro 5 & 6 components",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -142,9 +142,10 @@
|
|
|
142
142
|
}
|
|
143
143
|
},
|
|
144
144
|
"dependencies": {
|
|
145
|
-
"@storybook-astro/renderer": "1.
|
|
145
|
+
"@storybook-astro/renderer": "1.4.0-canary.2",
|
|
146
146
|
"hono": "^4.11.12",
|
|
147
147
|
"sanitize-html": "^2.17.0",
|
|
148
|
+
"tsconfck": "^3.1.6",
|
|
148
149
|
"vite": "^6.4.1 || ^7.0.0 || ^8.0.0"
|
|
149
150
|
}
|
|
150
151
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
5
|
+
import { resolveAliasedIsland } from './resolve-aliased-island.ts';
|
|
6
|
+
|
|
7
|
+
describe('resolveAliasedIsland', () => {
|
|
8
|
+
let tmpDir: string;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
tmpDir = await mkdtemp(join(tmpdir(), 'storybook-astro-alias-test-'));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(async () => {
|
|
15
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
async function writeTsconfig(content: object) {
|
|
19
|
+
await writeFile(join(tmpDir, 'tsconfig.json'), JSON.stringify(content, null, 2));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test('resolves a basic @/ alias to an absolute path', async () => {
|
|
23
|
+
await mkdir(join(tmpDir, 'src', 'components'), { recursive: true });
|
|
24
|
+
const counterFile = join(tmpDir, 'src', 'components', 'Counter.tsx');
|
|
25
|
+
|
|
26
|
+
await writeFile(counterFile, `export default function Counter() {}`);
|
|
27
|
+
await writeTsconfig({
|
|
28
|
+
compilerOptions: {
|
|
29
|
+
paths: { '@/*': ['src/*'] }
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const result = await resolveAliasedIsland('@/components/Counter', tmpDir);
|
|
34
|
+
|
|
35
|
+
expect(result).toBe(counterFile.replace(/\\/g, '/'));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('resolves when baseUrl shifts the path root', async () => {
|
|
39
|
+
await mkdir(join(tmpDir, 'src', 'components'), { recursive: true });
|
|
40
|
+
const counterFile = join(tmpDir, 'src', 'components', 'Counter.tsx');
|
|
41
|
+
|
|
42
|
+
await writeFile(counterFile, `export default function Counter() {}`);
|
|
43
|
+
// With baseUrl: "src", paths are relative to src/
|
|
44
|
+
await writeTsconfig({
|
|
45
|
+
compilerOptions: {
|
|
46
|
+
baseUrl: 'src',
|
|
47
|
+
paths: { '@/*': ['*'] }
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const result = await resolveAliasedIsland('@/components/Counter', tmpDir);
|
|
52
|
+
|
|
53
|
+
expect(result).toBe(counterFile.replace(/\\/g, '/'));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('falls back to second target when first does not exist on disk', async () => {
|
|
57
|
+
await mkdir(join(tmpDir, 'app', 'components'), { recursive: true });
|
|
58
|
+
const counterFile = join(tmpDir, 'app', 'components', 'Counter.tsx');
|
|
59
|
+
|
|
60
|
+
await writeFile(counterFile, `export default function Counter() {}`);
|
|
61
|
+
// First target points at a non-existent dir; second target resolves.
|
|
62
|
+
await writeTsconfig({
|
|
63
|
+
compilerOptions: {
|
|
64
|
+
paths: { '@/*': ['missing/*', 'app/*'] }
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const result = await resolveAliasedIsland('@/components/Counter', tmpDir);
|
|
69
|
+
|
|
70
|
+
expect(result).toBe(counterFile.replace(/\\/g, '/'));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('resolves an alias with an explicit file extension in the specifier', async () => {
|
|
74
|
+
await mkdir(join(tmpDir, 'src'), { recursive: true });
|
|
75
|
+
const file = join(tmpDir, 'src', 'Button.tsx');
|
|
76
|
+
|
|
77
|
+
await writeFile(file, `export default function Button() {}`);
|
|
78
|
+
await writeTsconfig({ compilerOptions: { paths: { '@/*': ['src/*'] } } });
|
|
79
|
+
|
|
80
|
+
const result = await resolveAliasedIsland('@/Button.tsx', tmpDir);
|
|
81
|
+
|
|
82
|
+
expect(result).toBe(file.replace(/\\/g, '/'));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('returns undefined when aliased file does not exist on disk', async () => {
|
|
86
|
+
await writeTsconfig({ compilerOptions: { paths: { '@/*': ['src/*'] } } });
|
|
87
|
+
|
|
88
|
+
const result = await resolveAliasedIsland('@/components/Missing', tmpDir);
|
|
89
|
+
|
|
90
|
+
expect(result).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('returns undefined when tsconfig has no paths', async () => {
|
|
94
|
+
await mkdir(join(tmpDir, 'src'), { recursive: true });
|
|
95
|
+
await writeFile(join(tmpDir, 'src', 'Counter.tsx'), `export default function Counter() {}`);
|
|
96
|
+
await writeTsconfig({ compilerOptions: {} });
|
|
97
|
+
|
|
98
|
+
const result = await resolveAliasedIsland('@/Counter', tmpDir);
|
|
99
|
+
|
|
100
|
+
expect(result).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('returns undefined when tsconfig.json is absent', async () => {
|
|
104
|
+
const result = await resolveAliasedIsland('@/Counter', tmpDir);
|
|
105
|
+
|
|
106
|
+
expect(result).toBeUndefined();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('returns undefined for relative specifiers (already handled elsewhere)', async () => {
|
|
110
|
+
const result = await resolveAliasedIsland('./components/Counter', tmpDir);
|
|
111
|
+
|
|
112
|
+
expect(result).toBeUndefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('returns undefined for absolute paths', async () => {
|
|
116
|
+
const result = await resolveAliasedIsland('/abs/path/Counter.tsx', tmpDir);
|
|
117
|
+
|
|
118
|
+
expect(result).toBeUndefined();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('returns undefined for virtual: specifiers', async () => {
|
|
122
|
+
const result = await resolveAliasedIsland('virtual:astro-container-renderers', tmpDir);
|
|
123
|
+
|
|
124
|
+
expect(result).toBeUndefined();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('returns undefined for astro: specifiers', async () => {
|
|
128
|
+
const result = await resolveAliasedIsland('astro:scripts/page.js', tmpDir);
|
|
129
|
+
|
|
130
|
+
expect(result).toBeUndefined();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('returns undefined for /@fs/ specifiers', async () => {
|
|
134
|
+
const result = await resolveAliasedIsland('/@fs/abs/path/Counter.tsx', tmpDir);
|
|
135
|
+
|
|
136
|
+
expect(result).toBeUndefined();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('resolves a custom alias prefix (not @/)', async () => {
|
|
140
|
+
await mkdir(join(tmpDir, 'src'), { recursive: true });
|
|
141
|
+
const file = join(tmpDir, 'src', 'Widget.vue');
|
|
142
|
+
|
|
143
|
+
await writeFile(file, `<template><div /></template>`);
|
|
144
|
+
await writeTsconfig({ compilerOptions: { paths: { '~/*': ['src/*'] } } });
|
|
145
|
+
|
|
146
|
+
const result = await resolveAliasedIsland('~/Widget', tmpDir);
|
|
147
|
+
|
|
148
|
+
expect(result).toBe(file.replace(/\\/g, '/'));
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { access } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { parse } from 'tsconfck';
|
|
4
|
+
|
|
5
|
+
// Islands embedded in `.astro` components are frequently imported through a
|
|
6
|
+
// tsconfig path alias (e.g. `@/components/Counter`). The raw aliased specifier
|
|
7
|
+
// is baked into `<astro-island component-url>` and `import()`d verbatim, so it
|
|
8
|
+
// must be turned into an on-disk path before it can hydrate. This helper is
|
|
9
|
+
// used as a last resort after the existing resolution has already had its
|
|
10
|
+
// chance.
|
|
11
|
+
|
|
12
|
+
const ALIAS_EXTS = ['.tsx', '.ts', '.jsx', '.js', '.vue', '.svelte', '.mts', '.mjs'];
|
|
13
|
+
|
|
14
|
+
async function isFile(candidate: string): Promise<boolean> {
|
|
15
|
+
try {
|
|
16
|
+
await access(candidate);
|
|
17
|
+
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resolves a tsconfig-aliased island specifier (e.g. `@/components/Counter`)
|
|
26
|
+
* to an absolute on-disk file path, or `undefined` when it is not a tsconfig
|
|
27
|
+
* alias or no matching file exists. Already-resolvable specifiers are skipped
|
|
28
|
+
* so this runs only as a genuine last resort.
|
|
29
|
+
*/
|
|
30
|
+
export async function resolveAliasedIsland(
|
|
31
|
+
specifier: string,
|
|
32
|
+
resolveFrom: string
|
|
33
|
+
): Promise<string | undefined> {
|
|
34
|
+
// Skip specifiers the existing resolution can already handle.
|
|
35
|
+
if (
|
|
36
|
+
!specifier ||
|
|
37
|
+
specifier.startsWith('./') ||
|
|
38
|
+
specifier.startsWith('../') ||
|
|
39
|
+
specifier.startsWith('/') ||
|
|
40
|
+
specifier.startsWith('\0') ||
|
|
41
|
+
specifier.startsWith('virtual:') ||
|
|
42
|
+
specifier.startsWith('astro:') ||
|
|
43
|
+
specifier.startsWith('@astrojs/') ||
|
|
44
|
+
specifier.startsWith('@id/') ||
|
|
45
|
+
specifier.startsWith('/@fs/')
|
|
46
|
+
) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let result;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
result = await parse(resolve(resolveFrom, 'tsconfig.json'));
|
|
54
|
+
} catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const compilerOptions = result.tsconfig?.compilerOptions ?? {};
|
|
59
|
+
const paths: Record<string, string[]> = compilerOptions.paths ?? {};
|
|
60
|
+
|
|
61
|
+
if (Object.keys(paths).length === 0) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// tsconfig paths resolve relative to baseUrl when present, otherwise the
|
|
66
|
+
// tsconfig directory (which tsconfck gives us as the tsconfig file's dir).
|
|
67
|
+
const tsconfigDir = result.tsconfigFile
|
|
68
|
+
? resolve(result.tsconfigFile, '..')
|
|
69
|
+
: resolveFrom;
|
|
70
|
+
const root = compilerOptions.baseUrl
|
|
71
|
+
? resolve(tsconfigDir, compilerOptions.baseUrl)
|
|
72
|
+
: tsconfigDir;
|
|
73
|
+
|
|
74
|
+
for (const [pattern, targets] of Object.entries(paths)) {
|
|
75
|
+
const prefix = pattern.replace(/\*$/, '');
|
|
76
|
+
|
|
77
|
+
if (!specifier.startsWith(prefix)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const rest = specifier.slice(prefix.length);
|
|
82
|
+
|
|
83
|
+
for (const target of targets) {
|
|
84
|
+
const resolvedTarget = target.replace(/\*$/, '');
|
|
85
|
+
const base = resolve(root, resolvedTarget, rest);
|
|
86
|
+
const candidates = [base, ...ALIAS_EXTS.map((ext) => `${base}${ext}`)];
|
|
87
|
+
|
|
88
|
+
for (const candidate of candidates) {
|
|
89
|
+
if (await isFile(candidate)) {
|
|
90
|
+
return candidate.replace(/\\/g, '/');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { loadConfigFromFile } from 'vite';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import type { AstroIntegration } from 'astro';
|
|
5
|
+
|
|
6
|
+
const CONFIG_FILENAMES = [
|
|
7
|
+
'astro.config.ts',
|
|
8
|
+
'astro.config.mjs',
|
|
9
|
+
'astro.config.js',
|
|
10
|
+
'astro.config.cjs',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Loads integrations declared in the user's astro.config.* so that any Vite
|
|
15
|
+
* plugins they register (e.g. astro-icon's virtual:astro-icon resolver) are
|
|
16
|
+
* present in both the main Storybook Vite server and the internal Astro SSR
|
|
17
|
+
* server. Returns an empty array on any failure so the calling code can
|
|
18
|
+
* continue with only the framework-level integrations.
|
|
19
|
+
*/
|
|
20
|
+
export async function loadUserAstroIntegrations(resolveFrom: string): Promise<AstroIntegration[]> {
|
|
21
|
+
const configFile = CONFIG_FILENAMES.find(name => existsSync(resolve(resolveFrom, name)));
|
|
22
|
+
|
|
23
|
+
if (!configFile) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const result = await loadConfigFromFile(
|
|
29
|
+
{ command: 'serve', mode: 'development' },
|
|
30
|
+
configFile,
|
|
31
|
+
resolveFrom
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (!result?.config) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const config = result.config as { integrations?: unknown };
|
|
39
|
+
const raw = config.integrations;
|
|
40
|
+
|
|
41
|
+
if (!raw) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Astro allows nested arrays from conditional spreads (e.g. ...whenX(() => mdx()))
|
|
46
|
+
const flat = (Array.isArray(raw) ? raw : [raw]).flat(Infinity);
|
|
47
|
+
|
|
48
|
+
return flat.filter(
|
|
49
|
+
(i): i is AstroIntegration => Boolean(i) && typeof i === 'object' && 'name' in i && 'hooks' in i
|
|
50
|
+
);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.warn(
|
|
53
|
+
'[storybook-astro] Could not load astro.config to discover integrations:',
|
|
54
|
+
err instanceof Error ? err.message : String(err)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/middleware.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { ensureAstroPassthroughImageService } from './astroImageService.ts';
|
|
|
6
6
|
import { createAstroRenderHandler, type HandlerProps } from './astroRenderHandler.ts';
|
|
7
7
|
import type { Integration } from './integrations/index.ts';
|
|
8
8
|
import type { SanitizationOptions } from './lib/sanitization.ts';
|
|
9
|
+
import { resolveAliasedIsland } from './lib/resolve-aliased-island.ts';
|
|
9
10
|
import { resolveStoryModuleMock } from './module-mocks.ts';
|
|
10
11
|
import { addRenderers, resolveClientModules } from 'virtual:astro-container-renderers';
|
|
11
12
|
|
|
@@ -18,6 +19,7 @@ type HandlerFactoryOptions = {
|
|
|
18
19
|
loadModule?: (id: string) => Promise<{ default: unknown }>;
|
|
19
20
|
invalidateModuleGraph?: () => void;
|
|
20
21
|
resolveModule?: (specifier: string) => string | undefined;
|
|
22
|
+
resolveFrom?: string;
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
export type { HandlerProps };
|
|
@@ -52,6 +54,19 @@ export async function handlerFactory(
|
|
|
52
54
|
return resolution;
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
// Last resort: an island imported via a tsconfig path alias (e.g. `@/...`)
|
|
58
|
+
// has its raw aliased specifier baked into the island's component-url.
|
|
59
|
+
// Resolve it to an on-disk file and hand back a `/@fs/` URL the dev Vite
|
|
60
|
+
// server can serve so the island still hydrates.
|
|
61
|
+
const aliasedIsland = await resolveAliasedIsland(
|
|
62
|
+
specifier,
|
|
63
|
+
options?.resolveFrom ?? process.cwd()
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (aliasedIsland) {
|
|
67
|
+
return `/@fs/${aliasedIsland}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
55
70
|
return specifier;
|
|
56
71
|
}
|
|
57
72
|
});
|
|
@@ -2,6 +2,7 @@ import { resolve } from 'node:path';
|
|
|
2
2
|
import { createAstroRenderHandler, type HandlerProps } from './astroRenderHandler.ts';
|
|
3
3
|
import type { Integration } from './integrations/index.ts';
|
|
4
4
|
import type { SanitizationOptions } from './lib/sanitization.ts';
|
|
5
|
+
import type { FrameworkOptions } from './types.ts';
|
|
5
6
|
import {
|
|
6
7
|
createClientModuleResolver,
|
|
7
8
|
createProductionAstroContainer,
|
|
@@ -34,6 +35,7 @@ type ProductionRenderRuntimeOptions = {
|
|
|
34
35
|
trackedSpecifiers: Set<string>;
|
|
35
36
|
resolveFrom: string;
|
|
36
37
|
resolveComponentId?: (id: string) => string;
|
|
38
|
+
fonts?: FrameworkOptions['fonts'];
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
/** Creates the shared SSR runtime used by both build-time prerendering and the standalone render server. */
|
|
@@ -43,7 +45,8 @@ export async function createProductionRenderRuntime(
|
|
|
43
45
|
const viteServer = await createStorySsrViteServer({
|
|
44
46
|
integrations: options.integrations,
|
|
45
47
|
trackedSpecifiers: options.trackedSpecifiers,
|
|
46
|
-
resolveFrom: options.resolveFrom
|
|
48
|
+
resolveFrom: options.resolveFrom,
|
|
49
|
+
fonts: options.fonts
|
|
47
50
|
});
|
|
48
51
|
|
|
49
52
|
try {
|
|
@@ -58,7 +61,8 @@ export async function createProductionRenderRuntime(
|
|
|
58
61
|
const astroContainer = await createProductionAstroContainer({
|
|
59
62
|
integrations: options.integrations,
|
|
60
63
|
resolveClientModule,
|
|
61
|
-
viteServer
|
|
64
|
+
viteServer,
|
|
65
|
+
resolveFrom: options.resolveFrom
|
|
62
66
|
});
|
|
63
67
|
|
|
64
68
|
const loadModule = async (moduleId: string) => {
|
package/src/storySsrVite.ts
CHANGED
|
@@ -4,9 +4,11 @@ import type { experimental_AstroContainer as AstroContainer } from 'astro/contai
|
|
|
4
4
|
import { ensureAstroPassthroughImageService } from './astroImageService.ts';
|
|
5
5
|
import { importAstroConfig } from './importAstroConfig.ts';
|
|
6
6
|
import type { Integration } from './integrations/index.ts';
|
|
7
|
+
import { resolveAliasedIsland } from './lib/resolve-aliased-island.ts';
|
|
7
8
|
import { ssrLoadModuleWithFsFallback } from './lib/ssr-load-module-with-fs-fallback.ts';
|
|
8
9
|
import { resolveStoryModuleMock } from './module-mocks.ts';
|
|
9
|
-
import {
|
|
10
|
+
import type { FrameworkOptions } from './types.ts';
|
|
11
|
+
import { vitePluginAstroFonts } from './vitePluginAstroFonts.ts';
|
|
10
12
|
import { vitePluginAstroIntegrationOptsFallback } from './vitePluginAstroIntegrationOptsFallback.ts';
|
|
11
13
|
import { vitePluginAstroRoutesFallback } from './vitePluginAstroRoutesFallback.ts';
|
|
12
14
|
import { vitePluginAstroVueFallback } from './vitePluginAstroVueFallback.ts';
|
|
@@ -16,6 +18,7 @@ export async function createStorySsrViteServer(options: {
|
|
|
16
18
|
integrations: Integration[];
|
|
17
19
|
trackedSpecifiers: Set<string>;
|
|
18
20
|
resolveFrom: string;
|
|
21
|
+
fonts?: FrameworkOptions['fonts'];
|
|
19
22
|
}) {
|
|
20
23
|
const { getViteConfig, passthroughImageService } = await importAstroConfig(options.resolveFrom);
|
|
21
24
|
const astroConfig = await getViteConfig(
|
|
@@ -44,7 +47,7 @@ export async function createStorySsrViteServer(options: {
|
|
|
44
47
|
},
|
|
45
48
|
plugins: [
|
|
46
49
|
createProjectAstroResolutionPlugin(options.resolveFrom),
|
|
47
|
-
|
|
50
|
+
vitePluginAstroFonts({ fonts: options.fonts, root: options.resolveFrom }),
|
|
48
51
|
vitePluginAstroIntegrationOptsFallback(),
|
|
49
52
|
vitePluginAstroVueFallback(),
|
|
50
53
|
vitePluginAstroRoutesFallback(),
|
|
@@ -107,6 +110,7 @@ export async function createProductionAstroContainer(options: {
|
|
|
107
110
|
integrations: Integration[];
|
|
108
111
|
resolveClientModule: (specifier: string) => string | undefined;
|
|
109
112
|
viteServer: ViteDevServer;
|
|
113
|
+
resolveFrom: string;
|
|
110
114
|
}) {
|
|
111
115
|
ensureAstroPassthroughImageService();
|
|
112
116
|
|
|
@@ -136,6 +140,15 @@ export async function createProductionAstroContainer(options: {
|
|
|
136
140
|
return resolution;
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
// Last resort: an island imported via a tsconfig path alias (e.g. `@/...`)
|
|
144
|
+
// never matches the static map under its raw specifier. Resolve the alias
|
|
145
|
+
// to an on-disk path and look that up in the built module map instead.
|
|
146
|
+
const abs = await resolveAliasedIsland(specifier, options.resolveFrom);
|
|
147
|
+
|
|
148
|
+
if (abs) {
|
|
149
|
+
return options.resolveClientModule(abs) ?? specifier;
|
|
150
|
+
}
|
|
151
|
+
|
|
139
152
|
return specifier;
|
|
140
153
|
}
|
|
141
154
|
});
|
package/src/types.ts
CHANGED
|
@@ -3,10 +3,11 @@ import type { InlineConfig } from 'vite';
|
|
|
3
3
|
import type { Integration } from './integrations/index.ts';
|
|
4
4
|
import type { SanitizationOptions } from './lib/sanitization.ts';
|
|
5
5
|
import type { StoryRulesOptions } from './rules-options.ts';
|
|
6
|
+
import type { StorybookFontFamily } from './vitePluginAstroFonts.ts';
|
|
6
7
|
|
|
7
8
|
type FrameworkName = CompatibleString<'@storybook-astro/framework'>;
|
|
8
9
|
|
|
9
|
-
export type { Integration, SanitizationOptions, StoryRulesOptions };
|
|
10
|
+
export type { Integration, SanitizationOptions, StoryRulesOptions, StorybookFontFamily };
|
|
10
11
|
export type RenderMode = 'server' | 'static';
|
|
11
12
|
|
|
12
13
|
export type ServerBuildOptions = {
|
|
@@ -25,6 +26,13 @@ type BaseFrameworkOptions = {
|
|
|
25
26
|
integrations?: Integration[];
|
|
26
27
|
sanitization?: SanitizationOptions;
|
|
27
28
|
resolveFrom?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Astro font families to resolve and inject as @font-face CSS during story
|
|
31
|
+
* rendering. Pass the same array you have in your `astro.config.ts` under
|
|
32
|
+
* `fonts:`. Currently honored in development; static/server builds fall
|
|
33
|
+
* back to no-op stubs.
|
|
34
|
+
*/
|
|
35
|
+
fonts?: StorybookFontFamily[];
|
|
28
36
|
};
|
|
29
37
|
|
|
30
38
|
type ServerFrameworkOptions = BaseFrameworkOptions & {
|
package/src/vitePluginAstro.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mergeConfig, type InlineConfig } from 'vite';
|
|
2
2
|
import type { Integration } from './integrations/index.ts';
|
|
3
3
|
import { importAstroConfig } from './importAstroConfig.ts';
|
|
4
|
+
import { loadUserAstroIntegrations } from './loadUserAstroConfig.ts';
|
|
4
5
|
|
|
5
6
|
const ASTRO_PLUGINS_THAT_ARE_SUPPOSEDLY_NOT_NEEDED_IN_STORYBOOK = [
|
|
6
7
|
'@astro/plugin-actions',
|
|
@@ -38,13 +39,19 @@ export async function mergeWithAstroConfig(
|
|
|
38
39
|
const { getViteConfig } = await importAstroConfig(resolveFrom);
|
|
39
40
|
const safeIntegrations = integrations ?? [];
|
|
40
41
|
|
|
42
|
+
const frameworkIntegrations = await Promise.all(
|
|
43
|
+
safeIntegrations.map((integration) => integration.loadIntegration(resolveFrom))
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const userIntegrations = await loadUserAstroIntegrations(resolveFrom);
|
|
47
|
+
const frameworkNames = new Set(frameworkIntegrations.map(i => i.name));
|
|
48
|
+
const extraIntegrations = userIntegrations.filter(i => !frameworkNames.has(i.name));
|
|
49
|
+
|
|
41
50
|
const astroConfig = await getViteConfig(
|
|
42
51
|
{},
|
|
43
52
|
{
|
|
44
53
|
configFile: false,
|
|
45
|
-
integrations:
|
|
46
|
-
safeIntegrations.map((integration) => integration.loadIntegration(resolveFrom))
|
|
47
|
-
)
|
|
54
|
+
integrations: [...frameworkIntegrations, ...extraIntegrations]
|
|
48
55
|
}
|
|
49
56
|
)({
|
|
50
57
|
mode,
|
|
@@ -138,6 +138,7 @@ export function vitePluginAstroBuildPrerender(options: FrameworkOptions): Plugin
|
|
|
138
138
|
staticCssMap,
|
|
139
139
|
trackedSpecifiers,
|
|
140
140
|
resolveFrom,
|
|
141
|
+
fonts: options.fonts,
|
|
141
142
|
bundle
|
|
142
143
|
});
|
|
143
144
|
|
|
@@ -162,6 +163,7 @@ async function prerenderAstroStories(options: {
|
|
|
162
163
|
staticCssMap: StaticCssMap;
|
|
163
164
|
trackedSpecifiers: Set<string>;
|
|
164
165
|
resolveFrom: string;
|
|
166
|
+
fonts?: FrameworkOptions['fonts'];
|
|
165
167
|
bundle: Rollup.OutputBundle;
|
|
166
168
|
}) {
|
|
167
169
|
const runtime = await createProductionRenderRuntime({
|
|
@@ -170,7 +172,8 @@ async function prerenderAstroStories(options: {
|
|
|
170
172
|
storyRulesConfigFilePath: options.storyRulesConfigFilePath,
|
|
171
173
|
staticModuleMap: options.staticModuleMap,
|
|
172
174
|
trackedSpecifiers: options.trackedSpecifiers,
|
|
173
|
-
resolveFrom: options.resolveFrom
|
|
175
|
+
resolveFrom: options.resolveFrom,
|
|
176
|
+
fonts: options.fonts
|
|
174
177
|
});
|
|
175
178
|
const assetPathMap = buildAssetPathMap(options.bundle);
|
|
176
179
|
|