diff --git a/examples/portfolio/src/pages/index.astro b/examples/portfolio/src/pages/index.astro index 4b56211ab..057c5171c 100644 --- a/examples/portfolio/src/pages/index.astro +++ b/examples/portfolio/src/pages/index.astro @@ -190,28 +190,19 @@ const featuredProject = projects[0]; height="1131" class="img" src="https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?w=1200&q=75" - srcSet="https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?w=1200&q=75 800w, - https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?w=1200&q=75 1200w, - https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?w=1600&q=75 1600w, - https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?w=2400&q=75 2400w," + srcSet="https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?w=1200&q=75 800w,https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?w=1200&q=75 1200w,https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?w=1600&q=75 1600w,https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?w=2400&q=75 2400w" sizes="(max-width: 800px) 800px, (max-width: 1200px) 1200px, (max-width: 1600px) 1600px, (max-width: 2400px) 2400px, 1200px" - /> -
-
+ > +
+

The personal site of Jeanine White

- - πŸ‘©β€πŸ’» Developer πŸ‘©β€πŸ’» Developer -   - - 🎀 Speaker 🎀 Speaker -   - - ✏️ Writer ✏️ Writer - + πŸ‘©β€πŸ’» Developer πŸ‘©β€πŸ’» Developer  + 🎀 Speaker 🎀 Speaker  + ✏️ Writer ✏️ Writer

Lover of dogs, roadtrips, and poetry.

@@ -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"