@storybook-astro/framework 1.5.0-canary.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5MNCPWIU.js → chunk-6RIGYMZP.js} +49 -28
- package/dist/chunk-6RIGYMZP.js.map +1 -0
- package/dist/preset.js +1 -1
- package/dist/vitest/index.js +1 -1
- package/package.json +2 -2
- package/src/vitePluginAstroComponentMarker.test.ts +55 -2
- package/src/vitePluginAstroComponentMarker.ts +107 -50
- package/dist/chunk-5MNCPWIU.js.map +0 -1
|
@@ -17,7 +17,7 @@ function vitePluginAstroComponentMarker() {
|
|
|
17
17
|
return null;
|
|
18
18
|
}
|
|
19
19
|
const moduleId = id;
|
|
20
|
-
const styleCode = isBuild ? generateInlineStyles(moduleId) :
|
|
20
|
+
const styleCode = isBuild ? generateInlineStyles(moduleId) : generateHybridStyles(moduleId);
|
|
21
21
|
return {
|
|
22
22
|
code: `
|
|
23
23
|
${styleCode}
|
|
@@ -33,22 +33,53 @@ export default __astro_component;
|
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
function
|
|
36
|
+
function generateHybridStyles(filePath) {
|
|
37
37
|
try {
|
|
38
38
|
const source = readFileSync(filePath, "utf-8");
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
const inlinedCss = extractStyleBlocksWithLang(source).map((block, i) => {
|
|
40
|
+
if (block.lang && block.lang !== "css") {
|
|
41
|
+
return warnUnsupportedStyleLang(filePath, block.lang);
|
|
42
|
+
}
|
|
43
|
+
return styleInjectionSnippet(
|
|
44
|
+
"data-astro-dev",
|
|
45
|
+
`${filePath}:${i}`,
|
|
46
|
+
unwrapGlobalSelectors(block.css)
|
|
47
|
+
);
|
|
48
|
+
}).join("\n");
|
|
44
49
|
const childImports = extractAstroImportSpecifiers(source).map(
|
|
45
50
|
(specifier) => `import ${JSON.stringify(specifier)};`
|
|
46
51
|
);
|
|
47
|
-
return [
|
|
52
|
+
return [inlinedCss, ...childImports].filter(Boolean).join("\n");
|
|
48
53
|
} catch {
|
|
49
54
|
return "";
|
|
50
55
|
}
|
|
51
56
|
}
|
|
57
|
+
function unwrapGlobalSelectors(css) {
|
|
58
|
+
return css.replace(/:global\(\s*([^)]*?)\s*\)/g, "$1");
|
|
59
|
+
}
|
|
60
|
+
function warnUnsupportedStyleLang(filePath, lang) {
|
|
61
|
+
const message = `[storybook-astro] Skipping <style lang="${lang}"> in ${filePath}: preprocessed styles are not supported in Storybook dev mode.`;
|
|
62
|
+
return `
|
|
63
|
+
(function() {
|
|
64
|
+
if (typeof console !== 'undefined') { console.warn(${JSON.stringify(message)}); }
|
|
65
|
+
})();`;
|
|
66
|
+
}
|
|
67
|
+
function styleInjectionSnippet(markerAttr, markerValue, css) {
|
|
68
|
+
const attr = JSON.stringify(markerAttr);
|
|
69
|
+
const value = JSON.stringify(markerValue);
|
|
70
|
+
return `
|
|
71
|
+
(function() {
|
|
72
|
+
if (typeof document === 'undefined') { return; }
|
|
73
|
+
const tagged = document.head.querySelectorAll('style[' + ${attr} + ']');
|
|
74
|
+
for (const node of tagged) {
|
|
75
|
+
if (node.getAttribute(${attr}) === ${value}) { return; }
|
|
76
|
+
}
|
|
77
|
+
const style = document.createElement('style');
|
|
78
|
+
style.setAttribute(${attr}, ${value});
|
|
79
|
+
style.textContent = ${JSON.stringify(css)};
|
|
80
|
+
document.head.appendChild(style);
|
|
81
|
+
})();`;
|
|
82
|
+
}
|
|
52
83
|
function extractAstroImportSpecifiers(source) {
|
|
53
84
|
const frontmatterMatch = source.match(/^---([\s\S]*?)---/m);
|
|
54
85
|
if (!frontmatterMatch) {
|
|
@@ -68,18 +99,9 @@ function generateInlineStyles(filePath) {
|
|
|
68
99
|
if (cssBlocks.length === 0) {
|
|
69
100
|
return "";
|
|
70
101
|
}
|
|
71
|
-
return cssBlocks.map(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
(function() {
|
|
75
|
-
if (typeof document !== 'undefined') {
|
|
76
|
-
const style = document.createElement('style');
|
|
77
|
-
style.setAttribute('data-astro-build', ${JSON.stringify(file + ":" + i)});
|
|
78
|
-
style.textContent = ${escaped};
|
|
79
|
-
document.head.appendChild(style);
|
|
80
|
-
}
|
|
81
|
-
})();`;
|
|
82
|
-
}).join("\n");
|
|
102
|
+
return cssBlocks.map(
|
|
103
|
+
({ file, css }, i) => styleInjectionSnippet("data-astro-build", `${file}:${i}`, unwrapGlobalSelectors(css))
|
|
104
|
+
).join("\n");
|
|
83
105
|
}
|
|
84
106
|
function collectStyleBlocks(filePath, visited) {
|
|
85
107
|
if (visited.has(filePath)) {
|
|
@@ -102,22 +124,21 @@ function collectStyleBlocks(filePath, visited) {
|
|
|
102
124
|
return blocks;
|
|
103
125
|
}
|
|
104
126
|
function extractStyleBlocks(source) {
|
|
127
|
+
return extractStyleBlocksWithLang(source).map((block) => block.css);
|
|
128
|
+
}
|
|
129
|
+
function extractStyleBlocksWithLang(source) {
|
|
105
130
|
const withoutFrontmatter = source.replace(/^---[\s\S]*?---/m, "");
|
|
106
131
|
const blocks = [];
|
|
107
|
-
const regex = /<style(?:\s[^>]*)
|
|
132
|
+
const regex = /<style((?:\s[^>]*)?)>([\s\S]*?)<\/style>/g;
|
|
108
133
|
let match;
|
|
109
134
|
while ((match = regex.exec(withoutFrontmatter)) !== null) {
|
|
110
|
-
|
|
135
|
+
const langMatch = match[1].match(/\blang\s*=\s*['"]?([\w-]+)/);
|
|
136
|
+
blocks.push({ css: match[2].trim(), lang: langMatch ? langMatch[1] : null });
|
|
111
137
|
}
|
|
112
138
|
return blocks;
|
|
113
139
|
}
|
|
114
|
-
function countStyleBlocks(source) {
|
|
115
|
-
const withoutFrontmatter = source.replace(/^---[\s\S]*?---/m, "");
|
|
116
|
-
const matches = withoutFrontmatter.match(/<style(\s|>)/g);
|
|
117
|
-
return matches ? matches.length : 0;
|
|
118
|
-
}
|
|
119
140
|
|
|
120
141
|
export {
|
|
121
142
|
vitePluginAstroComponentMarker
|
|
122
143
|
};
|
|
123
|
-
//# sourceMappingURL=chunk-
|
|
144
|
+
//# sourceMappingURL=chunk-6RIGYMZP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vitePluginAstroComponentMarker.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport type { PluginOption } from 'vite';\n\n/**\n * Vite plugin that patches Astro 6's client-side .astro file transforms for Storybook.\n *\n * In Astro 6, the client-side transform of .astro files produces a stub function that\n * throws \"Astro components cannot be used in the browser\" without setting the\n * `isAstroComponentFactory` marker. Storybook's renderer relies on this marker to detect\n * Astro components and route them to server-side rendering via the Container API.\n *\n * This plugin also preserves the component's scoped CSS by importing the style sub-modules\n * that the Astro Vite plugin exposes. Without this, the client-side stub would strip all\n * CSS since Astro 6 no longer includes style imports in client-side .astro transforms.\n *\n * During builds, Astro's compile metadata cache is not populated for client-side transforms,\n * so style sub-module imports would fail. Instead, raw CSS is extracted directly from the\n * .astro source and inlined.\n */\nexport function vitePluginAstroComponentMarker(): PluginOption {\n let isBuild = false;\n\n return {\n name: 'storybook-astro-component-marker',\n enforce: 'post',\n\n configResolved(config) {\n isBuild = config.command === 'build';\n },\n\n transform(code: string, id: string) {\n // Only process main .astro modules (not sub-modules like ?astro&type=style)\n if (!id.endsWith('.astro')) {return null;}\n\n // Detect the Astro 6 client-side stub pattern\n if (!code.includes('Astro components cannot be used in the browser')) {return null;}\n\n const moduleId = id;\n\n // In Storybook's Vite 6 setup with separate client/SSR environments, Astro's\n // CSS cache isn't populated for client-side transforms. CSS sub-module imports\n // fail with \"No Astro CSS at index N\", so we inline the CSS directly.\n // However, we still import child .astro components to bring them into the module\n // graph so the plugin processes them (fix for issue #114).\n const styleCode = isBuild\n ? generateInlineStyles(moduleId)\n : generateHybridStyles(moduleId);\n\n return {\n code: `\n${styleCode}\nconst __astro_component = () => {\n throw new Error('Astro components are rendered server-side by Storybook.');\n};\n__astro_component.isAstroComponentFactory = true;\n__astro_component.moduleId = ${JSON.stringify(moduleId)};\nexport default __astro_component;\n`,\n map: null,\n };\n },\n };\n}\n\n/**\n * Hybrid approach for dev mode: inline CSS for the current component (to avoid\n * Astro's cache issues) but import child .astro components (to bring them into\n * the module graph for processing). This preserves the fix for issue #114 while\n * avoiding \"No Astro CSS at index N\" errors.\n *\n * Two caveats follow from inlining raw <style> source instead of routing it\n * through Astro's compiler:\n * - Styles are injected globally, not scoped to the component. Plain class\n * selectors still match the SSR-rendered HTML, but there is no scope isolation\n * between components in dev.\n * - `:global(...)` wrappers are unwrapped (the browser can't parse them), and\n * preprocessed blocks (`<style lang=\"scss\">` etc.) are skipped with a warning\n * since the preprocessor isn't reachable here.\n */\nfunction generateHybridStyles(filePath: string): string {\n try {\n const source = readFileSync(filePath, 'utf-8');\n\n // Inline this component's own CSS (no recursion into children).\n const inlinedCss = extractStyleBlocksWithLang(source).map((block, i) => {\n if (block.lang && block.lang !== 'css') {\n return warnUnsupportedStyleLang(filePath, block.lang);\n }\n\n return styleInjectionSnippet(\n 'data-astro-dev',\n `${filePath}:${i}`,\n unwrapGlobalSelectors(block.css)\n );\n }).join('\\n');\n\n // Import child .astro components so they enter the module graph\n const childImports = extractAstroImportSpecifiers(source).map(\n (specifier) => `import ${JSON.stringify(specifier)};`\n );\n\n return [inlinedCss, ...childImports].filter(Boolean).join('\\n');\n } catch {\n return '';\n }\n}\n\n/**\n * Astro's `:global(...)` wrapper marks a selector as un-scoped. Dev-mode styles\n * are already injected globally, so the wrapper carries no meaning here — and the\n * browser treats `:global()` as an invalid pseudo-class and drops the whole rule.\n * Unwrap it to its inner selector so the rule applies.\n */\nfunction unwrapGlobalSelectors(css: string): string {\n return css.replace(/:global\\(\\s*([^)]*?)\\s*\\)/g, '$1');\n}\n\n/**\n * Builds a snippet that warns about a preprocessed <style> block we can't inline.\n * Astro's compiler (which would turn scss/less/etc. into CSS) isn't reachable\n * from this client-side transform, and shipping the raw source would break the\n * browser's CSS parser, so we surface a clear console warning instead.\n */\nfunction warnUnsupportedStyleLang(filePath: string, lang: string): string {\n const message =\n `[storybook-astro] Skipping <style lang=\"${lang}\"> in ${filePath}: ` +\n `preprocessed styles are not supported in Storybook dev mode.`;\n\n return `\n(function() {\n if (typeof console !== 'undefined') { console.warn(${JSON.stringify(message)}); }\n})();`;\n}\n\n/**\n * Builds a self-executing snippet that injects a CSS string into <head> as a\n * <style> tag, tagged with a marker attribute. The marker also dedupes\n * re-injection (e.g. when a module re-evaluates after HMR) so styles don't\n * accumulate in the document.\n */\nfunction styleInjectionSnippet(markerAttr: string, markerValue: string, css: string): string {\n const attr = JSON.stringify(markerAttr);\n const value = JSON.stringify(markerValue);\n\n return `\n(function() {\n if (typeof document === 'undefined') { return; }\n const tagged = document.head.querySelectorAll('style[' + ${attr} + ']');\n for (const node of tagged) {\n if (node.getAttribute(${attr}) === ${value}) { return; }\n }\n const style = document.createElement('style');\n style.setAttribute(${attr}, ${value});\n style.textContent = ${JSON.stringify(css)};\n document.head.appendChild(style);\n})();`;\n}\n\n/**\n * Extracts import specifiers ending in `.astro` from a component's frontmatter.\n * Comments are stripped first so commented-out imports don't resurface as\n * broken module requests in the browser.\n */\nexport function extractAstroImportSpecifiers(source: string): string[] {\n const frontmatterMatch = source.match(/^---([\\s\\S]*?)---/m);\n\n if (!frontmatterMatch) {return [];}\n\n const frontmatter = frontmatterMatch[1]\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/\\/\\/[^\\n]*/g, '');\n\n // Matches static imports (`import Child from './Child.astro'`), side-effect\n // imports (`import './Child.astro'`), and dynamic imports (`import('./Child.astro')`).\n const importPattern = /(?:from|import)\\s*\\(?\\s*['\"]([^'\"]+\\.astro)['\"]/g;\n const specifiers = new Set<string>();\n let match;\n\n while ((match = importPattern.exec(frontmatter)) !== null) {\n specifiers.add(match[1]);\n }\n\n return [...specifiers];\n}\n\n/**\n * Reads the original .astro source file and generates a JS snippet that injects\n * the raw CSS from each <style> block into the document, recursing into child\n * .astro components imported via relative paths. Used during builds where\n * Astro's compile metadata cache is unavailable.\n *\n * The CSS is unscoped (no Astro scoping transforms), so `:global(...)` wrappers\n * are unwrapped here too — without that the browser drops the whole rule (e.g.\n * PageCard's `.page-card__image-wrapper :global(img)` sizing).\n */\nfunction generateInlineStyles(filePath: string): string {\n const cssBlocks = collectStyleBlocks(filePath, new Set());\n\n if (cssBlocks.length === 0) {return '';}\n\n // Create a side-effect that injects styles into the document\n return cssBlocks\n .map(({ file, css }, i) =>\n styleInjectionSnippet('data-astro-build', `${file}:${i}`, unwrapGlobalSelectors(css))\n )\n .join('\\n');\n}\n\n/**\n * Collects <style> block contents from a component and its child .astro imports.\n * Only relative specifiers are followed (aliases and packages can't be resolved\n * from disk here). The visited set guards against import cycles.\n */\nfunction collectStyleBlocks(\n filePath: string,\n visited: Set<string>\n): Array<{ file: string; css: string }> {\n if (visited.has(filePath)) {return [];}\n visited.add(filePath);\n\n let source: string;\n\n try {\n source = readFileSync(filePath, 'utf-8');\n } catch {\n return [];\n }\n\n const blocks = extractStyleBlocks(source).map((css) => ({ file: filePath, css }));\n\n for (const specifier of extractAstroImportSpecifiers(source)) {\n if (!specifier.startsWith('.')) {continue;}\n\n blocks.push(...collectStyleBlocks(resolve(dirname(filePath), specifier), visited));\n }\n\n return blocks;\n}\n\n/**\n * Extracts the content of all top-level <style> blocks from an Astro component's source.\n * Strips frontmatter before parsing.\n */\nfunction extractStyleBlocks(source: string): string[] {\n return extractStyleBlocksWithLang(source).map((block) => block.css);\n}\n\n/**\n * Like {@link extractStyleBlocks}, but also reports each block's `lang` attribute\n * (e.g. \"scss\") so callers can tell preprocessed styles apart from plain CSS.\n * Returns `null` for the lang when no attribute is present.\n */\nfunction extractStyleBlocksWithLang(source: string): Array<{ css: string; lang: string | null }> {\n const withoutFrontmatter = source.replace(/^---[\\s\\S]*?---/m, '');\n const blocks: Array<{ css: string; lang: string | null }> = [];\n const regex = /<style((?:\\s[^>]*)?)>([\\s\\S]*?)<\\/style>/g;\n let match;\n\n while ((match = regex.exec(withoutFrontmatter)) !== null) {\n const langMatch = match[1].match(/\\blang\\s*=\\s*['\"]?([\\w-]+)/);\n\n blocks.push({ css: match[2].trim(), lang: langMatch ? langMatch[1] : null });\n }\n\n return blocks;\n}\n"],"mappings":"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","names":[]}
|
package/dist/preset.js
CHANGED
package/dist/vitest/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storybook-astro/framework",
|
|
3
|
-
"version": "1.5.0
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Community-supported Storybook framework for Astro 5 & 6 components",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
}
|
|
146
146
|
},
|
|
147
147
|
"dependencies": {
|
|
148
|
-
"@storybook-astro/renderer": "1.5.0
|
|
148
|
+
"@storybook-astro/renderer": "1.5.0",
|
|
149
149
|
"hono": "^4.11.12",
|
|
150
150
|
"sanitize-html": "^2.17.0",
|
|
151
151
|
"tsconfck": "^3.1.6",
|
|
@@ -58,7 +58,7 @@ describe('vitePluginAstroComponentMarker transform', () => {
|
|
|
58
58
|
expect(result?.code).toContain(JSON.stringify(filePath));
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
test('
|
|
61
|
+
test('inlines CSS for own <style> blocks in dev mode (hybrid approach)', () => {
|
|
62
62
|
const filePath = writeAstroFile(
|
|
63
63
|
'Styled.astro',
|
|
64
64
|
'<div class="a">Hi</div>\n<style>.a { color: red; }</style>'
|
|
@@ -66,7 +66,48 @@ describe('vitePluginAstroComponentMarker transform', () => {
|
|
|
66
66
|
const plugin = createPlugin();
|
|
67
67
|
const result = plugin.transform(ASTRO6_CLIENT_STUB, filePath);
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
// Dev mode uses inline CSS instead of sub-module imports to avoid Astro cache issues
|
|
70
|
+
expect(result?.code).toContain('.a { color: red; }');
|
|
71
|
+
expect(result?.code).toContain('data-astro-dev');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('unwraps :global() selectors so the CSS is valid in the browser', () => {
|
|
75
|
+
const filePath = writeAstroFile(
|
|
76
|
+
'Global.astro',
|
|
77
|
+
'<div class="wrap"><slot /></div>\n<style>.wrap > :global(img) { width: 100%; }</style>'
|
|
78
|
+
);
|
|
79
|
+
const plugin = createPlugin();
|
|
80
|
+
const result = plugin.transform(ASTRO6_CLIENT_STUB, filePath);
|
|
81
|
+
|
|
82
|
+
expect(result?.code).toContain('.wrap > img { width: 100%; }');
|
|
83
|
+
expect(result?.code).not.toContain(':global(');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('skips preprocessed <style lang="..."> blocks with a console warning in dev mode', () => {
|
|
87
|
+
const filePath = writeAstroFile(
|
|
88
|
+
'Scss.astro',
|
|
89
|
+
'<div class="a">Hi</div>\n<style lang="scss">.a { .b { color: red; } }</style>'
|
|
90
|
+
);
|
|
91
|
+
const plugin = createPlugin();
|
|
92
|
+
const result = plugin.transform(ASTRO6_CLIENT_STUB, filePath);
|
|
93
|
+
|
|
94
|
+
// The raw SCSS source must not be injected as a stylesheet.
|
|
95
|
+
expect(result?.code).not.toContain('document.createElement');
|
|
96
|
+
expect(result?.code).toContain('console.warn');
|
|
97
|
+
expect(result?.code).toContain('scss');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('dedupes injected styles so repeated module evaluation does not pile up', () => {
|
|
101
|
+
const filePath = writeAstroFile(
|
|
102
|
+
'Dedupe.astro',
|
|
103
|
+
'<div class="a">Hi</div>\n<style>.a { color: red; }</style>'
|
|
104
|
+
);
|
|
105
|
+
const plugin = createPlugin();
|
|
106
|
+
const result = plugin.transform(ASTRO6_CLIENT_STUB, filePath);
|
|
107
|
+
|
|
108
|
+
// The injection snippet bails out if a style with the same marker already exists.
|
|
109
|
+
expect(result?.code).toContain("getAttribute");
|
|
110
|
+
expect(result?.code).toContain('data-astro-dev');
|
|
70
111
|
});
|
|
71
112
|
|
|
72
113
|
test('re-imports child .astro components so their scoped styles load in dev mode', () => {
|
|
@@ -110,6 +151,18 @@ describe('vitePluginAstroComponentMarker transform', () => {
|
|
|
110
151
|
expect(result?.code).toContain('.child { color: red; }');
|
|
111
152
|
});
|
|
112
153
|
|
|
154
|
+
test('unwraps :global() selectors in build mode too', () => {
|
|
155
|
+
const filePath = writeAstroFile(
|
|
156
|
+
'build/Global.astro',
|
|
157
|
+
'<div class="wrap"><slot /></div>\n<style>.wrap :global(img) { width: 100%; }</style>'
|
|
158
|
+
);
|
|
159
|
+
const plugin = createPlugin('build');
|
|
160
|
+
const result = plugin.transform(ASTRO6_CLIENT_STUB, filePath);
|
|
161
|
+
|
|
162
|
+
expect(result?.code).toContain('.wrap img { width: 100%; }');
|
|
163
|
+
expect(result?.code).not.toContain(':global(');
|
|
164
|
+
});
|
|
165
|
+
|
|
113
166
|
test('handles circular child imports in build mode without recursing forever', () => {
|
|
114
167
|
const aPath = writeAstroFile(
|
|
115
168
|
'cycle/A.astro',
|
|
@@ -38,13 +38,14 @@ export function vitePluginAstroComponentMarker(): PluginOption {
|
|
|
38
38
|
|
|
39
39
|
const moduleId = id;
|
|
40
40
|
|
|
41
|
-
// In
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
41
|
+
// In Storybook's Vite 6 setup with separate client/SSR environments, Astro's
|
|
42
|
+
// CSS cache isn't populated for client-side transforms. CSS sub-module imports
|
|
43
|
+
// fail with "No Astro CSS at index N", so we inline the CSS directly.
|
|
44
|
+
// However, we still import child .astro components to bring them into the module
|
|
45
|
+
// graph so the plugin processes them (fix for issue #114).
|
|
45
46
|
const styleCode = isBuild
|
|
46
47
|
? generateInlineStyles(moduleId)
|
|
47
|
-
:
|
|
48
|
+
: generateHybridStyles(moduleId);
|
|
48
49
|
|
|
49
50
|
return {
|
|
50
51
|
code: `
|
|
@@ -63,32 +64,99 @@ export default __astro_component;
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
67
|
+
* Hybrid approach for dev mode: inline CSS for the current component (to avoid
|
|
68
|
+
* Astro's cache issues) but import child .astro components (to bring them into
|
|
69
|
+
* the module graph for processing). This preserves the fix for issue #114 while
|
|
70
|
+
* avoiding "No Astro CSS at index N" errors.
|
|
68
71
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
72
|
+
* Two caveats follow from inlining raw <style> source instead of routing it
|
|
73
|
+
* through Astro's compiler:
|
|
74
|
+
* - Styles are injected globally, not scoped to the component. Plain class
|
|
75
|
+
* selectors still match the SSR-rendered HTML, but there is no scope isolation
|
|
76
|
+
* between components in dev.
|
|
77
|
+
* - `:global(...)` wrappers are unwrapped (the browser can't parse them), and
|
|
78
|
+
* preprocessed blocks (`<style lang="scss">` etc.) are skipped with a warning
|
|
79
|
+
* since the preprocessor isn't reachable here.
|
|
73
80
|
*/
|
|
74
|
-
function
|
|
81
|
+
function generateHybridStyles(filePath: string): string {
|
|
75
82
|
try {
|
|
76
83
|
const source = readFileSync(filePath, 'utf-8');
|
|
77
|
-
const styleCount = countStyleBlocks(source);
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
// Inline this component's own CSS (no recursion into children).
|
|
86
|
+
const inlinedCss = extractStyleBlocksWithLang(source).map((block, i) => {
|
|
87
|
+
if (block.lang && block.lang !== 'css') {
|
|
88
|
+
return warnUnsupportedStyleLang(filePath, block.lang);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return styleInjectionSnippet(
|
|
92
|
+
'data-astro-dev',
|
|
93
|
+
`${filePath}:${i}`,
|
|
94
|
+
unwrapGlobalSelectors(block.css)
|
|
95
|
+
);
|
|
96
|
+
}).join('\n');
|
|
97
|
+
|
|
98
|
+
// Import child .astro components so they enter the module graph
|
|
82
99
|
const childImports = extractAstroImportSpecifiers(source).map(
|
|
83
100
|
(specifier) => `import ${JSON.stringify(specifier)};`
|
|
84
101
|
);
|
|
85
102
|
|
|
86
|
-
return [
|
|
103
|
+
return [inlinedCss, ...childImports].filter(Boolean).join('\n');
|
|
87
104
|
} catch {
|
|
88
105
|
return '';
|
|
89
106
|
}
|
|
90
107
|
}
|
|
91
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Astro's `:global(...)` wrapper marks a selector as un-scoped. Dev-mode styles
|
|
111
|
+
* are already injected globally, so the wrapper carries no meaning here — and the
|
|
112
|
+
* browser treats `:global()` as an invalid pseudo-class and drops the whole rule.
|
|
113
|
+
* Unwrap it to its inner selector so the rule applies.
|
|
114
|
+
*/
|
|
115
|
+
function unwrapGlobalSelectors(css: string): string {
|
|
116
|
+
return css.replace(/:global\(\s*([^)]*?)\s*\)/g, '$1');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Builds a snippet that warns about a preprocessed <style> block we can't inline.
|
|
121
|
+
* Astro's compiler (which would turn scss/less/etc. into CSS) isn't reachable
|
|
122
|
+
* from this client-side transform, and shipping the raw source would break the
|
|
123
|
+
* browser's CSS parser, so we surface a clear console warning instead.
|
|
124
|
+
*/
|
|
125
|
+
function warnUnsupportedStyleLang(filePath: string, lang: string): string {
|
|
126
|
+
const message =
|
|
127
|
+
`[storybook-astro] Skipping <style lang="${lang}"> in ${filePath}: ` +
|
|
128
|
+
`preprocessed styles are not supported in Storybook dev mode.`;
|
|
129
|
+
|
|
130
|
+
return `
|
|
131
|
+
(function() {
|
|
132
|
+
if (typeof console !== 'undefined') { console.warn(${JSON.stringify(message)}); }
|
|
133
|
+
})();`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Builds a self-executing snippet that injects a CSS string into <head> as a
|
|
138
|
+
* <style> tag, tagged with a marker attribute. The marker also dedupes
|
|
139
|
+
* re-injection (e.g. when a module re-evaluates after HMR) so styles don't
|
|
140
|
+
* accumulate in the document.
|
|
141
|
+
*/
|
|
142
|
+
function styleInjectionSnippet(markerAttr: string, markerValue: string, css: string): string {
|
|
143
|
+
const attr = JSON.stringify(markerAttr);
|
|
144
|
+
const value = JSON.stringify(markerValue);
|
|
145
|
+
|
|
146
|
+
return `
|
|
147
|
+
(function() {
|
|
148
|
+
if (typeof document === 'undefined') { return; }
|
|
149
|
+
const tagged = document.head.querySelectorAll('style[' + ${attr} + ']');
|
|
150
|
+
for (const node of tagged) {
|
|
151
|
+
if (node.getAttribute(${attr}) === ${value}) { return; }
|
|
152
|
+
}
|
|
153
|
+
const style = document.createElement('style');
|
|
154
|
+
style.setAttribute(${attr}, ${value});
|
|
155
|
+
style.textContent = ${JSON.stringify(css)};
|
|
156
|
+
document.head.appendChild(style);
|
|
157
|
+
})();`;
|
|
158
|
+
}
|
|
159
|
+
|
|
92
160
|
/**
|
|
93
161
|
* Extracts import specifiers ending in `.astro` from a component's frontmatter.
|
|
94
162
|
* Comments are stripped first so commented-out imports don't resurface as
|
|
@@ -122,8 +190,9 @@ export function extractAstroImportSpecifiers(source: string): string[] {
|
|
|
122
190
|
* .astro components imported via relative paths. Used during builds where
|
|
123
191
|
* Astro's compile metadata cache is unavailable.
|
|
124
192
|
*
|
|
125
|
-
* The CSS is unscoped (no Astro scoping transforms),
|
|
126
|
-
*
|
|
193
|
+
* The CSS is unscoped (no Astro scoping transforms), so `:global(...)` wrappers
|
|
194
|
+
* are unwrapped here too — without that the browser drops the whole rule (e.g.
|
|
195
|
+
* PageCard's `.page-card__image-wrapper :global(img)` sizing).
|
|
127
196
|
*/
|
|
128
197
|
function generateInlineStyles(filePath: string): string {
|
|
129
198
|
const cssBlocks = collectStyleBlocks(filePath, new Set());
|
|
@@ -131,20 +200,11 @@ function generateInlineStyles(filePath: string): string {
|
|
|
131
200
|
if (cssBlocks.length === 0) {return '';}
|
|
132
201
|
|
|
133
202
|
// Create a side-effect that injects styles into the document
|
|
134
|
-
return cssBlocks
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
(function() {
|
|
140
|
-
if (typeof document !== 'undefined') {
|
|
141
|
-
const style = document.createElement('style');
|
|
142
|
-
style.setAttribute('data-astro-build', ${JSON.stringify(file + ':' + i)});
|
|
143
|
-
style.textContent = ${escaped};
|
|
144
|
-
document.head.appendChild(style);
|
|
145
|
-
}
|
|
146
|
-
})();`;
|
|
147
|
-
}).join('\n');
|
|
203
|
+
return cssBlocks
|
|
204
|
+
.map(({ file, css }, i) =>
|
|
205
|
+
styleInjectionSnippet('data-astro-build', `${file}:${i}`, unwrapGlobalSelectors(css))
|
|
206
|
+
)
|
|
207
|
+
.join('\n');
|
|
148
208
|
}
|
|
149
209
|
|
|
150
210
|
/**
|
|
@@ -183,28 +243,25 @@ function collectStyleBlocks(
|
|
|
183
243
|
* Strips frontmatter before parsing.
|
|
184
244
|
*/
|
|
185
245
|
function extractStyleBlocks(source: string): string[] {
|
|
246
|
+
return extractStyleBlocksWithLang(source).map((block) => block.css);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Like {@link extractStyleBlocks}, but also reports each block's `lang` attribute
|
|
251
|
+
* (e.g. "scss") so callers can tell preprocessed styles apart from plain CSS.
|
|
252
|
+
* Returns `null` for the lang when no attribute is present.
|
|
253
|
+
*/
|
|
254
|
+
function extractStyleBlocksWithLang(source: string): Array<{ css: string; lang: string | null }> {
|
|
186
255
|
const withoutFrontmatter = source.replace(/^---[\s\S]*?---/m, '');
|
|
187
|
-
const blocks: string
|
|
188
|
-
const regex = /<style(?:\s[^>]*)
|
|
256
|
+
const blocks: Array<{ css: string; lang: string | null }> = [];
|
|
257
|
+
const regex = /<style((?:\s[^>]*)?)>([\s\S]*?)<\/style>/g;
|
|
189
258
|
let match;
|
|
190
259
|
|
|
191
260
|
while ((match = regex.exec(withoutFrontmatter)) !== null) {
|
|
192
|
-
|
|
261
|
+
const langMatch = match[1].match(/\blang\s*=\s*['"]?([\w-]+)/);
|
|
262
|
+
|
|
263
|
+
blocks.push({ css: match[2].trim(), lang: langMatch ? langMatch[1] : null });
|
|
193
264
|
}
|
|
194
265
|
|
|
195
266
|
return blocks;
|
|
196
267
|
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Counts the number of top-level <style> blocks in an Astro component's source.
|
|
200
|
-
* Only counts opening tags that are NOT inside the frontmatter fence (---).
|
|
201
|
-
*/
|
|
202
|
-
function countStyleBlocks(source: string): number {
|
|
203
|
-
// Strip frontmatter
|
|
204
|
-
const withoutFrontmatter = source.replace(/^---[\s\S]*?---/m, '');
|
|
205
|
-
// Match <style> opening tags (with optional attributes)
|
|
206
|
-
const matches = withoutFrontmatter.match(/<style(\s|>)/g);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
return matches ? matches.length : 0;
|
|
210
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vitePluginAstroComponentMarker.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport type { PluginOption } from 'vite';\n\n/**\n * Vite plugin that patches Astro 6's client-side .astro file transforms for Storybook.\n *\n * In Astro 6, the client-side transform of .astro files produces a stub function that\n * throws \"Astro components cannot be used in the browser\" without setting the\n * `isAstroComponentFactory` marker. Storybook's renderer relies on this marker to detect\n * Astro components and route them to server-side rendering via the Container API.\n *\n * This plugin also preserves the component's scoped CSS by importing the style sub-modules\n * that the Astro Vite plugin exposes. Without this, the client-side stub would strip all\n * CSS since Astro 6 no longer includes style imports in client-side .astro transforms.\n *\n * During builds, Astro's compile metadata cache is not populated for client-side transforms,\n * so style sub-module imports would fail. Instead, raw CSS is extracted directly from the\n * .astro source and inlined.\n */\nexport function vitePluginAstroComponentMarker(): PluginOption {\n let isBuild = false;\n\n return {\n name: 'storybook-astro-component-marker',\n enforce: 'post',\n\n configResolved(config) {\n isBuild = config.command === 'build';\n },\n\n transform(code: string, id: string) {\n // Only process main .astro modules (not sub-modules like ?astro&type=style)\n if (!id.endsWith('.astro')) {return null;}\n\n // Detect the Astro 6 client-side stub pattern\n if (!code.includes('Astro components cannot be used in the browser')) {return null;}\n\n const moduleId = id;\n\n // In dev mode, import style sub-modules via Astro's Vite plugin (which has\n // compile metadata cached from the SSR transform).\n // In build mode, Astro's compile metadata cache is not populated for client-side\n // transforms, so sub-module imports would fail. Extract raw CSS instead.\n const styleCode = isBuild\n ? generateInlineStyles(moduleId)\n : generateStyleImports(moduleId);\n\n return {\n code: `\n${styleCode}\nconst __astro_component = () => {\n throw new Error('Astro components are rendered server-side by Storybook.');\n};\n__astro_component.isAstroComponentFactory = true;\n__astro_component.moduleId = ${JSON.stringify(moduleId)};\nexport default __astro_component;\n`,\n map: null,\n };\n },\n };\n}\n\n/**\n * Reads the original .astro source file and generates import statements\n * for each <style> block, using the Astro Vite plugin's sub-module convention.\n *\n * Child .astro components imported in the frontmatter are re-imported too.\n * Only the server renders children, so without these imports the child modules\n * never enter the browser's module graph and their scoped styles never load.\n * Each child passes through this same plugin, so style loading is transitive.\n */\nfunction generateStyleImports(filePath: string): string {\n try {\n const source = readFileSync(filePath, 'utf-8');\n const styleCount = countStyleBlocks(source);\n\n const styleImports = Array.from({ length: styleCount }, (_, i) =>\n `import ${JSON.stringify(`${filePath}?astro&type=style&index=${i}&lang.css`)};`\n );\n const childImports = extractAstroImportSpecifiers(source).map(\n (specifier) => `import ${JSON.stringify(specifier)};`\n );\n\n return [...styleImports, ...childImports].join('\\n');\n } catch {\n return '';\n }\n}\n\n/**\n * Extracts import specifiers ending in `.astro` from a component's frontmatter.\n * Comments are stripped first so commented-out imports don't resurface as\n * broken module requests in the browser.\n */\nexport function extractAstroImportSpecifiers(source: string): string[] {\n const frontmatterMatch = source.match(/^---([\\s\\S]*?)---/m);\n\n if (!frontmatterMatch) {return [];}\n\n const frontmatter = frontmatterMatch[1]\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/\\/\\/[^\\n]*/g, '');\n\n // Matches static imports (`import Child from './Child.astro'`), side-effect\n // imports (`import './Child.astro'`), and dynamic imports (`import('./Child.astro')`).\n const importPattern = /(?:from|import)\\s*\\(?\\s*['\"]([^'\"]+\\.astro)['\"]/g;\n const specifiers = new Set<string>();\n let match;\n\n while ((match = importPattern.exec(frontmatter)) !== null) {\n specifiers.add(match[1]);\n }\n\n return [...specifiers];\n}\n\n/**\n * Reads the original .astro source file and generates a JS snippet that injects\n * the raw CSS from each <style> block into the document, recursing into child\n * .astro components imported via relative paths. Used during builds where\n * Astro's compile metadata cache is unavailable.\n *\n * The CSS is unscoped (no Astro scoping transforms), which is acceptable because\n * Astro components show a fallback message in static builds.\n */\nfunction generateInlineStyles(filePath: string): string {\n const cssBlocks = collectStyleBlocks(filePath, new Set());\n\n if (cssBlocks.length === 0) {return '';}\n\n // Create a side-effect that injects styles into the document\n return cssBlocks.map(({ file, css }, i) => {\n const escaped = JSON.stringify(css);\n\n\nreturn `\n(function() {\n if (typeof document !== 'undefined') {\n const style = document.createElement('style');\n style.setAttribute('data-astro-build', ${JSON.stringify(file + ':' + i)});\n style.textContent = ${escaped};\n document.head.appendChild(style);\n }\n})();`;\n }).join('\\n');\n}\n\n/**\n * Collects <style> block contents from a component and its child .astro imports.\n * Only relative specifiers are followed (aliases and packages can't be resolved\n * from disk here). The visited set guards against import cycles.\n */\nfunction collectStyleBlocks(\n filePath: string,\n visited: Set<string>\n): Array<{ file: string; css: string }> {\n if (visited.has(filePath)) {return [];}\n visited.add(filePath);\n\n let source: string;\n\n try {\n source = readFileSync(filePath, 'utf-8');\n } catch {\n return [];\n }\n\n const blocks = extractStyleBlocks(source).map((css) => ({ file: filePath, css }));\n\n for (const specifier of extractAstroImportSpecifiers(source)) {\n if (!specifier.startsWith('.')) {continue;}\n\n blocks.push(...collectStyleBlocks(resolve(dirname(filePath), specifier), visited));\n }\n\n return blocks;\n}\n\n/**\n * Extracts the content of all top-level <style> blocks from an Astro component's source.\n * Strips frontmatter before parsing.\n */\nfunction extractStyleBlocks(source: string): string[] {\n const withoutFrontmatter = source.replace(/^---[\\s\\S]*?---/m, '');\n const blocks: string[] = [];\n const regex = /<style(?:\\s[^>]*)?>([\\s\\S]*?)<\\/style>/g;\n let match;\n\n while ((match = regex.exec(withoutFrontmatter)) !== null) {\n blocks.push(match[1].trim());\n }\n\n return blocks;\n}\n\n/**\n * Counts the number of top-level <style> blocks in an Astro component's source.\n * Only counts opening tags that are NOT inside the frontmatter fence (---).\n */\nfunction countStyleBlocks(source: string): number {\n // Strip frontmatter\n const withoutFrontmatter = source.replace(/^---[\\s\\S]*?---/m, '');\n // Match <style> opening tags (with optional attributes)\n const matches = withoutFrontmatter.match(/<style(\\s|>)/g);\n\n \nreturn matches ? matches.length : 0;\n}\n"],"mappings":"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","names":[]}
|