@@ -233,8 +223,7 @@ const featuredProject = projects[0];
Hello! Iβm Jeanine, and this is my website. It was made using{' '}
Astro
-
- , a new way to build static sites. This is just an example template for you to modify.
+ , a new way to build static sites. This is just an example template for you to modify.
Read more
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 815d875576133..65b75532b2610 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -53,7 +53,7 @@
"test": "mocha --parallel --timeout 15000"
},
"dependencies": {
- "@astrojs/compiler": "^0.2.13",
+ "@astrojs/compiler": "^0.2.16-rc.1",
"@astrojs/language-server": "^0.7.16",
"@astrojs/markdown-remark": "^0.3.1",
"@astrojs/markdown-support": "0.3.1",
@@ -73,6 +73,7 @@
"estree-util-value-to-estree": "^1.2.0",
"fast-xml-parser": "^3.19.0",
"html-entities": "^2.3.2",
+ "htmlparser2": "^7.1.2",
"kleur": "^4.1.4",
"mime": "^2.5.2",
"morphdom": "^2.6.1",
diff --git a/packages/astro/src/@types/astro-public.ts b/packages/astro/src/@types/astro-public.ts
index e28485e868888..bc4630ae158d5 100644
--- a/packages/astro/src/@types/astro-public.ts
+++ b/packages/astro/src/@types/astro-public.ts
@@ -1 +1 @@
-export { AstroConfig, AstroUserConfig } from './astro-core';
+export { AstroConfig, AstroUserConfig } from './astro-core.js';
diff --git a/packages/astro/src/core/ssr/css.ts b/packages/astro/src/core/ssr/css.ts
new file mode 100644
index 0000000000000..647602a0912c2
--- /dev/null
+++ b/packages/astro/src/core/ssr/css.ts
@@ -0,0 +1,64 @@
+import type vite from '../../../vendor/vite';
+
+import path from 'path';
+import htmlparser2 from 'htmlparser2';
+
+// https://vitejs.dev/guide/features.html#css-pre-processors
+export const STYLE_EXTENSIONS = new Set(['.css', '.scss', '.sass', '.styl', '.stylus', '.less']);
+export const PREPROCESSOR_EXTENSIONS = new Set(['.scss', '.sass', '.styl', '.stylus', '.less']);
+
+/** find unloaded styles */
+export function getStylesForID(id: string, viteServer: vite.ViteDevServer): Set {
+ const css = new Set();
+ const { idToModuleMap } = viteServer.moduleGraph;
+ const moduleGraph = idToModuleMap.get(id);
+ if (!moduleGraph) return css;
+
+ // recursively crawl module graph to get all style files imported by parent id
+ function crawlCSS(entryModule: string, scanned = new Set()) {
+ const moduleName = idToModuleMap.get(entryModule);
+ if (!moduleName) return;
+ for (const importedModule of moduleName.importedModules) {
+ if (!importedModule.id || scanned.has(importedModule.id)) return;
+ const ext = path.extname(importedModule.id.toLowerCase());
+
+ if (STYLE_EXTENSIONS.has(ext)) {
+ css.add(importedModule.id); // if style file, add to list
+ } else {
+ crawlCSS(importedModule.id, scanned); // otherwise, crawl file to see if it imports any CSS
+ }
+ scanned.add(importedModule.id);
+ }
+ }
+ crawlCSS(id);
+
+ return css;
+}
+
+/** add CSS tags to HTML */
+export function addLinkTagsToHTML(html: string, styles: Set): string {
+ let output = html;
+
+ try {
+ // get position of
+ let headEndPos = -1;
+ const parser = new htmlparser2.Parser({
+ onclosetag(tagname) {
+ if (tagname === 'head') {
+ headEndPos = parser.startIndex;
+ }
+ },
+ });
+ parser.write(html);
+ parser.end();
+
+ // update html
+ if (headEndPos !== -1) {
+ output = html.substring(0, headEndPos) + [...styles].map((href) => ``).join('') + html.substring(headEndPos);
+ }
+ } catch (err) {
+ // on invalid HTML, do nothing
+ }
+
+ return output;
+}
diff --git a/packages/astro/src/core/ssr/index.ts b/packages/astro/src/core/ssr/index.ts
index e9d5dbd7e5bbb..b49dd2e828475 100644
--- a/packages/astro/src/core/ssr/index.ts
+++ b/packages/astro/src/core/ssr/index.ts
@@ -9,6 +9,7 @@ import fs from 'fs';
import path from 'path';
import { renderPage, renderSlot } from '../../runtime/server/index.js';
import { canonicalURL as getCanonicalURL, codeFrame } from '../util.js';
+import { addLinkTagsToHTML, getStylesForID } from './css.js';
import { generatePaginateFunction } from './paginate.js';
import { getParams, validateGetStaticPathsModule, validateGetStaticPathsResult } from './routing.js';
@@ -148,18 +149,23 @@ export async function ssr({ astroConfig, filePath, logging, mode, origin, pathna
({ default: render } = await import(render));
}
const { code } = await render(content, { ...renderOpts, ...(opts ?? {}) });
- return code
- }
+ return code;
+ },
} as unknown as AstroGlobal;
},
_metadata: { renderers },
};
let html = await renderPage(result, Component, pageProps, null);
+
// run transformIndexHtml() in development to add HMR client to the page.
if (mode === 'development') {
html = await viteServer.transformIndexHtml(fileURLToPath(filePath), html, pathname);
}
+
+ // insert CSS imported from Astro and JS components
+ html = addLinkTagsToHTML(html, getStylesForID(fileURLToPath(filePath), viteServer));
+
return html;
} catch (e: any) {
viteServer.ssrFixStacktrace(e);
diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts
index d40b2e278045b..5fb755b91a195 100644
--- a/packages/astro/src/runtime/server/index.ts
+++ b/packages/astro/src/runtime/server/index.ts
@@ -132,7 +132,7 @@ function extractDirectives(inputProps: Record): ExtractedP
}
} else if (key === 'class:list') {
// support "class" from an expression passed into a component (#782)
- extracted.props[key.slice(0, -5)] = serializeListValue(value)
+ extracted.props[key.slice(0, -5)] = serializeListValue(value);
} else {
extracted.props[key] = value;
}
@@ -308,29 +308,25 @@ export function spreadAttributes(values: Record) {
}
function serializeListValue(value: any) {
- const hash: Record = {}
+ const hash: Record = {};
- push(value)
+ push(value);
return Object.keys(hash).join(' ');
function push(item: any) {
// push individual iteratables
- if (item && typeof item.forEach === 'function') item.forEach(push)
-
+ if (item && typeof item.forEach === 'function') item.forEach(push);
// otherwise, push object value keys by truthiness
- else if (item === Object(item)) Object.keys(item).forEach(
- name => {
- if (item[name]) push(name)
- }
- )
-
+ else if (item === Object(item))
+ Object.keys(item).forEach((name) => {
+ if (item[name]) push(name);
+ });
// otherwise, push any other values as a string
- else if (item = item != null && String(item).trim()) item.split(/\s+/).forEach(
- (name: string) => {
- hash[name] = true
- }
- )
+ else if ((item = item != null && String(item).trim()))
+ item.split(/\s+/).forEach((name: string) => {
+ hash[name] = true;
+ });
}
}
diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts
index 597f61fa703d0..af10a98795bb8 100644
--- a/packages/astro/src/vite-plugin-astro/index.ts
+++ b/packages/astro/src/vite-plugin-astro/index.ts
@@ -1,4 +1,5 @@
import type { TransformResult } from '@astrojs/compiler';
+import type { SourceMapInput } from 'rollup';
import type vite from '../core/vite';
import type { AstroConfig } from '../@types/astro-core';
@@ -25,7 +26,7 @@ export default function astro({ config, devServer }: AstroPluginOptions): vite.P
viteTransform = getViteTransform(resolvedConfig);
},
// note: donβt claim .astro files with resolveId() βΒ it prevents Vite from transpiling the final JS (import.meta.globEager, etc.)
- async load(id) {
+ async load(id, ssr) {
if (!id.endsWith('.astro')) {
return null;
}
@@ -47,12 +48,20 @@ export default function astro({ config, devServer }: AstroPluginOptions): vite.P
internalURL: 'astro/internal',
preprocessStyle: async (value: string, attrs: Record) => {
if (!attrs || !attrs.lang) return null;
- const result = await transformWithVite(value, attrs, id, viteTransform);
+ const result = await transformWithVite({ value, attrs, id, transformHook: viteTransform, ssr });
if (!result) {
// TODO: compiler supports `null`, but types don't yet
return result as any;
}
- return { code: result.code, map: result.map?.toString() };
+ let map: SourceMapInput | undefined;
+ if (result.map) {
+ if (typeof result.map === 'string') {
+ map = result.map;
+ } else if (result.map.mappings) {
+ map = result.map.toString();
+ }
+ }
+ return { code: result.code, map };
},
});
// Compile `.ts` to `.js`
@@ -63,12 +72,14 @@ export default function astro({ config, devServer }: AstroPluginOptions): vite.P
map,
};
} catch (err: any) {
- // if esbuild threw the error, find original code source to display
+ // if esbuild threw the error, find original code source to display (if itβs mapped)
if (err.errors && tsResult?.map) {
const json = JSON.parse(tsResult.map);
const mappings = decode(json.mappings);
const focusMapping = mappings[err.errors[0].location.line + 1];
- err.sourceLoc = { file: id, line: (focusMapping[0][2] || 0) + 1, column: (focusMapping[0][3] || 0) + 1 };
+ if (Array.isArray(focusMapping) && focusMapping.length) {
+ err.sourceLoc = { file: id, line: (focusMapping[0][2] || 0) + 1, column: (focusMapping[0][3] || 0) + 1 };
+ }
}
throw err;
}
diff --git a/packages/astro/src/vite-plugin-astro/styles.ts b/packages/astro/src/vite-plugin-astro/styles.ts
index 2794a3728b8c9..40ef86d4d5fee 100644
--- a/packages/astro/src/vite-plugin-astro/styles.ts
+++ b/packages/astro/src/vite-plugin-astro/styles.ts
@@ -1,9 +1,8 @@
import type vite from '../core/vite';
-export type TransformHook = (code: string, id: string, ssr?: boolean) => Promise;
+import { PREPROCESSOR_EXTENSIONS } from '../core/ssr/css.js';
-// https://vitejs.dev/guide/features.html#css-pre-processors
-const SUPPORTED_PREPROCESSORS = new Set(['scss', 'sass', 'styl', 'stylus', 'less']);
+export type TransformHook = (code: string, id: string, ssr?: boolean) => Promise;
/** Load vite:cssβ transform() hook */
export function getViteTransform(viteConfig: vite.ResolvedConfig): TransformHook {
@@ -13,10 +12,19 @@ export function getViteTransform(viteConfig: vite.ResolvedConfig): TransformHook
return viteCSSPlugin.transform.bind(null as any) as any;
}
+interface TransformWithViteOptions {
+ value: string;
+ attrs: Record;
+ id: string;
+ transformHook: TransformHook;
+ ssr?: boolean;
+}
+
/** Transform style using Vite hook */
-export async function transformWithVite(value: string, attrs: Record, id: string, transformHook: TransformHook): Promise {
+export async function transformWithVite({ value, attrs, transformHook, id, ssr }: TransformWithViteOptions): Promise {
const lang = (attrs.lang || '').toLowerCase(); // donβt be case-sensitive
- if (!SUPPORTED_PREPROCESSORS.has(lang)) return null; // only preprocess the above
- const result = await transformHook(value, id.replace(/\.astro$/, `.${lang}`));
- return result || null;
+ if (!PREPROCESSOR_EXTENSIONS.has(lang)) {
+ return null; // only preprocess the above
+ }
+ return transformHook(value, id.replace(/\.astro$/, `.${lang}`), ssr);
}
diff --git a/packages/astro/test/astro-styles-ssr.test.js b/packages/astro/test/astro-styles-ssr.test.js
index ea9ea7e4d6da4..52e2a4c58d0e0 100644
--- a/packages/astro/test/astro-styles-ssr.test.js
+++ b/packages/astro/test/astro-styles-ssr.test.js
@@ -10,25 +10,24 @@ before(async () => {
});
describe('Styles SSR', () => {
- // TODO: convert