@@ -233,8 +224,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 98703512e..e9cc6ead7 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -4,7 +4,6 @@
"author": "Skypack",
"license": "MIT",
"type": "module",
- "types": "./dist/types/@types/astro-public.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/snowpackjs/astro.git",
@@ -53,7 +52,7 @@
"test": "mocha --parallel --timeout 15000"
},
"dependencies": {
- "@astrojs/compiler": "^0.2.16",
+ "@astrojs/compiler": "^0.2.17",
"@astrojs/language-server": "^0.7.16",
"@astrojs/markdown-remark": "^0.3.1",
"@astrojs/markdown-support": "0.3.1",
@@ -73,13 +72,14 @@
"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",
"node-fetch": "^2.6.5",
"path-to-regexp": "^6.2.0",
"remark-slug": "^7.0.0",
- "sass": "^1.43.2",
+ "sass": "^1.43.3",
"semver": "^7.3.5",
"send": "^0.17.1",
"shiki": "^0.9.10",
diff --git a/packages/astro/src/@types/astro-public.ts b/packages/astro/src/@types/astro-public.ts
deleted file mode 100644
index e28485e86..000000000
--- a/packages/astro/src/@types/astro-public.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { AstroConfig, AstroUserConfig } from './astro-core';
diff --git a/packages/astro/src/core/ssr/css.ts b/packages/astro/src/core/ssr/css.ts
new file mode 100644
index 000000000..4e25f7884
--- /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', '.pcss', '.scss', '.sass', '.styl', '.stylus', '.less']);
+export const PREPROCESSOR_EXTENSIONS = new Set(['.pcss', '.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 e9d5dbd7e..07755d79a 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,25 @@ 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
+ const styles = getStylesForID(fileURLToPath(filePath), viteServer);
+ const relativeStyles = new Set([...styles].map((url) => url.replace(fileURLToPath(astroConfig.projectRoot), '/')));
+ html = addLinkTagsToHTML(html, relativeStyles);
+
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 d40b2e278..3335e6720 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,32 @@ 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 {
+ // get the item as a string
+ item = item == null ? '' : String(item).trim();
+
+ // add the item if it is filled
+ if (item) {
+ 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 597f61fa7..738204df2 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';
@@ -15,6 +16,20 @@ interface AstroPluginOptions {
devServer?: AstroDevServer;
}
+// https://github.com/vitejs/vite/discussions/5109#discussioncomment-1450726
+function isSSR(options: undefined | boolean | { ssr: boolean }): boolean {
+ if (options === undefined) {
+ return false;
+ }
+ if (typeof options === 'boolean') {
+ return options;
+ }
+ if (typeof options == 'object') {
+ return !!options.ssr;
+ }
+ return false;
+}
+
/** Transform .astro files for Vite */
export default function astro({ config, devServer }: AstroPluginOptions): vite.Plugin {
let viteTransform: TransformHook;
@@ -25,7 +40,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, opts) {
if (!id.endsWith('.astro')) {
return null;
}
@@ -47,12 +62,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: isSSR(opts) });
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 +86,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 2794a3728..7c87e60d2 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;
}
-/** Transform style using Vite hook */
-export async function transformWithVite(value: string, attrs: Record, id: string, transformHook: TransformHook): 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;
+interface TransformWithViteOptions {
+ value: string;
+ attrs: Record;
+ id: string;
+ transformHook: TransformHook;
+ ssr?: boolean;
+}
+
+/** Transform style using Vite hook */
+export async function transformWithVite({ value, attrs, transformHook, id, ssr }: TransformWithViteOptions): Promise {
+ const lang = (`.${attrs.lang}` || '').toLowerCase(); // add leading "."; donβt be case-sensitive
+ if (!PREPROCESSOR_EXTENSIONS.has(lang)) {
+ return null; // only preprocess langs supported by Vite
+ }
+ return transformHook(value, id + `?astro&type=style&lang${lang}`, ssr);
}
diff --git a/packages/astro/test/astro-styles-ssr.test.js b/packages/astro/test/astro-styles-ssr.test.js
index ea9ea7e4d..6a1ec6cb3 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
diff --git a/packages/astro/test/preact-component.test.js b/packages/astro/test/preact-component.test.js
index ea12054e6..d9fb14d1b 100644
--- a/packages/astro/test/preact-component.test.js
+++ b/packages/astro/test/preact-component.test.js
@@ -21,8 +21,7 @@ describe('Preact component', () => {
expect($('#class-component')).to.have.lengthOf(1);
});
- // TODO: fix compiler bug (not interpreting as a component)
- it.skip('Can load function component', async () => {
+ it('Can load function component', async () => {
const html = await fixture.readFile('/fn/index.html');
const $ = cheerio.load(html);
diff --git a/packages/astro/test/react-component.test.js b/packages/astro/test/react-component.test.js
index f7908e0d4..0729e2c29 100644
--- a/packages/astro/test/react-component.test.js
+++ b/packages/astro/test/react-component.test.js
@@ -23,9 +23,8 @@ describe('React Components', () => {
// test 2: no reactroot
expect($('#react-h2').attr('data-reactroot')).to.equal(undefined);
- // TODO: fix compiler bug with arrow components
// test 3: Can use function components
- // expect($('#arrow-fn-component')).to.have.lengthOf(1);
+ expect($('#arrow-fn-component')).to.have.lengthOf(1);
// test 4: Can use spread for components
expect($('#component-spread-props')).to.have.lengthOf(1);
diff --git a/yarn.lock b/yarn.lock
index 0aea0e7ef..b1b21c787 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -106,10 +106,10 @@
"@algolia/logger-common" "4.10.5"
"@algolia/requester-common" "4.10.5"
-"@astrojs/compiler@^0.2.16":
- version "0.2.16"
- resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-0.2.16.tgz#e2b560d699c586bb26e5332255050f8b97d1a19d"
- integrity sha512-PVMIxBePkzxkg56g9WJXmKkeW0xCmAMOrmSpW0uySucWbdyAMc31sSZb9v6dhYt4lrFiV6CDOCCqcEmRc2wHoA==
+"@astrojs/compiler@^0.2.17":
+ version "0.2.17"
+ resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-0.2.17.tgz#bb814a70af9c3694160d9fe8f7a0c96b4b194429"
+ integrity sha512-qWGC1UT0olUR/iXHOrD4tnwJSttiVDWfY4KDodhzTkctaXqpc69dBb3gpN8r1jiqZQCPT9Wqmb9GSKWqbUDn2A==
dependencies:
typescript "^4.3.5"
@@ -3876,14 +3876,14 @@ domelementtype@^2.0.1, domelementtype@^2.2.0:
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
-domhandler@^4.0.0, domhandler@^4.2.0:
+domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f"
integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==
dependencies:
domelementtype "^2.2.0"
-domutils@^2.5.2, domutils@^2.6.0, domutils@^2.7.0:
+domutils@^2.5.2, domutils@^2.6.0, domutils@^2.7.0, domutils@^2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
@@ -4001,6 +4001,11 @@ entities@^2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
+entities@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4"
+ integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==
+
env-paths@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
@@ -5592,6 +5597,16 @@ htmlparser2@^6.1.0:
domutils "^2.5.2"
entities "^2.0.0"
+htmlparser2@^7.1.2:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-7.1.2.tgz#587923d38f03bc89e03076e00cba2c7473f37f7c"
+ integrity sha512-d6cqsbJba2nRdg8WW2okyD4ceonFHn9jLFxhwlNcLhQWcFPdxXeJulgOLjLKtAK9T6ahd+GQNZwG9fjmGW7lyg==
+ dependencies:
+ domelementtype "^2.0.1"
+ domhandler "^4.2.2"
+ domutils "^2.8.0"
+ entities "^3.0.1"
+
http-assert@^1.3.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.5.0.tgz#c389ccd87ac16ed2dfa6246fd73b926aa00e6b8f"
@@ -9539,10 +9554,10 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@^5.2.1,
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-sass@^1.43.2:
- version "1.43.2"
- resolved "https://registry.yarnpkg.com/sass/-/sass-1.43.2.tgz#c02501520c624ad6622529a8b3724eb08da82d65"
- integrity sha512-DncYhjl3wBaPMMJR0kIUaH3sF536rVrOcqqVGmTZHQRRzj7LQlyGV7Mb8aCKFyILMr5VsPHwRYtyKpnKYlmQSQ==
+sass@^1.43.3:
+ version "1.43.3"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.43.3.tgz#aa16a69131b84f0cd23189a242571e8905f1ce43"
+ integrity sha512-BJnLngqWpMeS65UvlYYEuCb3/fLxDxhHtOB/gWPxs6NKrslTxGt3ZxwIvOe/0Jm4tWwM/+tIpE3wj4dLEhPDeQ==
dependencies:
chokidar ">=3.0.0 <4.0.0"