update prettier width (#2968)

This commit is contained in:
Fred K. Schott 2022-04-02 14:15:41 -06:00 committed by GitHub
parent d63213f119
commit 1335797903
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
146 changed files with 2134 additions and 483 deletions

View file

@ -1,5 +1,5 @@
{
"printWidth": 180,
"printWidth": 100,
"semi": true,
"singleQuote": true,
"tabWidth": 2,

View file

@ -1,4 +1,9 @@
{
"recommendations": ["astro-build.astro-vscode", "esbenp.prettier-vscode", "editorconfig.editorconfig", "dbaeumer.vscode-eslint"],
"recommendations": [
"astro-build.astro-vscode",
"esbenp.prettier-vscode",
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint"
],
"unwantedRecommendations": []
}

View file

@ -1,8 +1,10 @@
:root {
--font-fallback: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
--font-fallback: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif,
Apple Color Emoji, Segoe UI Emoji;
--font-body: 'IBM Plex Sans', var(--font-fallback);
--font-mono: 'IBM Plex Mono', Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono',
'Liberation Mono', 'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace;
--font-mono: 'IBM Plex Mono', Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console',
'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono',
'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace;
--color-white: #fff;
--color-black: #000014;

View file

@ -6,8 +6,19 @@ import { KNOWN_LANGUAGES, langPathRegex } from '../../languages';
const LanguageSelect: FunctionalComponent<{ lang: string }> = ({ lang }) => {
return (
<div class="language-select-wrapper">
<svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 88.6 77.3" height="1.2em" width="1.2em">
<path fill="currentColor" d="M61,24.6h7.9l18.7,51.6h-7.7l-5.4-15.5H54.3l-5.6,15.5h-7.2L61,24.6z M72.6,55l-8-22.8L56.3,55H72.6z" />
<svg
aria-hidden="true"
focusable="false"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 88.6 77.3"
height="1.2em"
width="1.2em"
>
<path
fill="currentColor"
d="M61,24.6h7.9l18.7,51.6h-7.7l-5.4-15.5H54.3l-5.6,15.5h-7.2L61,24.6z M72.6,55l-8-22.8L56.3,55H72.6z"
/>
<path
fill="currentColor"
d="M53.6,60.6c-10-4-16-9-22-14c0,0,1.3,1.3,0,0c-6,5-20,13-20,13l-4-6c8-5,10-6,19-13c-2.1-1.9-12-13-13-19h8 c4,9,10,14,10,14c10-8,10-19,10-19h8c0,0-1,13-12,24l0,0c5,5,10,9,19,13L53.6,60.6z M1.6,16.6h56v-8h-23v-7h-9v7h-24V16.6z"

View file

@ -41,7 +41,13 @@ export default function Search() {
<>
<button type="button" ref={searchButtonRef} onClick={onOpen} className="search-input">
<svg width="24" height="24" fill="none">
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<span>Search</span>
<span className="search-hint">

View file

@ -15,9 +15,26 @@ const MenuToggle: FunctionalComponent = () => {
}, [sidebarShown]);
return (
<button type="button" aria-pressed={sidebarShown ? 'true' : 'false'} id="menu-toggle" onClick={() => setSidebarShown(!sidebarShown)}>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
<button
type="button"
aria-pressed={sidebarShown ? 'true' : 'false'}
id="menu-toggle"
onClick={() => setSidebarShown(!sidebarShown)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
<span className="sr-only">Toggle sidebar</span>
</button>

View file

@ -33,7 +33,11 @@ const TableOfContents: FunctionalComponent<{ headers: any[] }> = ({ headers = []
{headers
.filter(({ depth }) => depth > 1 && depth < 4)
.map((header) => (
<li class={`header-link depth-${header.depth} ${activeId === header.slug ? 'active' : ''}`.trim()}>
<li
class={`header-link depth-${header.depth} ${
activeId === header.slug ? 'active' : ''
}`.trim()}
>
<a href={`#${header.slug}`}>{header.text}</a>
</li>
))}

View file

@ -6,14 +6,26 @@ import './ThemeToggleButton.css';
const themes = ['light', 'dark'];
const icons = [
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
clipRule="evenodd"
/>
</svg>,
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
fill="currentColor"
>
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
</svg>,
];

View file

@ -7,7 +7,9 @@ export const SITE = {
export const OPEN_GRAPH = {
image: {
src: 'https://github.com/withastro/astro/blob/main/assets/social/banner.jpg?raw=true',
alt: 'astro logo on a starry expanse of space,' + ' with a purple saturn-like planet floating in the right foreground',
alt:
'astro logo on a starry expanse of space,' +
' with a purple saturn-like planet floating in the right foreground',
},
twitter: 'astrodotbuild',
};

View file

@ -1,8 +1,10 @@
:root {
--font-fallback: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
--font-fallback: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif,
Apple Color Emoji, Segoe UI Emoji;
--font-body: system-ui, var(--font-fallback);
--font-mono: 'IBM Plex Mono', Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono',
'Liberation Mono', 'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace;
--font-mono: 'IBM Plex Mono', Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console',
'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono',
'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace;
/*
* Variables with --color-base prefix define

View file

@ -24,7 +24,13 @@ function Nav() {
</svg>
</a>
<a className={Styles.social} href="https://dev.to/me">
<svg className={Styles.socialicon} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 40" style="enable-background:new 0 0 50 40" xmlSpace="preserve">
<svg
className={Styles.socialicon}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 50 40"
style="enable-background:new 0 0 50 40"
xmlSpace="preserve"
>
<path d="M15.7 15.5c-.4-.3-.7-.4-1.1-.4h-1.7v10.1h1.7c.4 0 .8-.1 1.1-.4.4-.3.6-.7.6-1.3v-6.7c0-.6-.2-1-.6-1.3z" />
<path d="M47 0H3C1.3 0 0 1.3 0 3v34c0 1.7 1.3 3 3 3h44c1.7 0 3-1.3 3-3V3c0-1.7-1.3-3-3-3zM19.1 23.5c0 1.3-.4 2.4-1.3 3.2-.8.9-1.9 1.3-3.3 1.3h-4.4V12.3h4.5c1.3 0 2.4.4 3.2 1.3.8.8 1.3 1.9 1.3 3.2v6.7zm9.1-8.4h-5.1v3.6h3.1v2.8h-3.1v3.7h5.1V28h-5.9c-.6 0-1-.2-1.4-.6-.4-.4-.6-.8-.6-1.4V14.2c0-.6.2-1 .6-1.4.4-.4.8-.6 1.4-.6h5.9v2.9zM37.5 26c-.6 1.3-1.3 2-2.2 2-.9 0-1.7-.7-2.2-2l-3.7-13.8h3.1L35.3 23l2.8-10.8h3.1L37.5 26z" />
</svg>

View file

@ -21,7 +21,11 @@ function getOrigin(request: Request): string {
return new URL(request.url).origin.replace('localhost', '127.0.0.1');
}
async function get<T>(incomingReq: Request, endpoint: string, cb: (response: Response) => Promise<T>): Promise<T> {
async function get<T>(
incomingReq: Request,
endpoint: string,
cb: (response: Response) => Promise<T>
): Promise<T> {
const response = await fetch(`${getOrigin(incomingReq)}${endpoint}`, {
credentials: 'same-origin',
});

View file

@ -4,7 +4,8 @@
}
:root {
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif,
Apple Color Emoji, Segoe UI Emoji;
font-size: 1rem;
--user-font-scale: 1rem - 16px;
font-size: clamp(0.875rem, 0.4626rem + 1.0309vw + var(--user-font-scale), 1.125rem);

View file

@ -1,6 +1,7 @@
:root {
--font-mono: Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono',
'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace;
--font-mono: Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter',
'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono', 'Nimbus Mono L', Monaco,
'Courier New', Courier, monospace;
--color-light: #f3f4f6;
}

View file

@ -6,6 +6,11 @@ export default defineConfig({
// Enable Custom Markdown options, plugins, etc.
markdown: {
remarkPlugins: ['remark-code-titles'],
rehypePlugins: [['rehype-autolink-headings', { behavior: 'prepend' }], ['rehype-toc', { headings: ['h2', 'h3'] }], [addClasses, { 'h1,h2,h3': 'title' }], 'rehype-slug'],
rehypePlugins: [
['rehype-autolink-headings', { behavior: 'prepend' }],
['rehype-toc', { headings: ['h2', 'h3'] }],
[addClasses, { 'h1,h2,h3': 'title' }],
'rehype-slug',
],
},
});

View file

@ -4,7 +4,8 @@
}
:root {
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif,
Apple Color Emoji, Segoe UI Emoji;
font-size: 1rem;
--user-font-scale: 1rem - 16px;
font-size: clamp(0.875rem, 0.4626rem + 1.0309vw + var(--user-font-scale), 1.125rem);

View file

@ -1,6 +1,7 @@
:root {
--font-mono: Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono',
'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace;
--font-mono: Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter',
'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono', 'Nimbus Mono L', Monaco,
'Courier New', Courier, monospace;
--color-light: #f3f4f6;
}

View file

@ -8,7 +8,9 @@ export function addAstro(Prism) {
scriptLang = 'typescript';
} else {
scriptLang = 'javascript';
console.warn('Prism TypeScript language not loaded, Astro scripts will be treated as JavaScript.');
console.warn(
'Prism TypeScript language not loaded, Astro scripts will be treated as JavaScript.'
);
}
let script = Prism.util.clone(Prism.languages[scriptLang]);
@ -38,10 +40,14 @@ export function addAstro(Prism) {
spread = re(spread).source;
Prism.languages.astro = Prism.languages.extend('markup', script);
Prism.languages.astro.tag.pattern = re(/<\/?(?:[\w.:-]+(?:<S>+(?:[\w.:$-]+(?:=(?:"(?:\\[^]|[^\\"])*"|'(?:\\[^]|[^\\'])*'|[^\s{'"/>=]+|<BRACES>))?|<SPREAD>))*<S>*\/?)?>/.source);
Prism.languages.astro.tag.pattern = re(
/<\/?(?:[\w.:-]+(?:<S>+(?:[\w.:$-]+(?:=(?:"(?:\\[^]|[^\\"])*"|'(?:\\[^]|[^\\'])*'|[^\s{'"/>=]+|<BRACES>))?|<SPREAD>))*<S>*\/?)?>/
.source
);
Prism.languages.astro.tag.inside['tag'].pattern = /^<\/?[^\s>\/]*/i;
Prism.languages.astro.tag.inside['attr-value'].pattern = /=(?!\{)(?:"(?:\\[^]|[^\\"])*"|'(?:\\[^]|[^\\'])*'|[^\s'">]+)/i;
Prism.languages.astro.tag.inside['attr-value'].pattern =
/=(?!\{)(?:"(?:\\[^]|[^\\"])*"|'(?:\\[^]|[^\\'])*'|[^\s'">]+)/i;
Prism.languages.astro.tag.inside['tag'].inside['class-name'] = /^[A-Z]\w*(?:\.[A-Z]\w*)*$/;
Prism.languages.astro.tag.inside['comment'] = script['comment'];
@ -109,7 +115,11 @@ export function addAstro(Prism) {
if (token.content[0].content[0].content === '</') {
// Closing tag
if (openedTags.length > 0 && openedTags[openedTags.length - 1].tagName === stringifyToken(token.content[0].content[1])) {
if (
openedTags.length > 0 &&
openedTags[openedTags.length - 1].tagName ===
stringifyToken(token.content[0].content[1])
) {
// Pop matching opening tag
openedTags.pop();
}
@ -127,7 +137,12 @@ export function addAstro(Prism) {
} else if (openedTags.length > 0 && token.type === 'punctuation' && token.content === '{') {
// Here we might have entered a Astro context inside a tag
openedTags[openedTags.length - 1].openedBraces++;
} else if (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces > 0 && token.type === 'punctuation' && token.content === '}') {
} else if (
openedTags.length > 0 &&
openedTags[openedTags.length - 1].openedBraces > 0 &&
token.type === 'punctuation' &&
token.content === '}'
) {
// Here we might have left a Astro context inside a tag
openedTags[openedTags.length - 1].openedBraces--;
} else {
@ -141,7 +156,10 @@ export function addAstro(Prism) {
let plainText = stringifyToken(token);
// And merge text with adjacent text
if (i < tokens.length - 1 && (typeof tokens[i + 1] === 'string' || tokens[i + 1].type === 'plain-text')) {
if (
i < tokens.length - 1 &&
(typeof tokens[i + 1] === 'string' || tokens[i + 1].type === 'plain-text')
) {
plainText += stringifyToken(tokens[i + 1]);
tokens.splice(i + 1, 1);
}

View file

@ -9,7 +9,8 @@
const CI_INSTRUCTIONS = {
NETLIFY: 'https://docs.netlify.com/configure-builds/manage-dependencies/#node-js-and-javascript',
GITHUB_ACTIONS: 'https://docs.github.com/en/actions/guides/building-and-testing-nodejs#specifying-the-nodejs-version',
GITHUB_ACTIONS:
'https://docs.github.com/en/actions/guides/building-and-testing-nodejs#specifying-the-nodejs-version',
VERCEL: 'https://vercel.com/docs/runtimes#official-runtimes/node-js/node-js-version',
};
@ -17,7 +18,10 @@ const CI_INSTRUCTIONS = {
async function main() {
// Check for ESM support.
// Load the "supports-esm" package in an way that works in both ESM & CJS.
let supportsESM = typeof require !== 'undefined' ? require('supports-esm') : (await import('supports-esm')).default;
let supportsESM =
typeof require !== 'undefined'
? require('supports-esm')
: (await import('supports-esm')).default;
// Check for CJS->ESM named export support.
// "path-to-regexp" is a real-world package that we depend on, that only
@ -79,7 +83,9 @@ Please upgrade Node.js to a supported version: "${engines}"\n`);
break;
}
}
console.log(`${ci.name} CI Environment Detected!\nAdditional steps may be needed to set your Node.js version:`);
console.log(
`${ci.name} CI Environment Detected!\nAdditional steps may be needed to set your Node.js version:`
);
console.log(`Documentation: https://docs.astro.build/guides/deploy`);
if (CI_INSTRUCTIONS[platform]) {
console.log(`${ci.name} Documentation: ${CI_INSTRUCTIONS[platform]}`);

View file

@ -56,7 +56,10 @@ export interface AstroGlobal extends AstroGlobalPartial {
/** get information about this page */
request: Request;
/** see if slots are used */
slots: Record<string, true | undefined> & { has(slotName: string): boolean; render(slotName: string, args?: any[]): Promise<string> };
slots: Record<string, true | undefined> & {
has(slotName: string): boolean;
render(slotName: string, args?: any[]): Promise<string>;
};
}
export interface AstroGlobalPartial {
@ -560,7 +563,12 @@ export interface AstroConfig extends z.output<typeof AstroConfigSchema> {
};
}
export type AsyncRendererComponentFn<U> = (Component: any, props: any, children: string | undefined, metadata?: AstroComponentMetadata) => Promise<U>;
export type AsyncRendererComponentFn<U> = (
Component: any,
props: any,
children: string | undefined,
metadata?: AstroComponentMetadata
) => Promise<U>;
/** Generic interface for a component (Astro, Svelte, React, etc.) */
export interface ComponentInstance {
@ -578,7 +586,9 @@ export interface MarkdownInstance<T extends Record<string, any>> {
getHeaders(): Promise<{ depth: number; slug: string; text: string }[]>;
}
export type GetHydrateCallback = () => Promise<(element: Element, innerHTML: string | null) => void>;
export type GetHydrateCallback = () => Promise<
(element: Element, innerHTML: string | null) => void
>;
/**
* getStaticPaths() options
@ -606,14 +616,20 @@ export interface JSXTransformConfig {
plugins?: babel.PluginItem[];
}
export type JSXTransformFn = (options: { mode: string; ssr: boolean }) => Promise<JSXTransformConfig>;
export type JSXTransformFn = (options: {
mode: string;
ssr: boolean;
}) => Promise<JSXTransformConfig>;
export interface ManifestData {
routes: RouteData[];
}
export type MarkdownRenderOptions = [string | MarkdownParser, Record<string, any>];
export type MarkdownParser = (contents: string, options?: Record<string, any>) => MarkdownParserResponse | PromiseLike<MarkdownParserResponse>;
export type MarkdownParser = (
contents: string,
options?: Record<string, any>
) => MarkdownParserResponse | PromiseLike<MarkdownParserResponse>;
export interface MarkdownParserResponse {
frontmatter: {
@ -731,13 +747,23 @@ export interface AstroIntegration {
// more generalized. Consider the SSR use-case as well.
// injectElement: (stage: vite.HtmlTagDescriptor, element: string) => void;
}) => void;
'astro:config:done'?: (options: { config: AstroConfig; setAdapter: (adapter: AstroAdapter) => void }) => void | Promise<void>;
'astro:config:done'?: (options: {
config: AstroConfig;
setAdapter: (adapter: AstroAdapter) => void;
}) => void | Promise<void>;
'astro:server:setup'?: (options: { server: vite.ViteDevServer }) => void | Promise<void>;
'astro:server:start'?: (options: { address: AddressInfo }) => void | Promise<void>;
'astro:server:done'?: () => void | Promise<void>;
'astro:build:start'?: (options: { buildConfig: BuildConfig }) => void | Promise<void>;
'astro:build:setup'?: (options: { vite: ViteConfigWithSSR; target: 'client' | 'server' }) => void;
'astro:build:done'?: (options: { pages: { pathname: string }[]; dir: URL; routes: RouteData[] }) => void | Promise<void>;
'astro:build:setup'?: (options: {
vite: ViteConfigWithSSR;
target: 'client' | 'server';
}) => void;
'astro:build:done'?: (options: {
pages: { pathname: string }[];
dir: URL;
routes: RouteData[];
}) => void | Promise<void>;
};
}
@ -821,7 +847,11 @@ export interface SSRResult {
styles: Set<SSRElement>;
scripts: Set<SSRElement>;
links: Set<SSRElement>;
createAstro(Astro: AstroGlobalPartial, props: Record<string, any>, slots: Record<string, any> | null): AstroGlobal;
createAstro(
Astro: AstroGlobalPartial,
props: Record<string, any>,
slots: Record<string, any> | null
): AstroGlobal;
resolve: (s: string) => Promise<string>;
_metadata: SSRMetadata;
}

View file

@ -8,7 +8,11 @@ import * as path from 'path';
import { pathToFileURL } from 'url';
import * as fs from 'fs';
async function openAllDocuments(workspaceUri: URL, filePathsToIgnore: string[], checker: AstroCheck) {
async function openAllDocuments(
workspaceUri: URL,
filePathsToIgnore: string[],
checker: AstroCheck
) {
const files = await glob('**/*.astro', {
cwd: workspaceUri.pathname,
ignore: ['node_modules/**'].concat(filePathsToIgnore.map((ignore) => `${ignore}/**`)),
@ -79,7 +83,11 @@ export async function check(astroConfig: AstroConfig) {
diag.diagnostics.forEach((d) => {
switch (d.severity) {
case DiagnosticSeverity.Error: {
console.error(`${bold(cyan(path.relative(root.pathname, diag.filePath)))}:${bold(yellow(d.range.start.line))}:${bold(yellow(d.range.start.character))} - ${d.message}`);
console.error(
`${bold(cyan(path.relative(root.pathname, diag.filePath)))}:${bold(
yellow(d.range.start.line)
)}:${bold(yellow(d.range.start.character))} - ${d.message}`
);
let startOffset = offsetAt({ line: d.range.start.line, character: 0 }, diag.text);
let endOffset = offsetAt({ line: d.range.start.line + 1, character: 0 }, diag.text);
let str = diag.text.substring(startOffset, endOffset - 1);

View file

@ -14,4 +14,5 @@ export async function generate(ast: t.File) {
return code;
}
export const parse = (code: string) => parser.parse(code, { sourceType: 'unambiguous', plugins: ['typescript'] });
export const parse = (code: string) =>
parser.parse(code, { sourceType: 'unambiguous', plugins: ['typescript'] });

View file

@ -50,7 +50,9 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO
configURL = await resolveConfigURL({ cwd, flags });
if (configURL?.pathname.endsWith('package.json')) {
throw new Error(`Unable to use astro add with package.json#astro configuration! Try migrating to \`astro.config.mjs\` and try again.`);
throw new Error(
`Unable to use astro add with package.json#astro configuration! Try migrating to \`astro.config.mjs\` and try again.`
);
}
applyPolyfill();
@ -102,7 +104,13 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO
debug('add', 'Parsed astro config');
const defineConfig = t.identifier('defineConfig');
ensureImport(ast, t.importDeclaration([t.importSpecifier(defineConfig, defineConfig)], t.stringLiteral('astro/config')));
ensureImport(
ast,
t.importDeclaration(
[t.importSpecifier(defineConfig, defineConfig)],
t.stringLiteral('astro/config')
)
);
wrapDefaultExport(ast, defineConfig);
debug('add', 'Astro config ensured `defineConfig`');
@ -136,9 +144,13 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO
case UpdateResult.none: {
const pkgURL = new URL('./package.json', configURL);
if (existsSync(fileURLToPath(pkgURL))) {
const { dependencies = {}, devDependencies = {} } = await fs.readFile(fileURLToPath(pkgURL)).then((res) => JSON.parse(res.toString()));
const { dependencies = {}, devDependencies = {} } = await fs
.readFile(fileURLToPath(pkgURL))
.then((res) => JSON.parse(res.toString()));
const deps = Object.keys(Object.assign(dependencies, devDependencies));
const missingDeps = integrations.filter((integration) => !deps.includes(integration.packageName));
const missingDeps = integrations.filter(
(integration) => !deps.includes(integration.packageName)
);
if (missingDeps.length === 0) {
info(logging, null, msg.success(`Configuration up-to-date.`));
return;
@ -156,7 +168,11 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO
case UpdateResult.updated: {
const len = integrations.length;
if (integrations.find((integration) => integration.id === 'tailwind')) {
const possibleConfigFiles = ['./tailwind.config.cjs', './tailwind.config.mjs', './tailwind.config.js'].map((p) => fileURLToPath(new URL(p, configURL)));
const possibleConfigFiles = [
'./tailwind.config.cjs',
'./tailwind.config.mjs',
'./tailwind.config.js',
].map((p) => fileURLToPath(new URL(p, configURL)));
let alreadyConfigured = false;
for (const possibleConfigPath of possibleConfigFiles) {
if (existsSync(possibleConfigPath)) {
@ -165,9 +181,19 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO
}
}
if (!alreadyConfigured) {
info(logging, null, `\n ${magenta(`Astro will generate a minimal ${bold('./tailwind.config.cjs')} file.`)}\n`);
info(
logging,
null,
`\n ${magenta(
`Astro will generate a minimal ${bold('./tailwind.config.cjs')} file.`
)}\n`
);
if (await askToContinue({ flags })) {
await fs.writeFile(fileURLToPath(new URL('./tailwind.config.cjs', configURL)), CONSTS.TAILWIND_CONFIG_STUB, { encoding: 'utf-8' });
await fs.writeFile(
fileURLToPath(new URL('./tailwind.config.cjs', configURL)),
CONSTS.TAILWIND_CONFIG_STUB,
{ encoding: 'utf-8' }
);
debug('add', `Generated default ./tailwind.config.cjs file`);
}
} else {
@ -175,11 +201,24 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO
}
}
const list = integrations.map((integration) => ` - ${integration.packageName}`).join('\n');
info(logging, null, msg.success(`Added the following integration${len === 1 ? '' : 's'} to your project:\n${list}`));
info(
logging,
null,
msg.success(
`Added the following integration${len === 1 ? '' : 's'} to your project:\n${list}`
)
);
return;
}
case UpdateResult.cancelled: {
info(logging, null, msg.cancelled(`Dependencies ${bold('NOT')} installed.`, `Be sure to install them manually before continuing!`));
info(
logging,
null,
msg.cancelled(
`Dependencies ${bold('NOT')} installed.`,
`Be sure to install them manually before continuing!`
)
);
return;
}
case UpdateResult.failure: {
@ -193,7 +232,8 @@ async function parseAstroConfig(configURL: URL): Promise<t.File> {
const result = parse(source);
if (!result) throw new Error('Unknown error parsing astro config');
if (result.errors.length > 0) throw new Error('Error parsing astro config: ' + JSON.stringify(result.errors));
if (result.errors.length > 0)
throw new Error('Error parsing astro config: ' + JSON.stringify(result.errors));
return result;
}
@ -217,7 +257,13 @@ Documentation: https://docs.astro.build/en/guides/integrations-guide/`;
async function addIntegration(ast: t.File, integration: IntegrationInfo) {
const integrationId = t.identifier(toIdent(integration.id));
ensureImport(ast, t.importDeclaration([t.importDefaultSpecifier(integrationId)], t.stringLiteral(integration.packageName)));
ensureImport(
ast,
t.importDeclaration(
[t.importDefaultSpecifier(integrationId)],
t.stringLiteral(integration.packageName)
)
);
visit(ast, {
// eslint-disable-next-line @typescript-eslint/no-shadow
@ -241,14 +287,20 @@ async function addIntegration(ast: t.File, integration: IntegrationInfo) {
const integrationCall = t.callExpression(integrationId, []);
if (!integrationsProp) {
configObject.properties.push(t.objectProperty(t.identifier('integrations'), t.arrayExpression([integrationCall])));
configObject.properties.push(
t.objectProperty(t.identifier('integrations'), t.arrayExpression([integrationCall]))
);
return;
}
if (integrationsProp.value.type !== 'ArrayExpression') throw new Error('Unable to parse integrations');
if (integrationsProp.value.type !== 'ArrayExpression')
throw new Error('Unable to parse integrations');
const existingIntegrationCall = integrationsProp.value.elements.find(
(expr) => t.isCallExpression(expr) && t.isIdentifier(expr.callee) && expr.callee.name === integrationId.name
(expr) =>
t.isCallExpression(expr) &&
t.isIdentifier(expr.callee) &&
expr.callee.name === integrationId.name
);
if (existingIntegrationCall) return;
@ -265,7 +317,17 @@ const enum UpdateResult {
failure,
}
async function updateAstroConfig({ configURL, ast, flags, logging }: { configURL: URL; ast: t.File; flags: yargs.Arguments; logging: LogOptions }): Promise<UpdateResult> {
async function updateAstroConfig({
configURL,
ast,
flags,
logging,
}: {
configURL: URL;
ast: t.File;
flags: yargs.Arguments;
logging: LogOptions;
}): Promise<UpdateResult> {
const input = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' });
let output = await generate(ast);
const comment = '// https://astro.build/config';
@ -299,9 +361,18 @@ async function updateAstroConfig({ configURL, ast, flags, logging }: { configURL
diffed = diffed.replace(newContent, coloredOutput);
}
const message = `\n${boxen(diffed, { margin: 0.5, padding: 0.5, borderStyle: 'round', title: configURL.pathname.split('/').pop() })}\n`;
const message = `\n${boxen(diffed, {
margin: 0.5,
padding: 0.5,
borderStyle: 'round',
title: configURL.pathname.split('/').pop(),
})}\n`;
info(logging, null, `\n ${magenta('Astro will make the following changes to your config file:')}\n${message}`);
info(
logging,
null,
`\n ${magenta('Astro will make the following changes to your config file:')}\n${message}`
);
if (await askToContinue({ flags })) {
await fs.writeFile(fileURLToPath(configURL), output, { encoding: 'utf-8' });
@ -318,7 +389,13 @@ interface InstallCommand {
flags: string[];
dependencies: string[];
}
async function getInstallIntegrationsCommand({ integrations, cwd = process.cwd() }: { integrations: IntegrationInfo[]; cwd?: string }): Promise<InstallCommand | null> {
async function getInstallIntegrationsCommand({
integrations,
cwd = process.cwd(),
}: {
integrations: IntegrationInfo[];
cwd?: string;
}): Promise<InstallCommand | null> {
const pm = await preferredPM(cwd);
debug('add', `package manager: ${JSON.stringify(pm)}`);
if (!pm) return null;
@ -359,14 +436,30 @@ async function tryToInstallIntegrations({
info(logging, null);
return UpdateResult.none;
} else {
const coloredOutput = `${bold(installCommand.pm)} ${installCommand.command} ${installCommand.flags.join(' ')} ${cyan(installCommand.dependencies.join(' '))}`;
const message = `\n${boxen(coloredOutput, { margin: 0.5, padding: 0.5, borderStyle: 'round' })}\n`;
info(logging, null, `\n ${magenta('Astro will run the following command:')}\n ${dim('If you skip this step, you can always run it yourself later')}\n${message}`);
const coloredOutput = `${bold(installCommand.pm)} ${
installCommand.command
} ${installCommand.flags.join(' ')} ${cyan(installCommand.dependencies.join(' '))}`;
const message = `\n${boxen(coloredOutput, {
margin: 0.5,
padding: 0.5,
borderStyle: 'round',
})}\n`;
info(
logging,
null,
`\n ${magenta('Astro will run the following command:')}\n ${dim(
'If you skip this step, you can always run it yourself later'
)}\n${message}`
);
if (await askToContinue({ flags })) {
const spinner = ora('Installing dependencies...').start();
try {
await execa(installCommand.pm, [installCommand.command, ...installCommand.flags, ...installCommand.dependencies], { cwd });
await execa(
installCommand.pm,
[installCommand.command, ...installCommand.flags, ...installCommand.dependencies],
{ cwd }
);
spinner.succeed();
return UpdateResult.updated;
} catch (err) {
@ -405,7 +498,9 @@ export async function validateIntegrations(integrations: string[]): Promise<Inte
return res.json();
});
let dependencies: IntegrationInfo['dependencies'] = [[result['name'], `^${result['version']}`]];
let dependencies: IntegrationInfo['dependencies'] = [
[result['name'], `^${result['version']}`],
];
if (result['peerDependencies']) {
for (const peer in result['peerDependencies']) {

View file

@ -4,7 +4,12 @@ export function wrapDefaultExport(ast: t.File, functionIdentifier: t.Identifier)
visit(ast, {
ExportDefaultDeclaration(path) {
if (!t.isExpression(path.node.declaration)) return;
if (t.isCallExpression(path.node.declaration) && t.isIdentifier(path.node.declaration.callee) && path.node.declaration.callee.name === functionIdentifier.name) return;
if (
t.isCallExpression(path.node.declaration) &&
t.isIdentifier(path.node.declaration.callee) &&
path.node.declaration.callee.name === functionIdentifier.name
)
return;
path.node.declaration = t.callExpression(functionIdentifier, [path.node.declaration]);
},
});

View file

@ -1,4 +1,9 @@
import type { ComponentInstance, EndpointHandler, ManifestData, RouteData } from '../../@types/astro';
import type {
ComponentInstance,
EndpointHandler,
ManifestData,
RouteData,
} from '../../@types/astro';
import type { SSRManifest as Manifest, RouteInfo } from './types';
import type { LogOptions } from '../logger/core.js';
@ -9,7 +14,10 @@ import { matchRoute } from '../routing/match.js';
import { render } from '../render/core.js';
import { call as callEndpoint } from '../endpoint/index.js';
import { RouteCache } from '../render/route-cache.js';
import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } from '../render/ssr-element.js';
import {
createLinkStylesheetElementSet,
createModuleScriptElementWithSrcSet,
} from '../render/ssr-element.js';
import { prependForwardSlash } from '../path.js';
import { createRequest } from '../request.js';
@ -58,7 +66,11 @@ export class App {
}
}
async #renderPage(request: Request, routeData: RouteData, mod: ComponentInstance): Promise<Response> {
async #renderPage(
request: Request,
routeData: RouteData,
mod: ComponentInstance
): Promise<Response> {
const url = new URL(request.url);
const manifest = this.#manifest;
const renderers = manifest.renderers;
@ -105,7 +117,11 @@ export class App {
});
}
async #callEndpoint(request: Request, _routeData: RouteData, mod: ComponentInstance): Promise<Response> {
async #callEndpoint(
request: Request,
_routeData: RouteData,
mod: ComponentInstance
): Promise<Response> {
const url = new URL(request.url);
const handler = mod as unknown as EndpointHandler;
const result = await callEndpoint(handler, {

View file

@ -1,4 +1,10 @@
import type { RouteData, SerializedRouteData, MarkdownRenderOptions, ComponentInstance, SSRLoadedRenderer } from '../../@types/astro';
import type {
RouteData,
SerializedRouteData,
MarkdownRenderOptions,
ComponentInstance,
SSRLoadedRenderer,
} from '../../@types/astro';
export type ComponentPath = string;
@ -28,4 +34,7 @@ export type SerializedSSRManifest = Omit<SSRManifest, 'routes'> & {
routes: SerializedRouteInfo[];
};
export type AdapterCreateExports<T = any> = (manifest: SSRManifest, args?: T) => Record<string, any>;
export type AdapterCreateExports<T = any> = (
manifest: SSRManifest,
args?: T
) => Record<string, any>;

View file

@ -8,7 +8,11 @@ function getOutRoot(astroConfig: AstroConfig): URL {
return new URL('./', astroConfig.outDir);
}
export function getOutFolder(astroConfig: AstroConfig, pathname: string, routeType: RouteType): URL {
export function getOutFolder(
astroConfig: AstroConfig,
pathname: string,
routeType: RouteType
): URL {
const outRoot = getOutRoot(astroConfig);
// This is the root folder to write to.
@ -30,7 +34,12 @@ export function getOutFolder(astroConfig: AstroConfig, pathname: string, routeTy
}
}
export function getOutFile(astroConfig: AstroConfig, outFolder: URL, pathname: string, routeType: RouteType): URL {
export function getOutFile(
astroConfig: AstroConfig,
outFolder: URL,
pathname: string,
routeType: RouteType
): URL {
switch (routeType) {
case 'endpoint':
return new URL(npath.basename(pathname), outFolder);

View file

@ -4,7 +4,12 @@ import { bgGreen, black, cyan, dim, green, magenta } from 'kleur/colors';
import npath from 'path';
import type { OutputAsset, OutputChunk, RollupOutput } from 'rollup';
import { fileURLToPath } from 'url';
import type { AstroConfig, ComponentInstance, EndpointHandler, SSRLoadedRenderer } from '../../@types/astro';
import type {
AstroConfig,
ComponentInstance,
EndpointHandler,
SSRLoadedRenderer,
} from '../../@types/astro';
import type { BuildInternals } from '../../core/build/internal.js';
import { debug, info } from '../logger/core.js';
import { prependForwardSlash } from '../../core/path.js';
@ -12,7 +17,10 @@ import type { RenderOptions } from '../../core/render/core';
import { BEFORE_HYDRATION_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { call as callEndpoint } from '../endpoint/index.js';
import { render } from '../render/core.js';
import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } from '../render/ssr-element.js';
import {
createLinkStylesheetElementSet,
createModuleScriptElementWithSrcSet,
} from '../render/ssr-element.js';
import { getOutputFilename, isBuildingToSSR } from '../util.js';
import { getOutFile, getOutFolder } from './common.js';
import { eachPageData, getPageDataByComponent } from './internal.js';
@ -56,19 +64,30 @@ export function rootRelativeFacadeId(facadeId: string, astroConfig: AstroConfig)
}
// Determines of a Rollup chunk is an entrypoint page.
export function chunkIsPage(astroConfig: AstroConfig, output: OutputAsset | OutputChunk, internals: BuildInternals) {
export function chunkIsPage(
astroConfig: AstroConfig,
output: OutputAsset | OutputChunk,
internals: BuildInternals
) {
if (output.type !== 'chunk') {
return false;
}
const chunk = output as OutputChunk;
if (chunk.facadeModuleId) {
const facadeToEntryId = prependForwardSlash(rootRelativeFacadeId(chunk.facadeModuleId, astroConfig));
const facadeToEntryId = prependForwardSlash(
rootRelativeFacadeId(chunk.facadeModuleId, astroConfig)
);
return internals.entrySpecifierToBundleMap.has(facadeToEntryId);
}
return false;
}
export async function generatePages(result: RollupOutput, opts: StaticBuildOptions, internals: BuildInternals, facadeIdToPageDataMap: Map<string, PageBuildData>) {
export async function generatePages(
result: RollupOutput,
opts: StaticBuildOptions,
internals: BuildInternals,
facadeIdToPageDataMap: Map<string, PageBuildData>
) {
const timer = performance.now();
info(opts.logging, null, `\n${bgGreen(black(' generating static routes '))}`);
@ -101,7 +120,9 @@ async function generatePage(
const pageModule = ssrEntry.pageMap.get(pageData.component);
if (!pageModule) {
throw new Error(`Unable to find the module for ${pageData.component}. This is unexpected and likely a bug in Astro, please report.`);
throw new Error(
`Unable to find the module for ${pageData.component}. This is unexpected and likely a bug in Astro, please report.`
);
}
const generationOptions: Readonly<GeneratePathOptions> = {
@ -141,7 +162,11 @@ function addPageName(pathname: string, opts: StaticBuildOptions): void {
opts.pageNames.push(pathname.replace(/\/?$/, '/').replace(/^\//, ''));
}
async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: GeneratePathOptions) {
async function generatePath(
pathname: string,
opts: StaticBuildOptions,
gopts: GeneratePathOptions
) {
const { astroConfig, logging, origin, routeCache } = opts;
const { mod, internals, linkIds, hoistedId, pageData, renderers } = gopts;
@ -197,7 +222,9 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G
request: createRequest({ url, headers: new Headers(), logging }),
route: pageData.route,
routeCache,
site: astroConfig.site ? new URL(astroConfig.base, astroConfig.site).toString() : astroConfig.site,
site: astroConfig.site
? new URL(astroConfig.base, astroConfig.site).toString()
: astroConfig.site,
ssr: isBuildingToSSR(opts.astroConfig),
};

View file

@ -7,13 +7,25 @@ import { apply as applyPolyfill } from '../polyfill.js';
import { performance } from 'perf_hooks';
import * as vite from 'vite';
import { createVite, ViteConfigWithSSR } from '../create-vite.js';
import { debug, info, levels, timerMessage, warn, warnIfUsingExperimentalSSR } from '../logger/core.js';
import {
debug,
info,
levels,
timerMessage,
warn,
warnIfUsingExperimentalSSR,
} from '../logger/core.js';
import { nodeLogOptions } from '../logger/node.js';
import { createRouteManifest } from '../routing/index.js';
import { collectPagesData } from './page-data.js';
import { staticBuild } from './static-build.js';
import { RouteCache } from '../render/route-cache.js';
import { runHookBuildDone, runHookBuildStart, runHookConfigDone, runHookConfigSetup } from '../../integrations/index.js';
import {
runHookBuildDone,
runHookBuildStart,
runHookConfigDone,
runHookConfigSetup,
} from '../../integrations/index.js';
import { getTimeStat } from './util.js';
import { createSafeError, isBuildingToSSR } from '../util.js';
import { fixViteErrorMessage } from '../errors.js';
@ -24,7 +36,10 @@ export interface BuildOptions {
}
/** `astro build` */
export default async function build(config: AstroConfig, options: BuildOptions = { logging: nodeLogOptions }): Promise<void> {
export default async function build(
config: AstroConfig,
options: BuildOptions = { logging: nodeLogOptions }
): Promise<void> {
applyPolyfill();
const builder = new AstroBuilder(config, options);
await builder.run();
@ -46,7 +61,9 @@ class AstroBuilder {
this.config = config;
this.logging = options.logging;
this.routeCache = new RouteCache(this.logging);
this.origin = config.site ? new URL(config.site).origin : `http://localhost:${config.server.port}`;
this.origin = config.site
? new URL(config.site).origin
: `http://localhost:${config.server.port}`;
this.manifest = createRouteManifest({ config }, this.logging);
this.timer = {};
}
@ -76,7 +93,13 @@ class AstroBuilder {
}
/** Run the build logic. build() is marked private because usage should go through ".run()" */
private async build({ viteConfig, viteServer }: { viteConfig: ViteConfigWithSSR; viteServer: vite.ViteDevServer }) {
private async build({
viteConfig,
viteServer,
}: {
viteConfig: ViteConfigWithSSR;
viteServer: vite.ViteDevServer;
}) {
const { origin } = this;
const buildConfig: BuildConfig = {
client: new URL('./client/', this.config.outDir),
@ -118,7 +141,11 @@ class AstroBuilder {
// Bundle the assets in your final build: This currently takes the HTML output
// of every page (stored in memory) and bundles the assets pointed to on those pages.
this.timer.buildStart = performance.now();
info(this.logging, 'build', colors.dim(`Completed in ${getTimeStat(this.timer.init, performance.now())}.`));
info(
this.logging,
'build',
colors.dim(`Completed in ${getTimeStat(this.timer.init, performance.now())}.`)
);
await staticBuild({
allPages,
@ -145,11 +172,20 @@ class AstroBuilder {
// You're done! Time to clean up.
await viteServer.close();
await runHookBuildDone({ config: this.config, pages: pageNames, routes: Object.values(allPages).map((pd) => pd.route) });
await runHookBuildDone({
config: this.config,
pages: pageNames,
routes: Object.values(allPages).map((pd) => pd.route),
});
if (this.logging.level && levels[this.logging.level] <= levels['info']) {
const buildMode = isBuildingToSSR(this.config) ? 'ssr' : 'static';
await this.printStats({ logging: this.logging, timeStart: this.timer.init, pageCount: pageNames.length, buildMode });
await this.printStats({
logging: this.logging,
timeStart: this.timer.init,
pageCount: pageNames.length,
buildMode,
});
}
}
@ -165,7 +201,17 @@ class AstroBuilder {
}
/** Stats */
private async printStats({ logging, timeStart, pageCount, buildMode }: { logging: LogOptions; timeStart: number; pageCount: number; buildMode: 'static' | 'ssr' }) {
private async printStats({
logging,
timeStart,
pageCount,
buildMode,
}: {
logging: LogOptions;
timeStart: number;
pageCount: number;
buildMode: 'static' | 'ssr';
}) {
const buildTime = performance.now() - timeStart;
const total = getTimeStat(timeStart, performance.now());

View file

@ -76,13 +76,22 @@ export function createBuildInternals(): BuildInternals {
};
}
export function trackPageData(internals: BuildInternals, component: string, pageData: PageBuildData, componentModuleId: string, componentURL: URL): void {
export function trackPageData(
internals: BuildInternals,
component: string,
pageData: PageBuildData,
componentModuleId: string,
componentURL: URL
): void {
pageData.moduleSpecifier = componentModuleId;
internals.pagesByComponent.set(component, pageData);
internals.pagesByViteID.set(viteID(componentURL), pageData);
}
export function* getPageDatasByChunk(internals: BuildInternals, chunk: RenderedChunk): Generator<PageBuildData, void, unknown> {
export function* getPageDatasByChunk(
internals: BuildInternals,
chunk: RenderedChunk
): Generator<PageBuildData, void, unknown> {
const pagesByViteID = internals.pagesByViteID;
for (const [modulePath] of Object.entries(chunk.modules)) {
if (pagesByViteID.has(modulePath)) {
@ -91,14 +100,20 @@ export function* getPageDatasByChunk(internals: BuildInternals, chunk: RenderedC
}
}
export function getPageDataByComponent(internals: BuildInternals, component: string): PageBuildData | undefined {
export function getPageDataByComponent(
internals: BuildInternals,
component: string
): PageBuildData | undefined {
if (internals.pagesByComponent.has(component)) {
return internals.pagesByComponent.get(component);
}
return undefined;
}
export function getPageDataByViteID(internals: BuildInternals, viteid: ViteID): PageBuildData | undefined {
export function getPageDataByViteID(
internals: BuildInternals,
viteid: ViteID
): PageBuildData | undefined {
if (internals.pagesByViteID.has(viteid)) {
return internals.pagesByViteID.get(viteid);
}

View file

@ -28,7 +28,9 @@ export interface CollectPagesDataResult {
}
// Examines the routes and returns a collection of information about each page.
export async function collectPagesData(opts: CollectPagesDataOptions): Promise<CollectPagesDataResult> {
export async function collectPagesData(
opts: CollectPagesDataOptions
): Promise<CollectPagesDataResult> {
const { astroConfig, logging, manifest, origin, routeCache, viteServer } = opts;
const assets: Record<string, string> = {};
@ -75,7 +77,10 @@ export async function collectPagesData(opts: CollectPagesDataOptions): Promise<C
clearInterval(routeCollectionLogTimeout);
if (buildMode === 'static') {
const html = `${route.pathname}`.replace(/\/?$/, '/index.html');
debug('build', `├── ${colors.bold(colors.green('✔'))} ${route.component}${colors.yellow(html)}`);
debug(
'build',
`├── ${colors.bold(colors.green('✔'))} ${route.component}${colors.yellow(html)}`
);
} else {
debug('build', `├── ${colors.bold(colors.green('✔'))} ${route.component}`);
}
@ -93,7 +98,12 @@ export async function collectPagesData(opts: CollectPagesDataOptions): Promise<C
const result = await getStaticPathsForRoute(opts, route)
.then((_result) => {
const label = _result.staticPaths.length === 1 ? 'page' : 'pages';
debug('build', `├── ${colors.bold(colors.green('✔'))} ${route.component}${colors.magenta(`[${_result.staticPaths.length} ${label}]`)}`);
debug(
'build',
`├── ${colors.bold(colors.green('✔'))} ${route.component}${colors.magenta(
`[${_result.staticPaths.length} ${label}]`
)}`
);
return _result;
})
.catch((err) => {
@ -108,7 +118,9 @@ export async function collectPagesData(opts: CollectPagesDataOptions): Promise<C
if (content) {
const rssFile = new URL(url.replace(/^\/?/, './'), astroConfig.outDir);
if (assets[fileURLToPath(rssFile)]) {
throw new Error(`[getStaticPaths] RSS feed ${url} already exists.\nUse \`rss(data, {url: '...'})\` to choose a unique, custom URL. (${route.component})`);
throw new Error(
`[getStaticPaths] RSS feed ${url} already exists.\nUse \`rss(data, {url: '...'})\` to choose a unique, custom URL. (${route.component})`
);
}
assets[fileURLToPath(rssFile)] = content;
}
@ -124,7 +136,9 @@ export async function collectPagesData(opts: CollectPagesDataOptions): Promise<C
assets[fileURLToPath(stylesheetFile)] = content;
}
}
const finalPaths = result.staticPaths.map((staticPath) => staticPath.params && route.generate(staticPath.params)).filter(Boolean);
const finalPaths = result.staticPaths
.map((staticPath) => staticPath.params && route.generate(staticPath.params))
.filter(Boolean);
allPages[route.component] = {
component: route.component,
route,
@ -146,7 +160,10 @@ export async function collectPagesData(opts: CollectPagesDataOptions): Promise<C
return { assets, allPages };
}
async function getStaticPathsForRoute(opts: CollectPagesDataOptions, route: RouteData): Promise<RouteCacheEntry> {
async function getStaticPathsForRoute(
opts: CollectPagesDataOptions,
route: RouteData
): Promise<RouteCacheEntry> {
const { astroConfig, logging, routeCache, ssr, viteServer } = opts;
if (!viteServer) throw new Error(`vite.createServer() not called!`);
const filePath = new URL(`./${route.component}`, astroConfig.root);

View file

@ -64,7 +64,9 @@ export async function staticBuild(opts: StaticBuildOptions) {
// Any hydration directive like astro/client/idle.js
...metadata.hydrationDirectiveSpecifiers(),
// The client path for each renderer
...renderers.filter((renderer) => !!renderer.clientEntrypoint).map((renderer) => renderer.clientEntrypoint!),
...renderers
.filter((renderer) => !!renderer.clientEntrypoint)
.map((renderer) => renderer.clientEntrypoint!),
]);
// Add hoisted scripts
@ -149,7 +151,8 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
}),
...(viteConfig.plugins || []),
// SSR needs to be last
isBuildingToSSR(opts.astroConfig) && vitePluginSSR(opts, internals, opts.astroConfig._ctx.adapter!),
isBuildingToSSR(opts.astroConfig) &&
vitePluginSSR(opts, internals, opts.astroConfig._ctx.adapter!),
],
publicDir: ssr ? false : viteConfig.publicDir,
root: viteConfig.root,
@ -166,7 +169,11 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
return await vite.build(viteBuildConfig);
}
async function clientBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set<string>) {
async function clientBuild(
opts: StaticBuildOptions,
internals: BuildInternals,
input: Set<string>
) {
const { astroConfig, viteConfig } = opts;
const timer = performance.now();
const ssr = isBuildingToSSR(astroConfig);
@ -260,7 +267,9 @@ async function copyFiles(fromFolder: URL, toFolder: URL) {
async function ssrMoveAssets(opts: StaticBuildOptions) {
info(opts.logging, 'build', 'Rearranging server assets...');
const serverRoot = opts.buildConfig.staticMode ? opts.buildConfig.client : opts.buildConfig.server;
const serverRoot = opts.buildConfig.staticMode
? opts.buildConfig.client
: opts.buildConfig.server;
const clientRoot = opts.buildConfig.client;
const serverAssets = new URL('./assets/', serverRoot);
const clientAssets = new URL('./assets/', clientRoot);

View file

@ -1,5 +1,12 @@
import type { ComponentPreload } from '../render/dev/index';
import type { AstroConfig, BuildConfig, ManifestData, RouteData, ComponentInstance, SSRLoadedRenderer } from '../../@types/astro';
import type {
AstroConfig,
BuildConfig,
ManifestData,
RouteData,
ComponentInstance,
SSRLoadedRenderer,
} from '../../@types/astro';
import type { ViteConfigWithSSR } from '../../create-vite';
import type { LogOptions } from '../../logger';
import type { RouteCache } from '../../render/route-cache.js';

View file

@ -8,7 +8,10 @@ function virtualHoistedEntry(id: string) {
return id.endsWith('.astro/hoisted.js') || id.endsWith('.md/hoisted.js');
}
export function vitePluginHoistedScripts(astroConfig: AstroConfig, internals: BuildInternals): VitePlugin {
export function vitePluginHoistedScripts(
astroConfig: AstroConfig,
internals: BuildInternals
): VitePlugin {
return {
name: '@astro/rollup-plugin-astro-hoisted-scripts',
@ -35,7 +38,11 @@ export function vitePluginHoistedScripts(astroConfig: AstroConfig, internals: Bu
// Find all page entry points and create a map of the entry point to the hashed hoisted script.
// This is used when we render so that we can add the script to the head.
for (const [id, output] of Object.entries(bundle)) {
if (output.type === 'chunk' && output.facadeModuleId && virtualHoistedEntry(output.facadeModuleId)) {
if (
output.type === 'chunk' &&
output.facadeModuleId &&
virtualHoistedEntry(output.facadeModuleId)
) {
const facadeId = output.facadeModuleId!;
const pathname = facadeId.slice(0, facadeId.length - '/hoisted.js'.length);

View file

@ -15,7 +15,11 @@ export const virtualModuleId = '@astrojs-ssr-virtual-entry';
const resolvedVirtualModuleId = '\0' + virtualModuleId;
const manifestReplace = '@@ASTRO_MANIFEST_REPLACE@@';
export function vitePluginSSR(buildOpts: StaticBuildOptions, internals: BuildInternals, adapter: AstroAdapter): VitePlugin {
export function vitePluginSSR(
buildOpts: StaticBuildOptions,
internals: BuildInternals,
adapter: AstroAdapter
): VitePlugin {
return {
name: '@astrojs/vite-plugin-astro-ssr',
enforce: 'post',
@ -91,7 +95,8 @@ function buildManifest(opts: StaticBuildOptions, internals: BuildInternals): Ser
// HACK! Patch this special one.
const entryModules = Object.fromEntries(internals.entrySpecifierToBundleMap.entries());
entryModules[BEFORE_HYDRATION_SCRIPT_ID] = 'data:text/javascript;charset=utf-8,//[no before-hydration script]';
entryModules[BEFORE_HYDRATION_SCRIPT_ID] =
'data:text/javascript;charset=utf-8,//[no before-hydration script]';
const ssrManifest: SerializedSSRManifest = {
routes,

View file

@ -124,7 +124,9 @@ export const AstroConfigSchema = z.object({
// preprocess
(val) => (Array.isArray(val) ? val.flat(Infinity).filter(Boolean) : val),
// validate
z.array(z.object({ name: z.string(), hooks: z.object({}).passthrough().default({}) })).default([])
z
.array(z.object({ name: z.string(), hooks: z.object({}).passthrough().default({}) }))
.default([])
),
style: z
.object({
@ -168,7 +170,11 @@ export const AstroConfigSchema = z.object({
});
/** Turn raw config values into normalized values */
export async function validateConfig(userConfig: any, root: string, cmd: string): Promise<AstroConfig> {
export async function validateConfig(
userConfig: any,
root: string,
cmd: string
): Promise<AstroConfig> {
const fileProtocolRoot = pathToFileURL(root + path.sep);
// Manual deprecation checks
/* eslint-disable no-console */
@ -176,8 +182,12 @@ export async function validateConfig(userConfig: any, root: string, cmd: string)
console.error('Astro "renderers" are now "integrations"!');
console.error('Update your configuration and install new dependencies:');
try {
const rendererKeywords = userConfig.renderers.map((r: string) => r.replace('@astrojs/renderer-', ''));
const rendererImports = rendererKeywords.map((r: string) => ` import ${r} from '@astrojs/${r === 'solid' ? 'solid-js' : r}';`).join('\n');
const rendererKeywords = userConfig.renderers.map((r: string) =>
r.replace('@astrojs/renderer-', '')
);
const rendererImports = rendererKeywords
.map((r: string) => ` import ${r} from '@astrojs/${r === 'solid' ? 'solid-js' : r}';`)
.join('\n');
const rendererIntegrations = rendererKeywords.map((r: string) => ` ${r}(),`).join('\n');
console.error('');
console.error(colors.dim(' // astro.config.js'));
@ -208,7 +218,9 @@ export async function validateConfig(userConfig: any, root: string, cmd: string)
}
}
if (oldConfig) {
throw new Error(`Legacy configuration detected. Please update your configuration to the new format!\nSee https://astro.build/config for more information.`);
throw new Error(
`Legacy configuration detected. Please update your configuration to the new format!\nSee https://astro.build/config for more information.`
);
}
/* eslint-enable no-console */
@ -233,7 +245,8 @@ export async function validateConfig(userConfig: any, root: string, cmd: string)
.transform((val) => new URL(appendForwardSlash(val), fileProtocolRoot)),
server: z.preprocess(
// preprocess
(val) => (typeof val === 'function' ? val({ command: cmd === 'dev' ? 'dev' : 'preview' }) : val),
(val) =>
typeof val === 'function' ? val({ command: cmd === 'dev' ? 'dev' : 'preview' }) : val,
// validate
z
.object({
@ -265,7 +278,10 @@ export async function validateConfig(userConfig: any, root: string, cmd: string)
_ctx: { scripts: [], renderers: [], adapter: undefined },
};
// Final-Pass Validation (perform checks that require the full config object)
if (!result.experimental?.integrations && !result.integrations.every((int) => int.name.startsWith('@astrojs/'))) {
if (
!result.experimental?.integrations &&
!result.integrations.every((int) => int.name.startsWith('@astrojs/'))
) {
throw new Error(
[
`Astro integrations are still experimental.`,
@ -288,9 +304,11 @@ function resolveFlags(flags: Partial<Flags>): CLIFlags {
site: typeof flags.site === 'string' ? flags.site : undefined,
port: typeof flags.port === 'number' ? flags.port : undefined,
config: typeof flags.config === 'string' ? flags.config : undefined,
host: typeof flags.host === 'string' || typeof flags.host === 'boolean' ? flags.host : undefined,
host:
typeof flags.host === 'string' || typeof flags.host === 'boolean' ? flags.host : undefined,
experimentalSsr: typeof flags.experimentalSsr === 'boolean' ? flags.experimentalSsr : false,
experimentalIntegrations: typeof flags.experimentalIntegrations === 'boolean' ? flags.experimentalIntegrations : false,
experimentalIntegrations:
typeof flags.experimentalIntegrations === 'boolean' ? flags.experimentalIntegrations : false,
drafts: typeof flags.drafts === 'boolean' ? flags.drafts : false,
};
}
@ -301,15 +319,21 @@ function mergeCLIFlags(astroConfig: AstroUserConfig, flags: CLIFlags, cmd: strin
astroConfig.experimental = astroConfig.experimental || {};
astroConfig.markdown = astroConfig.markdown || {};
if (typeof flags.site === 'string') astroConfig.site = flags.site;
if (typeof flags.experimentalSsr === 'boolean') astroConfig.experimental.ssr = flags.experimentalSsr;
if (typeof flags.experimentalIntegrations === 'boolean') astroConfig.experimental.integrations = flags.experimentalIntegrations;
if (typeof flags.experimentalSsr === 'boolean')
astroConfig.experimental.ssr = flags.experimentalSsr;
if (typeof flags.experimentalIntegrations === 'boolean')
astroConfig.experimental.integrations = flags.experimentalIntegrations;
if (typeof flags.drafts === 'boolean') astroConfig.markdown.drafts = flags.drafts;
if (typeof flags.port === 'number') {
// @ts-expect-error astroConfig.server may be a function, but TS doesn't like attaching properties to a function.
// TODO: Come back here and refactor to remove this expected error.
if (typeof flags.port === 'number') astroConfig.server.port = flags.port;
astroConfig.server.port = flags.port;
}
if (typeof flags.host === 'string' || typeof flags.host === 'boolean') {
// @ts-expect-error astroConfig.server may be a function, but TS doesn't like attaching properties to a function.
// TODO: Come back here and refactor to remove this expected error.
if (typeof flags.host === 'string' || typeof flags.host === 'boolean') astroConfig.server.host = flags.host;
astroConfig.server.host = flags.host;
}
return astroConfig;
}
@ -325,7 +349,9 @@ interface LoadConfigOptions {
* Note: currently the same as loadConfig but only returns the `filePath`
* instead of the resolved config
*/
export async function resolveConfigURL(configOptions: Pick<LoadConfigOptions, 'cwd' | 'flags'>): Promise<URL | undefined> {
export async function resolveConfigURL(
configOptions: Pick<LoadConfigOptions, 'cwd' | 'flags'>
): Promise<URL | undefined> {
const root = configOptions.cwd ? path.resolve(configOptions.cwd) : process.cwd();
const flags = resolveFlags(configOptions.flags || {});
let userConfigPath: string | undefined;
@ -363,14 +389,23 @@ export async function loadConfig(configOptions: LoadConfigOptions): Promise<Astr
}
/** Attempt to resolve an Astro configuration object. Normalize, validate, and return. */
export async function resolveConfig(userConfig: AstroUserConfig, root: string, flags: CLIFlags = {}, cmd: string): Promise<AstroConfig> {
export async function resolveConfig(
userConfig: AstroUserConfig,
root: string,
flags: CLIFlags = {},
cmd: string
): Promise<AstroConfig> {
const mergedConfig = mergeCLIFlags(userConfig, flags, cmd);
const validatedConfig = await validateConfig(mergedConfig, root, cmd);
return validatedConfig;
}
function mergeConfigRecursively(defaults: Record<string, any>, overrides: Record<string, any>, rootPath: string) {
function mergeConfigRecursively(
defaults: Record<string, any>,
overrides: Record<string, any>,
rootPath: string
) {
const merged: Record<string, any> = { ...defaults };
for (const key in overrides) {
const value = overrides[key];
@ -405,6 +440,10 @@ function mergeConfigRecursively(defaults: Record<string, any>, overrides: Record
return merged;
}
export function mergeConfig(defaults: Record<string, any>, overrides: Record<string, any>, isRoot = true): Record<string, any> {
export function mergeConfig(
defaults: Record<string, any>,
overrides: Record<string, any>,
isRoot = true
): Record<string, any> {
return mergeConfigRecursively(defaults, overrides, isRoot ? '' : '.');
}

View file

@ -43,7 +43,10 @@ interface CreateViteOptions {
}
/** Return a common starting point for all Vite actions */
export async function createVite(commandConfig: ViteConfigWithSSR, { astroConfig, logging, mode }: CreateViteOptions): Promise<ViteConfigWithSSR> {
export async function createVite(
commandConfig: ViteConfigWithSSR,
{ astroConfig, logging, mode }: CreateViteOptions
): Promise<ViteConfigWithSSR> {
// Scan for any third-party Astro packages. Vite needs these to be passed to `ssr.noExternal`.
const astroPackages = await getAstroPackages(astroConfig);
// Start with the Vite configuration that Astro core needs
@ -72,7 +75,10 @@ export async function createVite(commandConfig: ViteConfigWithSSR, { astroConfig
envPrefix: 'PUBLIC_',
server: {
force: true, // force dependency rebuild (TODO: enabled only while next is unstable; eventually only call in "production" mode?)
hmr: process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'production' ? false : undefined, // disable HMR for test
hmr:
process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'production'
? false
: undefined, // disable HMR for test
// handle Vite URLs
proxy: {
// add proxies here
@ -127,7 +133,11 @@ async function getAstroPackages({ root }: AstroConfig): Promise<string[]> {
const depPkgPath = fileURLToPath(depPkgUrl);
if (!fs.existsSync(depPkgPath)) return false;
const { dependencies = {}, peerDependencies = {}, keywords = [] } = JSON.parse(fs.readFileSync(depPkgPath, 'utf-8'));
const {
dependencies = {},
peerDependencies = {},
keywords = [],
} = JSON.parse(fs.readFileSync(depPkgPath, 'utf-8'));
// Attempt: package relies on `astro`. ✅ Definitely an Astro package
if (peerDependencies.astro || dependencies.astro) return true;
// Attempt: package is tagged with `astro` or `astro-component`. ✅ Likely a community package
@ -181,7 +191,10 @@ function isCommonNotAstro(dep: string): boolean {
return (
COMMON_DEPENDENCIES_NOT_ASTRO.includes(dep) ||
COMMON_PREFIXES_NOT_ASTRO.some(
(prefix) => (prefix.startsWith('@') ? dep.startsWith(prefix) : dep.substring(dep.lastIndexOf('/') + 1).startsWith(prefix)) // check prefix omitting @scope/
(prefix) =>
prefix.startsWith('@')
? dep.startsWith(prefix)
: dep.substring(dep.lastIndexOf('/') + 1).startsWith(prefix) // check prefix omitting @scope/
)
);
}

View file

@ -2,7 +2,13 @@ import type { AddressInfo } from 'net';
import { performance } from 'perf_hooks';
import * as vite from 'vite';
import type { AstroConfig } from '../../@types/astro';
import { runHookConfigDone, runHookConfigSetup, runHookServerDone, runHookServerSetup, runHookServerStart } from '../../integrations/index.js';
import {
runHookConfigDone,
runHookConfigSetup,
runHookServerDone,
runHookServerSetup,
runHookServerStart,
} from '../../integrations/index.js';
import { createVite } from '../create-vite.js';
import { info, LogOptions, warn, warnIfUsingExperimentalSSR } from '../logger/core.js';
import { nodeLogOptions } from '../logger/node.js';
@ -19,7 +25,10 @@ export interface DevServer {
}
/** `astro dev` */
export default async function dev(config: AstroConfig, options: DevOptions = { logging: nodeLogOptions }): Promise<DevServer> {
export default async function dev(
config: AstroConfig,
options: DevOptions = { logging: nodeLogOptions }
): Promise<DevServer> {
const devStart = performance.now();
applyPolyfill();
config = await runHookConfigSetup({ config, command: 'dev' });
@ -39,7 +48,17 @@ export default async function dev(config: AstroConfig, options: DevOptions = { l
const devServerAddressInfo = viteServer.httpServer!.address() as AddressInfo;
const site = config.site ? new URL(config.base, config.site) : undefined;
info(options.logging, null, msg.devStart({ startupTime: performance.now() - devStart, config, devServerAddressInfo, site, https: !!viteConfig.server?.https }));
info(
options.logging,
null,
msg.devStart({
startupTime: performance.now() - devStart,
config,
devServerAddressInfo,
site,
https: !!viteConfig.server?.https,
})
);
const currentVersion = process.env.PACKAGE_VERSION ?? '0.0.0';
if (currentVersion.includes('-')) {

View file

@ -3,7 +3,10 @@ import type { RenderOptions } from '../render/core';
import { renderEndpoint } from '../../runtime/server/index.js';
import { getParamsAndProps, GetParamsAndPropsError } from '../render/core.js';
export type EndpointOptions = Pick<RenderOptions, 'logging' | 'origin' | 'request' | 'route' | 'routeCache' | 'pathname' | 'route' | 'site' | 'ssr'>;
export type EndpointOptions = Pick<
RenderOptions,
'logging' | 'origin' | 'request' | 'route' | 'routeCache' | 'pathname' | 'route' | 'site' | 'ssr'
>;
type EndpointCallResult =
| {
@ -15,11 +18,16 @@ type EndpointCallResult =
response: Response;
};
export async function call(mod: EndpointHandler, opts: EndpointOptions): Promise<EndpointCallResult> {
export async function call(
mod: EndpointHandler,
opts: EndpointOptions
): Promise<EndpointCallResult> {
const paramsAndPropsResp = await getParamsAndProps({ ...opts, mod: mod as any });
if (paramsAndPropsResp === GetParamsAndPropsError.NoMatchingStaticPath) {
throw new Error(`[getStaticPath] route pattern matched, but no matching static path found. (${opts.pathname})`);
throw new Error(
`[getStaticPath] route pattern matched, but no matching static path found. (${opts.pathname})`
);
}
const [params] = paramsAndPropsResp;

View file

@ -47,7 +47,12 @@ export const levels: Record<LoggerLevel, number> = {
};
/** Full logging API */
export function log(opts: LogOptions, level: LoggerLevel, type: string | null, ...args: Array<any>) {
export function log(
opts: LogOptions,
level: LoggerLevel,
type: string | null,
...args: Array<any>
) {
const logLevel = opts.level;
const dest = opts.dest;
const event: LogMessage = {
@ -120,7 +125,8 @@ if (typeof process !== 'undefined') {
/** Print out a timer message for debug() */
export function timerMessage(message: string, startTime: number = Date.now()) {
let timeDiff = Date.now() - startTime;
let timeDisplay = timeDiff < 750 ? `${Math.round(timeDiff)}ms` : `${(timeDiff / 1000).toFixed(1)}s`;
let timeDisplay =
timeDiff < 750 ? `${Math.round(timeDiff)}ms` : `${(timeDiff / 1000).toFixed(1)}s`;
return `${message} ${dim(timeDisplay)}`;
}

View file

@ -127,5 +127,8 @@ export const logger = {
export function enableVerboseLogging() {
//debugPackage.enable('*,-babel');
debug('cli', '--verbose flag enabled! Enabling: DEBUG="*,-babel"');
debug('cli', 'Tip: Set the DEBUG env variable directly for more control. Example: "DEBUG=astro:*,vite:* astro build".');
debug(
'cli',
'Tip: Set the DEBUG env variable directly for more control. Example: "DEBUG=astro:*,vite:* astro build".'
);
}

View file

@ -2,7 +2,20 @@
* Dev server messages (organized here to prevent clutter)
*/
import { bold, dim, red, green, underline, yellow, bgYellow, cyan, bgGreen, black, bgRed, bgWhite } from 'kleur/colors';
import {
bold,
dim,
red,
green,
underline,
yellow,
bgYellow,
cyan,
bgGreen,
black,
bgRed,
bgWhite,
} from 'kleur/colors';
import os from 'os';
import type { AddressInfo } from 'net';
import type { AstroConfig } from '../@types/astro';
@ -13,13 +26,23 @@ import { emoji, getLocalAddress, padMultilineString } from './util.js';
const PREFIX_PADDING = 6;
/** Display */
export function req({ url, statusCode, reqTime }: { url: string; statusCode: number; reqTime?: number }): string {
export function req({
url,
statusCode,
reqTime,
}: {
url: string;
statusCode: number;
reqTime?: number;
}): string {
let color = dim;
if (statusCode >= 500) color = red;
else if (statusCode >= 400) color = yellow;
else if (statusCode >= 300) color = dim;
else if (statusCode >= 200) color = green;
return `${bold(color(`${statusCode}`.padStart(PREFIX_PADDING)))} ${url.padStart(40)} ${reqTime ? dim(Math.round(reqTime) + 'ms') : ''}`.trim();
return `${bold(color(`${statusCode}`.padStart(PREFIX_PADDING)))} ${url.padStart(40)} ${
reqTime ? dim(Math.round(reqTime) + 'ms') : ''
}`.trim();
}
export function reload({ file }: { file: string }): string {
@ -53,17 +76,23 @@ export function devStart({
const { address: networkAddress, port } = devServerAddressInfo;
const localAddress = getLocalAddress(networkAddress, config.server.host);
const networkLogging = getNetworkLogging(config.server.host);
const toDisplayUrl = (hostname: string) => `${https ? 'https' : 'http'}://${hostname}:${port}${rootPath}`;
const toDisplayUrl = (hostname: string) =>
`${https ? 'https' : 'http'}://${hostname}:${port}${rootPath}`;
let addresses = [];
if (networkLogging === 'none') {
addresses = [`${localPrefix}${bold(cyan(toDisplayUrl(localAddress)))}`];
} else if (networkLogging === 'host-to-expose') {
addresses = [`${localPrefix}${bold(cyan(toDisplayUrl(localAddress)))}`, `${networkPrefix}${dim('use --host to expose')}`];
addresses = [
`${localPrefix}${bold(cyan(toDisplayUrl(localAddress)))}`,
`${networkPrefix}${dim('use --host to expose')}`,
];
} else {
addresses = Object.values(os.networkInterfaces())
.flatMap((networkInterface) => networkInterface ?? [])
.filter((networkInterface) => networkInterface?.address && networkInterface?.family === 'IPv4')
.filter(
(networkInterface) => networkInterface?.address && networkInterface?.family === 'IPv4'
)
.map(({ address }) => {
if (address.includes('127.0.0.1')) {
const displayAddress = address.replace('127.0.0.1', localAddress);
@ -76,7 +105,14 @@ export function devStart({
.sort((msg) => (msg.startsWith(localPrefix) ? -1 : 1));
}
const messages = [`${emoji('🚀 ', '')}${bgGreen(black(` astro `))} ${green(`v${version}`)} ${dim(`started in ${Math.round(startupTime)}ms`)}`, '', ...addresses, ''];
const messages = [
`${emoji('🚀 ', '')}${bgGreen(black(` astro `))} ${green(`v${version}`)} ${dim(
`started in ${Math.round(startupTime)}ms`
)}`,
'',
...addresses,
'',
];
return messages.map((msg) => ` ${msg}`).join('\n');
}
@ -136,8 +172,12 @@ export function getNetworkLogging(host: string | boolean): 'none' | 'host-to-exp
}
export function formatConfigErrorMessage(err: ZodError) {
const errorList = err.issues.map((issue) => ` ! ${bold(issue.path.join('.'))} ${red(issue.message + '.')}`);
return `${red('[config]')} Astro found issue(s) with your configuration:\n${errorList.join('\n')}`;
const errorList = err.issues.map(
(issue) => ` ! ${bold(issue.path.join('.'))} ${red(issue.message + '.')}`
);
return `${red('[config]')} Astro found issue(s) with your configuration:\n${errorList.join(
'\n'
)}`;
}
export function formatErrorMessage(_err: Error, args: string[] = []): string {
@ -196,7 +236,12 @@ export function printHelp({
let message = [];
if (headline) {
message.push(linebreak(), ` ${bgGreen(black(` ${commandName} `))} ${green(`v${process.env.PACKAGE_VERSION ?? ''}`)} ${headline}`);
message.push(
linebreak(),
` ${bgGreen(black(` ${commandName} `))} ${green(
`v${process.env.PACKAGE_VERSION ?? ''}`
)} ${headline}`
);
}
if (usage) {
@ -204,7 +249,11 @@ export function printHelp({
}
if (commands) {
message.push(linebreak(), title('Commands'), table(commands, { padding: 28, prefix: ' astro ' }));
message.push(
linebreak(),
title('Commands'),
table(commands, { padding: 28, prefix: ' astro ' })
);
}
if (flags) {

View file

@ -24,7 +24,10 @@ export interface PreviewServer {
const HAS_FILE_EXTENSION_REGEXP = /^.*\.[^\\]+$/;
/** The primary dev action */
export default async function preview(config: AstroConfig, { logging }: PreviewOptions): Promise<PreviewServer> {
export default async function preview(
config: AstroConfig,
{ logging }: PreviewOptions
): Promise<PreviewServer> {
const startServerTime = performance.now();
const defaultOrigin = 'http://localhost';
const trailingSlash = config.trailingSlash;
@ -61,7 +64,10 @@ export default async function preview(config: AstroConfig, { logging }: PreviewO
case hasTrailingSlash && trailingSlash == 'never' && !isRoot:
sendError('Not Found (trailingSlash is set to "never")');
return;
case !hasTrailingSlash && trailingSlash == 'always' && !isRoot && !HAS_FILE_EXTENSION_REGEXP.test(pathname):
case !hasTrailingSlash &&
trailingSlash == 'always' &&
!isRoot &&
!HAS_FILE_EXTENSION_REGEXP.test(pathname):
sendError('Not Found (trailingSlash is set to "always")');
return;
default: {
@ -87,7 +93,17 @@ export default async function preview(config: AstroConfig, { logging }: PreviewO
httpServer = server.listen(port, host, async () => {
if (!showedListenMsg) {
const devServerAddressInfo = server.address() as AddressInfo;
info(logging, null, msg.devStart({ startupTime: performance.now() - timerStart, config, devServerAddressInfo, https: false, site: baseURL }));
info(
logging,
null,
msg.devStart({
startupTime: performance.now() - timerStart,
config,
devServerAddressInfo,
https: false,
site: baseURL,
})
);
}
showedListenMsg = true;
resolve();

View file

@ -1,4 +1,13 @@
import type { ComponentInstance, EndpointHandler, MarkdownRenderOptions, Params, Props, SSRLoadedRenderer, RouteData, SSRElement } from '../../@types/astro';
import type {
ComponentInstance,
EndpointHandler,
MarkdownRenderOptions,
Params,
Props,
SSRLoadedRenderer,
RouteData,
SSRElement,
} from '../../@types/astro';
import type { LogOptions } from '../logger/core.js';
import { renderHead, renderPage } from '../../runtime/server/index.js';
@ -19,7 +28,9 @@ export const enum GetParamsAndPropsError {
NoMatchingStaticPath,
}
export async function getParamsAndProps(opts: GetParamsAndPropsOptions): Promise<[Params, Props] | GetParamsAndPropsError> {
export async function getParamsAndProps(
opts: GetParamsAndPropsOptions
): Promise<[Params, Props] | GetParamsAndPropsError> {
const { logging, mod, route, routeCache, pathname, ssr } = opts;
// Handle dynamic routes
let params: Params = {};
@ -73,8 +84,26 @@ export interface RenderOptions {
request: Request;
}
export async function render(opts: RenderOptions): Promise<{ type: 'html'; html: string } | { type: 'response'; response: Response }> {
const { legacyBuild, links, logging, origin, markdownRender, mod, pathname, scripts, renderers, request, resolve, route, routeCache, site, ssr } = opts;
export async function render(
opts: RenderOptions
): Promise<{ type: 'html'; html: string } | { type: 'response'; response: Response }> {
const {
legacyBuild,
links,
logging,
origin,
markdownRender,
mod,
pathname,
scripts,
renderers,
request,
resolve,
route,
routeCache,
site,
ssr,
} = opts;
const paramsAndPropsRes = await getParamsAndProps({
logging,
@ -86,14 +115,18 @@ export async function render(opts: RenderOptions): Promise<{ type: 'html'; html:
});
if (paramsAndPropsRes === GetParamsAndPropsError.NoMatchingStaticPath) {
throw new Error(`[getStaticPath] route pattern matched, but no matching static path found. (${pathname})`);
throw new Error(
`[getStaticPath] route pattern matched, but no matching static path found. (${pathname})`
);
}
const [params, pageProps] = paramsAndPropsRes;
// Validate the page component before rendering the page
const Component = await mod.default;
if (!Component) throw new Error(`Expected an exported Astro component but received typeof ${typeof Component}`);
if (!Component.isAstroComponentFactory) throw new Error(`Unable to SSR non-Astro component (${route?.component})`);
if (!Component)
throw new Error(`Expected an exported Astro component but received typeof ${typeof Component}`);
if (!Component.isAstroComponentFactory)
throw new Error(`Unable to SSR non-Astro component (${route?.component})`);
const result = createResult({
legacyBuild,

View file

@ -98,7 +98,10 @@ function serializeTag({ tag, attrs, children }: vite.HtmlTagDescriptor, indent =
if (unaryTags.has(tag)) {
return `<${tag}${serializeAttrs(attrs)}>`;
} else {
return `<${tag}${serializeAttrs(attrs)}>${serializeTags(children, incrementIndent(indent))}</${tag}>`;
return `<${tag}${serializeAttrs(attrs)}>${serializeTags(
children,
incrementIndent(indent)
)}</${tag}>`;
}
}

View file

@ -1,7 +1,15 @@
import astroRemark from '@astrojs/markdown-remark';
import { fileURLToPath } from 'url';
import type * as vite from 'vite';
import type { AstroConfig, AstroRenderer, ComponentInstance, RouteData, RuntimeMode, SSRElement, SSRLoadedRenderer } from '../../../@types/astro';
import type {
AstroConfig,
AstroRenderer,
ComponentInstance,
RouteData,
RuntimeMode,
SSRElement,
SSRLoadedRenderer,
} from '../../../@types/astro';
import { LogOptions } from '../../logger/core.js';
import { render as coreRender } from '../core.js';
import { prependForwardSlash } from '../../../core/path.js';
@ -37,24 +45,38 @@ export interface SSROptions {
export type ComponentPreload = [SSRLoadedRenderer[], ComponentInstance];
export type RenderResponse = { type: 'html'; html: string } | { type: 'response'; response: Response };
export type RenderResponse =
| { type: 'html'; html: string }
| { type: 'response'; response: Response };
const svelteStylesRE = /svelte\?svelte&type=style/;
async function loadRenderer(viteServer: vite.ViteDevServer, renderer: AstroRenderer): Promise<SSRLoadedRenderer> {
async function loadRenderer(
viteServer: vite.ViteDevServer,
renderer: AstroRenderer
): Promise<SSRLoadedRenderer> {
// Vite modules can be out-of-date when using an un-resolved url
// We also encountered inconsistencies when using the resolveUrl and resolveId helpers
// We've found that pulling the ID directly from the urlToModuleMap is the most stable!
const id = viteServer.moduleGraph.urlToModuleMap.get(renderer.serverEntrypoint)?.id ?? renderer.serverEntrypoint;
const id =
viteServer.moduleGraph.urlToModuleMap.get(renderer.serverEntrypoint)?.id ??
renderer.serverEntrypoint;
const mod = (await viteServer.ssrLoadModule(id)) as { default: SSRLoadedRenderer['ssr'] };
return { ...renderer, ssr: mod.default };
}
export async function loadRenderers(viteServer: vite.ViteDevServer, astroConfig: AstroConfig): Promise<SSRLoadedRenderer[]> {
export async function loadRenderers(
viteServer: vite.ViteDevServer,
astroConfig: AstroConfig
): Promise<SSRLoadedRenderer[]> {
return Promise.all(astroConfig._ctx.renderers.map((r) => loadRenderer(viteServer, r)));
}
export async function preload({ astroConfig, filePath, viteServer }: Pick<SSROptions, 'astroConfig' | 'filePath' | 'viteServer'>): Promise<ComponentPreload> {
export async function preload({
astroConfig,
filePath,
viteServer,
}: Pick<SSROptions, 'astroConfig' | 'filePath' | 'viteServer'>): Promise<ComponentPreload> {
// Important: This needs to happen first, in case a renderer provides polyfills.
const renderers = await loadRenderers(viteServer, astroConfig);
// Load the module from the Vite SSR Runtime.
@ -64,13 +86,32 @@ export async function preload({ astroConfig, filePath, viteServer }: Pick<SSROpt
}
/** use Vite to SSR */
export async function render(renderers: SSRLoadedRenderer[], mod: ComponentInstance, ssrOpts: SSROptions): Promise<RenderResponse> {
const { astroConfig, filePath, logging, mode, origin, pathname, request, route, routeCache, viteServer } = ssrOpts;
export async function render(
renderers: SSRLoadedRenderer[],
mod: ComponentInstance,
ssrOpts: SSROptions
): Promise<RenderResponse> {
const {
astroConfig,
filePath,
logging,
mode,
origin,
pathname,
request,
route,
routeCache,
viteServer,
} = ssrOpts;
// TODO: clean up "legacy" flag passed through helper functions
const isLegacyBuild = false;
// Add hoisted script tags
const scripts = createModuleScriptElementWithSrcSet(!isLegacyBuild && mod.hasOwnProperty('$$metadata') ? Array.from(mod.$$metadata.hoistedScriptPaths()) : []);
const scripts = createModuleScriptElementWithSrcSet(
!isLegacyBuild && mod.hasOwnProperty('$$metadata')
? Array.from(mod.$$metadata.hoistedScriptPaths())
: []
);
// Inject HMR scripts
if (mod.hasOwnProperty('$$metadata') && mode === 'development' && !isLegacyBuild) {
@ -79,7 +120,10 @@ export async function render(renderers: SSRLoadedRenderer[], mod: ComponentInsta
children: '',
});
scripts.add({
props: { type: 'module', src: new URL('../../../runtime/client/hmr.js', import.meta.url).pathname },
props: {
type: 'module',
src: new URL('../../../runtime/client/hmr.js', import.meta.url).pathname,
},
children: '',
});
}
@ -203,7 +247,10 @@ export async function render(renderers: SSRLoadedRenderer[], mod: ComponentInsta
};
}
export async function ssr(preloadedComponent: ComponentPreload, ssrOpts: SSROptions): Promise<RenderResponse> {
export async function ssr(
preloadedComponent: ComponentPreload,
ssrOpts: SSROptions
): Promise<RenderResponse> {
const [renderers, mod] = preloadedComponent;
return await render(renderers, mod, ssrOpts); // NOTE: without "await", errors wont get caught below
}

View file

@ -1,7 +1,17 @@
import { GetStaticPathsResult, Page, PaginateFunction, Params, Props, RouteData } from '../../@types/astro';
import {
GetStaticPathsResult,
Page,
PaginateFunction,
Params,
Props,
RouteData,
} from '../../@types/astro';
export function generatePaginateFunction(routeMatch: RouteData): PaginateFunction {
return function paginateUtility(data: any[], args: { pageSize?: number; params?: Params; props?: Props } = {}) {
return function paginateUtility(
data: any[],
args: { pageSize?: number; params?: Params; props?: Props } = {}
) {
let { pageSize: _pageSize, params: _params, props: _props } = args;
const pageSize = _pageSize || 10;
const paramName = 'page';
@ -41,8 +51,20 @@ export function generatePaginateFunction(routeMatch: RouteData): PaginateFunctio
lastPage: lastPage,
url: {
current: routeMatch.generate({ ...params }),
next: pageNum === lastPage ? undefined : routeMatch.generate({ ...params, page: String(pageNum + 1) }),
prev: pageNum === 1 ? undefined : routeMatch.generate({ ...params, page: !includesFirstPageNumber && pageNum - 1 === 1 ? undefined : String(pageNum - 1) }),
next:
pageNum === lastPage
? undefined
: routeMatch.generate({ ...params, page: String(pageNum + 1) }),
prev:
pageNum === 1
? undefined
: routeMatch.generate({
...params,
page:
!includesFirstPageNumber && pageNum - 1 === 1
? undefined
: String(pageNum - 1),
}),
},
} as Page,
},

View file

@ -1,5 +1,14 @@
import { bold } from 'kleur/colors';
import type { AstroGlobal, AstroGlobalPartial, MarkdownParser, MarkdownRenderOptions, Params, SSRElement, SSRLoadedRenderer, SSRResult } from '../../@types/astro';
import type {
AstroGlobal,
AstroGlobalPartial,
MarkdownParser,
MarkdownRenderOptions,
Params,
SSRElement,
SSRLoadedRenderer,
SSRResult,
} from '../../@types/astro';
import { renderSlot } from '../../runtime/server/index.js';
import { LogOptions, warn } from '../logger/core.js';
import { createCanonicalURL, isCSSRequest } from './util.js';
@ -45,7 +54,9 @@ class Slots {
if (slots) {
for (const key of Object.keys(slots)) {
if ((this as any)[key] !== undefined) {
throw new Error(`Unable to create a slot named "${key}". "${key}" is a reserved slot name!\nPlease update the name of this slot.`);
throw new Error(
`Unable to create a slot named "${key}". "${key}" is a reserved slot name!\nPlease update the name of this slot.`
);
}
Object.defineProperty(this, key, {
get() {
@ -75,10 +86,14 @@ class Slots {
const expression = getFunctionExpression(component);
if (expression) {
const slot = expression(...args);
return await renderSlot(this.#result, slot).then((res) => (res != null ? String(res) : res));
return await renderSlot(this.#result, slot).then((res) =>
res != null ? String(res) : res
);
}
}
const content = await renderSlot(this.#result, this.#slots[name]).then((res) => (res != null ? String(res) : res));
const content = await renderSlot(this.#result, this.#slots[name]).then((res) =>
res != null ? String(res) : res
);
if (cacheable) this.#cache.set(name, content);
return content;
}
@ -98,7 +113,11 @@ export function createResult(args: CreateResultArgs): SSRResult {
scripts: args.scripts ?? new Set<SSRElement>(),
links: args.links ?? new Set<SSRElement>(),
/** This function returns the `Astro` faux-global */
createAstro(astroGlobal: AstroGlobalPartial, props: Record<string, any>, slots: Record<string, any> | null) {
createAstro(
astroGlobal: AstroGlobalPartial,
props: Record<string, any>,
slots: Record<string, any> | null
) {
const astroSlots = new Slots(result, slots);
const Astro = {
@ -142,7 +161,9 @@ or consider make it a module like so:
warn(
args.logging,
`deprecation`,
`${bold('Astro.resolve()')} is deprecated. We see that you are trying to resolve ${path}.
`${bold(
'Astro.resolve()'
)} is deprecated. We see that you are trying to resolve ${path}.
${extra}`
);
// Intentionally return an empty string so that it is not relied upon.

View file

@ -1,8 +1,19 @@
import type { ComponentInstance, GetStaticPathsItem, GetStaticPathsResult, GetStaticPathsResultKeyed, Params, RouteData, RSS } from '../../@types/astro';
import type {
ComponentInstance,
GetStaticPathsItem,
GetStaticPathsResult,
GetStaticPathsResultKeyed,
Params,
RouteData,
RSS,
} from '../../@types/astro';
import { LogOptions, warn, debug } from '../logger/core.js';
import { generatePaginateFunction } from './paginate.js';
import { validateGetStaticPathsModule, validateGetStaticPathsResult } from '../routing/validation.js';
import {
validateGetStaticPathsModule,
validateGetStaticPathsResult,
} from '../routing/validation.js';
type RSSFn = (...args: any[]) => any;
@ -19,7 +30,13 @@ interface CallGetStaticPathsOptions {
ssr: boolean;
}
export async function callGetStaticPaths({ isValidate, logging, mod, route, ssr }: CallGetStaticPathsOptions): Promise<RouteCacheEntry> {
export async function callGetStaticPaths({
isValidate,
logging,
mod,
route,
ssr,
}: CallGetStaticPathsOptions): Promise<RouteCacheEntry> {
validateGetStaticPathsModule(mod, { ssr });
const resultInProgress = {
rss: [] as RSS[],
@ -80,7 +97,11 @@ export class RouteCache {
// Warn here so that an unexpected double-call of getStaticPaths()
// isn't invisible and developer can track down the issue.
if (this.cache[route.component]) {
warn(this.logging, 'routeCache', `Internal Warning: route cache overwritten. (${route.component})`);
warn(
this.logging,
'routeCache',
`Internal Warning: route cache overwritten. (${route.component})`
);
}
this.cache[route.component] = entry;
}
@ -98,5 +119,7 @@ export function findPathItemByKey(staticPaths: GetStaticPathsResultKeyed, params
}
debug('findPathItemByKey', `Unexpected cache miss looking for ${paramsKey}`);
matchedStaticPath = staticPaths.find(({ params: _params }) => JSON.stringify(_params) === paramsKey);
matchedStaticPath = staticPaths.find(
({ params: _params }) => JSON.stringify(_params) === paramsKey
);
}

View file

@ -9,8 +9,10 @@ export function validateRSS(args: GenerateRSSArgs): void {
const { rssData, srcFile } = args;
if (!rssData.title) throw new Error(`[${srcFile}] rss.title required`);
if (!rssData.description) throw new Error(`[${srcFile}] rss.description required`);
if ((rssData as any).item) throw new Error(`[${srcFile}] \`item: Function\` should be \`items: Item[]\``);
if (!Array.isArray(rssData.items)) throw new Error(`[${srcFile}] rss.items should be an array of items`);
if ((rssData as any).item)
throw new Error(`[${srcFile}] \`item: Function\` should be \`items: Item[]\``);
if (!Array.isArray(rssData.items))
throw new Error(`[${srcFile}] rss.items should be an array of items`);
}
type GenerateRSSArgs = { site: string; rssData: RSS; srcFile: string };
@ -19,7 +21,10 @@ type GenerateRSSArgs = { site: string; rssData: RSS; srcFile: string };
export function generateRSS(args: GenerateRSSArgs): string {
validateRSS(args);
const { srcFile, rssData, site } = args;
if ((rssData as any).item) throw new Error(`[${srcFile}] rss() \`item()\` function was deprecated, and is now \`items: object[]\`.`);
if ((rssData as any).item)
throw new Error(
`[${srcFile}] rss() \`item()\` function was deprecated, and is now \`items: object[]\`.`
);
let xml = `<?xml version="1.0" encoding="UTF-8"?>`;
if (typeof rssData.stylesheet === 'string') {
@ -45,12 +50,27 @@ export function generateRSS(args: GenerateRSSArgs): string {
for (const result of rssData.items) {
xml += `<item>`;
// validate
if (typeof result !== 'object') throw new Error(`[${srcFile}] rss.items expected an object. got: "${JSON.stringify(result)}"`);
if (!result.title) throw new Error(`[${srcFile}] rss.items required "title" property is missing. got: "${JSON.stringify(result)}"`);
if (!result.link) throw new Error(`[${srcFile}] rss.items required "link" property is missing. got: "${JSON.stringify(result)}"`);
if (typeof result !== 'object')
throw new Error(
`[${srcFile}] rss.items expected an object. got: "${JSON.stringify(result)}"`
);
if (!result.title)
throw new Error(
`[${srcFile}] rss.items required "title" property is missing. got: "${JSON.stringify(
result
)}"`
);
if (!result.link)
throw new Error(
`[${srcFile}] rss.items required "link" property is missing. got: "${JSON.stringify(
result
)}"`
);
xml += `<title><![CDATA[${result.title}]]></title>`;
// If the item's link is already a valid URL, don't mess with it.
const itemLink = isValidURL(result.link) ? result.link : createCanonicalURL(result.link, site).href;
const itemLink = isValidURL(result.link)
? result.link
: createCanonicalURL(result.link, site).href;
xml += `<link>${itemLink}</link>`;
xml += `<guid>${itemLink}</guid>`;
if (result.description) xml += `<description><![CDATA[${result.description}]]></description>`;
@ -87,7 +107,9 @@ export function generateRSSStylesheet() {
export function generateRssFunction(site: string | undefined, route: RouteData): RSSFunction {
return function rssUtility(args: RSS): RSSResult {
if (!site) {
throw new Error(`[${route.component}] rss() tried to generate RSS but "site" missing in astro.config.mjs`);
throw new Error(
`[${route.component}] rss() tried to generate RSS but "site" missing in astro.config.mjs`
);
}
let result: RSSResult = {} as any;
const { dest, ...rssData } = args;

View file

@ -35,6 +35,9 @@ export function createModuleScriptElementWithSrc(src: string, site?: string): SS
};
}
export function createModuleScriptElementWithSrcSet(srces: string[], site?: string): Set<SSRElement> {
export function createModuleScriptElementWithSrcSet(
srces: string[],
site?: string
): Set<SSRElement> {
return new Set<SSRElement>(srces.map((src) => createModuleScriptElementWithSrc(src, site)));
}

View file

@ -19,7 +19,16 @@ export function isValidURL(url: string): boolean {
}
// https://vitejs.dev/guide/features.html#css-pre-processors
export const STYLE_EXTENSIONS = new Set(['.css', '.pcss', '.postcss', '.scss', '.sass', '.styl', '.stylus', '.less']);
export const STYLE_EXTENSIONS = new Set([
'.css',
'.pcss',
'.postcss',
'.scss',
'.sass',
'.styl',
'.stylus',
'.less',
]);
const cssRe = new RegExp(
`\\.(${Array.from(STYLE_EXTENSIONS)

View file

@ -13,8 +13,17 @@ export interface CreateRequestOptions {
logging: LogOptions;
}
export function createRequest({ url, headers, method = 'GET', body = undefined, logging }: CreateRequestOptions): Request {
let headersObj = headers instanceof Headers ? headers : new Headers(Object.entries(headers as Record<string, any>));
export function createRequest({
url,
headers,
method = 'GET',
body = undefined,
logging,
}: CreateRequestOptions): Request {
let headersObj =
headers instanceof Headers
? headers
: new Headers(Object.entries(headers as Record<string, any>));
const request = new Request(url.toString(), {
method: method,
@ -25,7 +34,11 @@ export function createRequest({ url, headers, method = 'GET', body = undefined,
Object.defineProperties(request, {
canonicalURL: {
get() {
warn(logging, 'deprecation', `Astro.request.canonicalURL has been moved to Astro.canonicalURL`);
warn(
logging,
'deprecation',
`Astro.request.canonicalURL has been moved to Astro.canonicalURL`
);
return undefined;
},
},

View file

@ -79,7 +79,8 @@ function getPattern(segments: Part[][], addTrailingSlash: AstroConfig['trailingS
})
.join('');
const trailing = addTrailingSlash && segments.length ? getTrailingSlashPattern(addTrailingSlash) : '$';
const trailing =
addTrailingSlash && segments.length ? getTrailingSlashPattern(addTrailingSlash) : '$';
return new RegExp(`^${pathname || '\\/'}${trailing}`);
}
@ -155,7 +156,10 @@ function comparator(a: Item, b: Item) {
}
if (!aSubPart.dynamic && aSubPart.content !== bSubPart.content) {
return bSubPart.content.length - aSubPart.content.length || (aSubPart.content < bSubPart.content ? -1 : 1);
return (
bSubPart.content.length - aSubPart.content.length ||
(aSubPart.content < bSubPart.content ? -1 : 1)
);
}
}
@ -168,7 +172,10 @@ function comparator(a: Item, b: Item) {
}
/** Create manifest of all static routes */
export function createRouteManifest({ config, cwd }: { config: AstroConfig; cwd?: string }, logging: LogOptions): ManifestData {
export function createRouteManifest(
{ config, cwd }: { config: AstroConfig; cwd?: string },
logging: LogOptions
): ManifestData {
const components: string[] = [];
const routes: RouteData[] = [];
const validPageExtensions: Set<string> = new Set(['.astro', '.md']);
@ -196,7 +203,9 @@ export function createRouteManifest({ config, cwd }: { config: AstroConfig; cwd?
}
const segment = isDir ? basename : name;
if (/^\$/.test(segment)) {
throw new Error(`Invalid route ${file} — Astro's Collections API has been replaced by dynamic route params.`);
throw new Error(
`Invalid route ${file} — Astro's Collections API has been replaced by dynamic route params.`
);
}
if (/\]\[/.test(segment)) {
throw new Error(`Invalid route ${file} — parameters must be separated`);
@ -269,7 +278,9 @@ export function createRouteManifest({ config, cwd }: { config: AstroConfig; cwd?
const trailingSlash = item.isPage ? config.trailingSlash : 'never';
const pattern = getPattern(segments, trailingSlash);
const generate = getGenerator(segments, trailingSlash);
const pathname = segments.every((segment) => segment.length === 1 && !segment[0].dynamic) ? `/${segments.map((segment) => segment[0].content).join('/')}` : null;
const pathname = segments.every((segment) => segment.length === 1 && !segment[0].dynamic)
? `/${segments.map((segment) => segment[0].content).join('/')}`
: null;
routes.push({
type: item.isPage ? 'page' : 'endpoint',

View file

@ -1,6 +1,12 @@
import type { RouteData, SerializedRouteData } from '../../../@types/astro';
function createRouteData(pattern: RegExp, params: string[], component: string, pathname: string | undefined, type: 'page' | 'endpoint'): RouteData {
function createRouteData(
pattern: RegExp,
params: string[],
component: string,
pathname: string | undefined,
type: 'page' | 'endpoint'
): RouteData {
return {
type,
pattern,

View file

@ -12,26 +12,48 @@ export function validateGetStaticPathsModule(mod: ComponentInstance, { ssr }: Va
throw new Error(`[createCollection] deprecated. Please use getStaticPaths() instead.`);
}
if (!mod.getStaticPaths && !ssr) {
throw new Error(`[getStaticPaths] getStaticPaths() function is required. Make sure that you \`export\` the function from your component.`);
throw new Error(
`[getStaticPaths] getStaticPaths() function is required. Make sure that you \`export\` the function from your component.`
);
}
}
/** Throw error for malformed getStaticPaths() response */
export function validateGetStaticPathsResult(result: GetStaticPathsResult, logging: LogOptions) {
if (!Array.isArray(result)) {
throw new Error(`[getStaticPaths] invalid return value. Expected an array of path objects, but got \`${JSON.stringify(result)}\`.`);
throw new Error(
`[getStaticPaths] invalid return value. Expected an array of path objects, but got \`${JSON.stringify(
result
)}\`.`
);
}
result.forEach((pathObject) => {
if (!pathObject.params) {
warn(logging, 'getStaticPaths', `invalid path object. Expected an object with key \`params\`, but got \`${JSON.stringify(pathObject)}\`. Skipped.`);
warn(
logging,
'getStaticPaths',
`invalid path object. Expected an object with key \`params\`, but got \`${JSON.stringify(
pathObject
)}\`. Skipped.`
);
return;
}
for (const [key, val] of Object.entries(pathObject.params)) {
if (!(typeof val === 'undefined' || typeof val === 'string')) {
warn(logging, 'getStaticPaths', `invalid path param: ${key}. A string value was expected, but got \`${JSON.stringify(val)}\`.`);
warn(
logging,
'getStaticPaths',
`invalid path param: ${key}. A string value was expected, but got \`${JSON.stringify(
val
)}\`.`
);
}
if (val === '') {
warn(logging, 'getStaticPaths', `invalid path param: ${key}. \`undefined\` expected for an optional param, but got empty string.`);
warn(
logging,
'getStaticPaths',
`invalid path param: ${key}. \`undefined\` expected for an optional param, but got empty string.`
);
}
}
});

View file

@ -41,7 +41,9 @@ export function getOutputFilename(astroConfig: AstroConfig, name: string) {
}
/** is a specifier an npm package? */
export function parseNpmName(spec: string): { scope?: string; name: string; subpath?: string } | undefined {
export function parseNpmName(
spec: string
): { scope?: string; name: string; subpath?: string } | undefined {
// not an npm package
if (!spec || spec[0] === '.' || spec[0] === '/') return undefined;
@ -66,7 +68,9 @@ export function parseNpmName(spec: string): { scope?: string; name: string; subp
/** Coalesce any throw variable to an Error instance. */
export function createSafeError(err: any): Error {
return err instanceof Error || (err && err.name && err.message) ? err : new Error(JSON.stringify(err));
return err instanceof Error || (err && err.name && err.message)
? err
: new Error(JSON.stringify(err));
}
/** generate code frame from esbuild error */
@ -90,7 +94,10 @@ export function codeFrame(src: string, loc: ErrorPayload['err']['loc']): string
const isFocusedLine = lineNo === loc.line - 1;
output += isFocusedLine ? '> ' : ' ';
output += `${lineNo + 1} | ${lines[lineNo]}\n`;
if (isFocusedLine) output += `${[...new Array(gutterWidth)].join(' ')} | ${[...new Array(loc.column)].join(' ')}^\n`;
if (isFocusedLine)
output += `${[...new Array(gutterWidth)].join(' ')} | ${[...new Array(loc.column)].join(
' '
)}^\n`;
}
return output;
}

View file

@ -5,7 +5,13 @@ import { mergeConfig } from '../core/config.js';
import ssgAdapter from '../adapter-ssg/index.js';
import type { ViteConfigWithSSR } from '../core/create-vite.js';
export async function runHookConfigSetup({ config: _config, command }: { config: AstroConfig; command: 'dev' | 'build' }): Promise<AstroConfig> {
export async function runHookConfigSetup({
config: _config,
command,
}: {
config: AstroConfig;
command: 'dev' | 'build';
}): Promise<AstroConfig> {
if (_config.adapter) {
_config.integrations.push(_config.adapter);
}
@ -38,7 +44,9 @@ export async function runHookConfigDone({ config }: { config: AstroConfig }) {
config,
setAdapter(adapter) {
if (config._ctx.adapter && config._ctx.adapter.name !== adapter.name) {
throw new Error(`Adapter already set to ${config._ctx.adapter.name}. You can only have one adapter.`);
throw new Error(
`Adapter already set to ${config._ctx.adapter.name}. You can only have one adapter.`
);
}
config._ctx.adapter = adapter;
},
@ -60,7 +68,13 @@ export async function runHookConfigDone({ config }: { config: AstroConfig }) {
}
}
export async function runHookServerSetup({ config, server }: { config: AstroConfig; server: ViteDevServer }) {
export async function runHookServerSetup({
config,
server,
}: {
config: AstroConfig;
server: ViteDevServer;
}) {
for (const integration of config.integrations) {
if (integration.hooks['astro:server:setup']) {
await integration.hooks['astro:server:setup']({ server });
@ -68,7 +82,13 @@ export async function runHookServerSetup({ config, server }: { config: AstroConf
}
}
export async function runHookServerStart({ config, address }: { config: AstroConfig; address: AddressInfo }) {
export async function runHookServerStart({
config,
address,
}: {
config: AstroConfig;
address: AddressInfo;
}) {
for (const integration of config.integrations) {
if (integration.hooks['astro:server:start']) {
await integration.hooks['astro:server:start']({ address });
@ -84,7 +104,13 @@ export async function runHookServerDone({ config }: { config: AstroConfig }) {
}
}
export async function runHookBuildStart({ config, buildConfig }: { config: AstroConfig; buildConfig: BuildConfig }) {
export async function runHookBuildStart({
config,
buildConfig,
}: {
config: AstroConfig;
buildConfig: BuildConfig;
}) {
for (const integration of config.integrations) {
if (integration.hooks['astro:build:start']) {
await integration.hooks['astro:build:start']({ buildConfig });
@ -92,7 +118,15 @@ export async function runHookBuildStart({ config, buildConfig }: { config: Astro
}
}
export async function runHookBuildSetup({ config, vite, target }: { config: AstroConfig; vite: ViteConfigWithSSR; target: 'server' | 'client' }) {
export async function runHookBuildSetup({
config,
vite,
target,
}: {
config: AstroConfig;
vite: ViteConfigWithSSR;
target: 'server' | 'client';
}) {
for (const integration of config.integrations) {
if (integration.hooks['astro:build:setup']) {
await integration.hooks['astro:build:setup']({ vite, target });
@ -100,10 +134,22 @@ export async function runHookBuildSetup({ config, vite, target }: { config: Astr
}
}
export async function runHookBuildDone({ config, pages, routes }: { config: AstroConfig; pages: string[]; routes: RouteData[] }) {
export async function runHookBuildDone({
config,
pages,
routes,
}: {
config: AstroConfig;
pages: string[];
routes: RouteData[];
}) {
for (const integration of config.integrations) {
if (integration.hooks['astro:build:done']) {
await integration.hooks['astro:build:done']({ pages: pages.map((p) => ({ pathname: p })), dir: config.outDir, routes });
await integration.hooks['astro:build:done']({
pages: pages.map((p) => ({ pathname: p })),
dir: config.outDir,
routes,
});
}
}
}

View file

@ -4,7 +4,11 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro';
* Hydrate this component as soon as the main thread is free
* (or after a short delay, if `requestIdleCallback`) isn't supported
*/
export default async function onIdle(astroId: string, options: HydrateOptions, getHydrateCallback: GetHydrateCallback) {
export default async function onIdle(
astroId: string,
options: HydrateOptions,
getHydrateCallback: GetHydrateCallback
) {
const cb = async () => {
const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`);
if (roots.length === 0) {

View file

@ -3,7 +3,11 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro';
/**
* Hydrate this component immediately
*/
export default async function onLoad(astroId: string, options: HydrateOptions, getHydrateCallback: GetHydrateCallback) {
export default async function onLoad(
astroId: string,
options: HydrateOptions,
getHydrateCallback: GetHydrateCallback
) {
const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`);
if (roots.length === 0) {
throw new Error(`Unable to find the root for the component ${options.name}`);

View file

@ -3,7 +3,11 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro';
/**
* Hydrate this component when a matching media query is found
*/
export default async function onMedia(astroId: string, options: HydrateOptions, getHydrateCallback: GetHydrateCallback) {
export default async function onMedia(
astroId: string,
options: HydrateOptions,
getHydrateCallback: GetHydrateCallback
) {
const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`);
if (roots.length === 0) {
throw new Error(`Unable to find the root for the component ${options.name}`);

View file

@ -3,7 +3,11 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro';
/**
* Hydrate this component immediately
*/
export default async function onLoad(astroId: string, options: HydrateOptions, getHydrateCallback: GetHydrateCallback) {
export default async function onLoad(
astroId: string,
options: HydrateOptions,
getHydrateCallback: GetHydrateCallback
) {
const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`);
if (roots.length === 0) {
throw new Error(`Unable to find the root for the component ${options.name}`);

View file

@ -5,7 +5,11 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro';
* We target the children because `astro-root` is set to `display: contents`
* which doesn't work with IntersectionObserver
*/
export default async function onVisible(astroId: string, options: HydrateOptions, getHydrateCallback: GetHydrateCallback) {
export default async function onVisible(
astroId: string,
options: HydrateOptions,
getHydrateCallback: GetHydrateCallback
) {
const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`);
if (roots.length === 0) {
throw new Error(`Unable to find the root for the component ${options.name}`);

View file

@ -58,12 +58,21 @@ export function extractDirectives(inputProps: Record<string | number, any>): Ext
// throw an error if an invalid hydration directive was provided
if (HydrationDirectives.indexOf(extracted.hydration.directive) < 0) {
throw new Error(`Error: invalid hydration directive "${key}". Supported hydration methods: ${HydrationDirectives.map((d) => `"client:${d}"`).join(', ')}`);
throw new Error(
`Error: invalid hydration directive "${key}". Supported hydration methods: ${HydrationDirectives.map(
(d) => `"client:${d}"`
).join(', ')}`
);
}
// throw an error if the query wasn't provided for client:media
if (extracted.hydration.directive === 'media' && typeof extracted.hydration.value !== 'string') {
throw new Error('Error: Media query must be provided for "client:media", similar to client:media="(max-width: 600px)"');
if (
extracted.hydration.directive === 'media' &&
typeof extracted.hydration.value !== 'string'
) {
throw new Error(
'Error: Media query must be provided for "client:media", similar to client:media="(max-width: 600px)"'
);
}
break;
@ -88,20 +97,27 @@ interface HydrateScriptOptions {
}
/** For hydrated components, generate a <script type="module"> to load the component */
export async function generateHydrateScript(scriptOptions: HydrateScriptOptions, metadata: Required<AstroComponentMetadata>): Promise<SSRElement> {
export async function generateHydrateScript(
scriptOptions: HydrateScriptOptions,
metadata: Required<AstroComponentMetadata>
): Promise<SSRElement> {
const { renderer, result, astroId, props } = scriptOptions;
const { hydrate, componentUrl, componentExport } = metadata;
if (!componentExport) {
throw new Error(`Unable to resolve a componentExport for "${metadata.displayName}"! Please open an issue.`);
throw new Error(
`Unable to resolve a componentExport for "${metadata.displayName}"! Please open an issue.`
);
}
let hydrationSource = ``;
hydrationSource += renderer.clientEntrypoint
? `const [{ ${componentExport.value}: Component }, { default: hydrate }] = await Promise.all([import("${await result.resolve(componentUrl)}"), import("${await result.resolve(
renderer.clientEntrypoint
)}")]);
? `const [{ ${
componentExport.value
}: Component }, { default: hydrate }] = await Promise.all([import("${await result.resolve(
componentUrl
)}"), import("${await result.resolve(renderer.clientEntrypoint)}")]);
return (el, children) => hydrate(el)(Component, ${serializeProps(props)}, children);
`
: `await import("${await result.resolve(componentUrl)}");
@ -113,7 +129,9 @@ export async function generateHydrateScript(scriptOptions: HydrateScriptOptions,
props: { type: 'module', 'data-astro-component-hydration': true },
children: `import setup from '${await result.resolve(hydrationSpecifier(hydrate))}';
${`import '${await result.resolve('astro:scripts/before-hydration.js')}';`}
setup("${astroId}", {name:"${metadata.displayName}",${metadata.hydrateArgs ? `value: ${JSON.stringify(metadata.hydrateArgs)}` : ''}}, async () => {
setup("${astroId}", {name:"${metadata.displayName}",${
metadata.hydrateArgs ? `value: ${JSON.stringify(metadata.hydrateArgs)}` : ''
}}, async () => {
${hydrationSource}
});
`,

View file

@ -1,5 +1,13 @@
import shorthash from 'shorthash';
import type { AstroComponentMetadata, AstroGlobalPartial, EndpointHandler, Params, SSRElement, SSRLoadedRenderer, SSRResult } from '../../@types/astro';
import type {
AstroComponentMetadata,
AstroGlobalPartial,
EndpointHandler,
Params,
SSRElement,
SSRLoadedRenderer,
SSRResult,
} from '../../@types/astro';
import { escapeHTML, HTMLString, markHTMLString } from './escape.js';
import { extractDirectives, generateHydrateScript, serializeProps } from './hydration.js';
import { serializeListValue } from './util.js';
@ -8,7 +16,8 @@ export { markHTMLString, markHTMLString as unescapeHTML } from './escape.js';
export type { Metadata } from './metadata';
export { createMetadata } from './metadata.js';
const voidElementNames = /^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i;
const voidElementNames =
/^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i;
const htmlBooleanAttributes =
/^(allowfullscreen|async|autofocus|autoplay|controls|default|defer|disabled|disablepictureinpicture|disableremoteplayback|formnovalidate|hidden|loop|nomodule|novalidate|open|playsinline|readonly|required|reversed|scoped|seamless|itemscope)$/i;
const htmlEnumAttributes = /^(contenteditable|draggable|spellcheck|value)$/i;
@ -40,7 +49,10 @@ async function _render(child: any): Promise<any> {
}
// Add a comment explaining why each of these are needed.
// Maybe create clearly named function for what this is doing.
else if (child instanceof AstroComponent || Object.prototype.toString.call(child) === '[object AstroComponent]') {
else if (
child instanceof AstroComponent ||
Object.prototype.toString.call(child) === '[object AstroComponent]'
) {
return markHTMLString(await renderAstroComponent(child));
} else {
return child;
@ -76,7 +88,9 @@ export class AstroComponent {
}
function isAstroComponent(obj: any): obj is AstroComponent {
return typeof obj === 'object' && Object.prototype.toString.call(obj) === '[object AstroComponent]';
return (
typeof obj === 'object' && Object.prototype.toString.call(obj) === '[object AstroComponent]'
);
}
export async function render(htmlParts: TemplateStringsArray, ...expressions: any[]) {
@ -128,7 +142,13 @@ function formatList(values: string[]): string {
return `${values.slice(0, -1).join(', ')} or ${values[values.length - 1]}`;
}
export async function renderComponent(result: SSRResult, displayName: string, Component: unknown, _props: Record<string | number, any>, slots: any = {}) {
export async function renderComponent(
result: SSRResult,
displayName: string,
Component: unknown,
_props: Record<string | number, any>,
slots: any = {}
) {
Component = await Component;
const children = await renderSlot(result, slots?.default);
@ -145,7 +165,9 @@ export async function renderComponent(result: SSRResult, displayName: string, Co
}
if (Component === null && !_props['client:only']) {
throw new Error(`Unable to render ${displayName} because it is ${Component}!\nDid you forget to import the component or is it possible there is a typo?`);
throw new Error(
`Unable to render ${displayName} because it is ${Component}!\nDid you forget to import the component or is it possible there is a typo?`
);
}
const { renderers } = result._metadata;
@ -162,7 +184,12 @@ export async function renderComponent(result: SSRResult, displayName: string, Co
}
const probableRendererNames = guessRenderers(metadata.componentUrl);
if (Array.isArray(renderers) && renderers.length === 0 && typeof Component !== 'string' && !componentIsHTMLElement(Component)) {
if (
Array.isArray(renderers) &&
renderers.length === 0 &&
typeof Component !== 'string' &&
!componentIsHTMLElement(Component)
) {
const message = `Unable to render ${metadata.displayName}!
There are no \`integrations\` set in your \`astro.config.mjs\` file.
@ -189,7 +216,9 @@ Did you mean to add ${formatList(probableRendererNames.map((r) => '`' + r + '`')
// Attempt: use explicitly passed renderer name
if (metadata.hydrateArgs) {
const rendererName = metadata.hydrateArgs;
renderer = renderers.filter(({ name }) => name === `@astrojs/${rendererName}` || name === rendererName)[0];
renderer = renderers.filter(
({ name }) => name === `@astrojs/${rendererName}` || name === rendererName
)[0];
}
// Attempt: user only has a single renderer, default to that
if (!renderer && renderers.length === 1) {
@ -198,7 +227,9 @@ Did you mean to add ${formatList(probableRendererNames.map((r) => '`' + r + '`')
// Attempt: can we guess the renderer from the export extension?
if (!renderer) {
const extname = metadata.componentUrl?.split('.').pop();
renderer = renderers.filter(({ name }) => name === `@astrojs/${extname}` || name === extname)[0];
renderer = renderers.filter(
({ name }) => name === `@astrojs/${extname}` || name === extname
)[0];
}
}
@ -209,7 +240,9 @@ Did you mean to add ${formatList(probableRendererNames.map((r) => '`' + r + '`')
throw new Error(`Unable to render ${metadata.displayName}!
Using the \`client:only\` hydration strategy, Astro needs a hint to use the correct renderer.
Did you mean to pass <${metadata.displayName} client:only="${probableRendererNames.map((r) => r.replace('@astrojs/', '')).join('|')}" />
Did you mean to pass <${metadata.displayName} client:only="${probableRendererNames
.map((r) => r.replace('@astrojs/', ''))
.join('|')}" />
`);
} else if (typeof Component !== 'string') {
const matchingRenderers = renderers.filter((r) => probableRendererNames.includes(r.name));
@ -217,7 +250,9 @@ Did you mean to pass <${metadata.displayName} client:only="${probableRendererNam
if (matchingRenderers.length === 0) {
throw new Error(`Unable to render ${metadata.displayName}!
There ${plural ? 'are' : 'is'} ${renderers.length} renderer${plural ? 's' : ''} configured in your \`astro.config.mjs\` file,
There ${plural ? 'are' : 'is'} ${renderers.length} renderer${
plural ? 's' : ''
} configured in your \`astro.config.mjs\` file,
but ${plural ? 'none were' : 'it was not'} able to server-side render ${metadata.displayName}.
Did you mean to enable ${formatList(probableRendererNames.map((r) => '`' + r + '`'))}?`);
@ -253,7 +288,9 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
if (!html && typeof Component === 'string') {
html = await renderAstroComponent(
await render`<${Component}${spreadAttributes(props)}${markHTMLString(
(children == null || children == '') && voidElementNames.test(Component) ? `/>` : `>${children}</${Component}>`
(children == null || children == '') && voidElementNames.test(Component)
? `/>`
: `>${children}</${Component}>`
)}`
);
}
@ -263,16 +300,29 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
}
// Include componentExport name, componentUrl, and props in hash to dedupe identical islands
const astroId = shorthash.unique(`<!--${metadata.componentExport!.value}:${metadata.componentUrl}-->\n${html}\n${serializeProps(props)}`);
const astroId = shorthash.unique(
`<!--${metadata.componentExport!.value}:${metadata.componentUrl}-->\n${html}\n${serializeProps(
props
)}`
);
// Rather than appending this inline in the page, puts this into the `result.scripts` set that will be appended to the head.
// INVESTIGATE: This will likely be a problem in streaming because the `<head>` will be gone at this point.
result.scripts.add(await generateHydrateScript({ renderer: renderer!, result, astroId, props }, metadata as Required<AstroComponentMetadata>));
result.scripts.add(
await generateHydrateScript(
{ renderer: renderer!, result, astroId, props },
metadata as Required<AstroComponentMetadata>
)
);
// Render a template if no fragment is provided.
const needsAstroTemplate = children && !/<\/?astro-fragment\>/.test(html);
const template = needsAstroTemplate ? `<template data-astro-template>${children}</template>` : '';
return markHTMLString(`<astro-root uid="${astroId}"${needsAstroTemplate ? ' tmpl' : ''}>${html ?? ''}${template}</astro-root>`);
return markHTMLString(
`<astro-root uid="${astroId}"${needsAstroTemplate ? ' tmpl' : ''}>${
html ?? ''
}${template}</astro-root>`
);
}
/** Create the Astro.fetchContent() runtime function. */
@ -299,7 +349,11 @@ function createAstroGlobFn() {
// This is used to create the top-level Astro global; the one that you can use
// Inside of getStaticPaths.
export function createAstro(filePathname: string, _site: string, projectRootStr: string): AstroGlobalPartial {
export function createAstro(
filePathname: string,
_site: string,
projectRootStr: string
): AstroGlobalPartial {
const site = new URL(_site);
const url = new URL(filePathname, site);
const projectRoot = new URL(projectRootStr);
@ -320,7 +374,8 @@ export function createAstro(filePathname: string, _site: string, projectRootStr:
};
}
const toAttributeString = (value: any, shouldEscape = true) => (shouldEscape ? String(value).replace(/&/g, '&#38;').replace(/"/g, '&#34;') : value);
const toAttributeString = (value: any, shouldEscape = true) =>
shouldEscape ? String(value).replace(/&/g, '&#38;').replace(/"/g, '&#34;') : value;
const STATIC_DIRECTIVES = new Set(['set:html', 'set:text']);
@ -392,7 +447,9 @@ export async function renderEndpoint(mod: EndpointHandler, request: Request, par
const handler = mod[chosenMethod];
if (!handler || typeof handler !== 'function') {
throw new Error(`Endpoint handler not found! Expected an exported function for "${chosenMethod}"`);
throw new Error(
`Endpoint handler not found! Expected an exported function for "${chosenMethod}"`
);
}
return await handler.call(mod, params, request);
@ -409,7 +466,12 @@ async function replaceHeadInjection(result: SSRResult, html: string): Promise<st
}
// Calls a component and renders it into a string of HTML
export async function renderToString(result: SSRResult, componentFactory: AstroComponentFactory, props: any, children: any): Promise<string> {
export async function renderToString(
result: SSRResult,
componentFactory: AstroComponentFactory,
props: any,
children: any
): Promise<string> {
const Component = await componentFactory(result, props, children);
if (!isAstroComponent(Component)) {
const response: Response = Component;
@ -458,7 +520,9 @@ export async function renderPage(
const uniqueElements = (item: any, index: number, all: any[]) => {
const props = JSON.stringify(item.props);
const children = item.children;
return index === all.findIndex((i) => JSON.stringify(i.props) === props && i.children == children);
return (
index === all.findIndex((i) => JSON.stringify(i.props) === props && i.children == children)
);
};
// Renders a page to completion by first calling the factory callback, waiting for its result, and then appending
@ -487,12 +551,19 @@ export async function renderHead(result: SSRResult): Promise<string> {
});
});
if (needsHydrationStyles) {
styles.push(renderElement('style', { props: { 'astro-style': true }, children: 'astro-root, astro-fragment { display: contents; }' }));
styles.push(
renderElement('style', {
props: { 'astro-style': true },
children: 'astro-root, astro-fragment { display: contents; }',
})
);
}
const links = Array.from(result.links)
.filter(uniqueElements)
.map((link) => renderElement('link', link, false));
return markHTMLString(links.join('\n') + styles.join('\n') + scripts.join('\n') + '\n' + '<!--astro:head:injected-->');
return markHTMLString(
links.join('\n') + styles.join('\n') + scripts.join('\n') + '\n' + '<!--astro:head:injected-->'
);
}
export async function renderAstroComponent(component: InstanceType<typeof AstroComponent>) {
@ -511,7 +582,12 @@ function componentIsHTMLElement(Component: unknown) {
return typeof HTMLElement !== 'undefined' && HTMLElement.isPrototypeOf(Component as object);
}
export async function renderHTMLElement(result: SSRResult, constructor: typeof HTMLElement, props: any, slots: any) {
export async function renderHTMLElement(
result: SSRResult,
constructor: typeof HTMLElement,
props: any,
slots: any
) {
const name = getHTMLElementName(constructor);
let attrHTML = '';
@ -520,11 +596,15 @@ export async function renderHTMLElement(result: SSRResult, constructor: typeof H
attrHTML += ` ${attr}="${toAttributeString(await props[attr])}"`;
}
return markHTMLString(`<${name}${attrHTML}>${await renderSlot(result, slots?.default)}</${name}>`);
return markHTMLString(
`<${name}${attrHTML}>${await renderSlot(result, slots?.default)}</${name}>`
);
}
function getHTMLElementName(constructor: typeof HTMLElement) {
const definedName = (customElements as CustomElementRegistry & { getName(_constructor: typeof HTMLElement): string }).getName(constructor);
const definedName = (
customElements as CustomElementRegistry & { getName(_constructor: typeof HTMLElement): string }
).getName(constructor);
if (definedName) return definedName;
const assignedName = constructor.name
@ -535,7 +615,11 @@ function getHTMLElementName(constructor: typeof HTMLElement) {
return assignedName;
}
function renderElement(name: string, { props: _props, children = '' }: SSRElement, shouldEscape = true) {
function renderElement(
name: string,
{ props: _props, children = '' }: SSRElement,
shouldEscape = true
) {
// Do not print `hoist`, `lang`, `is:global`
const { lang: _, 'data-astro-id': astroId, 'define:vars': defineVars, ...props } = _props;
if (defineVars) {

View file

@ -15,7 +15,13 @@ interface ErrorTemplateOptions {
}
/** Display all errors */
export default function template({ title, pathname, statusCode = 404, tabTitle, body }: ErrorTemplateOptions): string {
export default function template({
title,
pathname,
statusCode = 404,
tabTitle,
body,
}: ErrorTemplateOptions): string {
return `<!doctype html>
<html lang="en">
<head>
@ -46,7 +52,9 @@ export default function template({ title, pathname, statusCode = 404, tabTitle,
<body>
<main class="center">
<svg class="astro" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M163.008 18.929c1.944 2.413 2.935 5.67 4.917 12.181l43.309 142.27a180.277 180.277 0 00-51.778-17.53l-28.198-95.29a3.67 3.67 0 00-7.042.01l-27.857 95.232a180.225 180.225 0 00-52.01 17.557l43.52-142.281c1.99-6.502 2.983-9.752 4.927-12.16a15.999 15.999 0 016.484-4.798c2.872-1.154 6.271-1.154 13.07-1.154h31.085c6.807 0 10.211 0 13.086 1.157a16.004 16.004 0 016.487 4.806z" fill="white"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M168.19 180.151c-7.139 6.105-21.39 10.268-37.804 10.268-20.147 0-37.033-6.272-41.513-14.707-1.602 4.835-1.961 10.367-1.961 13.902 0 0-1.056 17.355 11.015 29.426 0-6.268 5.081-11.349 11.349-11.349 10.743 0 10.731 9.373 10.721 16.977v.679c0 11.542 7.054 21.436 17.086 25.606a23.27 23.27 0 01-2.339-10.2c0-11.008 6.463-15.107 13.974-19.87 5.976-3.79 12.616-8.001 17.192-16.449a31.024 31.024 0 003.743-14.82c0-3.299-.513-6.479-1.463-9.463z" fill="#ff5d01"></path></svg>
<h1>${statusCode ? `<span class="statusCode">${statusCode}: </span> ` : ''}<span class="statusMessage">${title}</span></h1>
<h1>${
statusCode ? `<span class="statusCode">${statusCode}: </span> ` : ''
}<span class="statusMessage">${title}</span></h1>
${
body ||
`

View file

@ -17,7 +17,14 @@ interface ErrorTemplateOptions {
}
/** Display all errors */
export default function template({ title, url, message, stack, statusCode, tabTitle }: ErrorTemplateOptions): string {
export default function template({
title,
url,
message,
stack,
statusCode,
tabTitle,
}: ErrorTemplateOptions): string {
let error = url ? message.replace(url, '') : message;
return `<!doctype html>
<html lang="en">
@ -60,7 +67,9 @@ export default function template({ title, url, message, stack, statusCode, tabTi
<main class="wrapper">
<header>
<svg class="astro" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M163.008 18.929c1.944 2.413 2.935 5.67 4.917 12.181l43.309 142.27a180.277 180.277 0 00-51.778-17.53l-28.198-95.29a3.67 3.67 0 00-7.042.01l-27.857 95.232a180.225 180.225 0 00-52.01 17.557l43.52-142.281c1.99-6.502 2.983-9.752 4.927-12.16a15.999 15.999 0 016.484-4.798c2.872-1.154 6.271-1.154 13.07-1.154h31.085c6.807 0 10.211 0 13.086 1.157a16.004 16.004 0 016.487 4.806z" fill="white"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M168.19 180.151c-7.139 6.105-21.39 10.268-37.804 10.268-20.147 0-37.033-6.272-41.513-14.707-1.602 4.835-1.961 10.367-1.961 13.902 0 0-1.056 17.355 11.015 29.426 0-6.268 5.081-11.349 11.349-11.349 10.743 0 10.731 9.373 10.721 16.977v.679c0 11.542 7.054 21.436 17.086 25.606a23.27 23.27 0 01-2.339-10.2c0-11.008 6.463-15.107 13.974-19.87 5.976-3.79 12.616-8.001 17.192-16.449a31.024 31.024 0 003.743-14.82c0-3.299-.513-6.479-1.463-9.463z" fill="#ff5d01"></path></svg>
<h1>${statusCode ? `<span class="statusCode">${statusCode}: </span> ` : ''}<span class="statusMessage">${title}</span></h1>
<h1>${
statusCode ? `<span class="statusCode">${statusCode}: </span> ` : ''
}<span class="statusMessage">${title}</span></h1>
</header>
<pre>${encode(error)}</pre>
${url ? `<a target="_blank" href="${url}">${url}</a>` : ''}

View file

@ -1,5 +1,9 @@
import { parse as babelParser } from '@babel/parser';
import type { ArrowFunctionExpressionKind, CallExpressionKind, StringLiteralKind } from 'ast-types/gen/kinds';
import type {
ArrowFunctionExpressionKind,
CallExpressionKind,
StringLiteralKind,
} from 'ast-types/gen/kinds';
import type { NodePath } from 'ast-types/lib/node-path';
import { parse, print, types, visit } from 'recast';
import type { Plugin } from 'vite';
@ -58,7 +62,11 @@ export default function astro({ config }: AstroPluginOptions): Plugin {
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: { type: 'MetaProperty', meta: { type: 'Identifier', name: 'import' }, property: { type: 'Identifier', name: 'meta' } },
object: {
type: 'MetaProperty',
meta: { type: 'Identifier', name: 'import' },
property: { type: 'Identifier', name: 'meta' },
},
property: { type: 'Identifier', name: 'glob' },
computed: false,
},

View file

@ -22,7 +22,11 @@ interface AstroPluginOptions {
logging: LogOptions;
}
const BAD_VITE_MIDDLEWARE = ['viteIndexHtmlMiddleware', 'vite404Middleware', 'viteSpaFallbackMiddleware'];
const BAD_VITE_MIDDLEWARE = [
'viteIndexHtmlMiddleware',
'vite404Middleware',
'viteSpaFallbackMiddleware',
];
function removeViteHttpMiddleware(server: vite.Connect.Server) {
for (let i = server.stack.length - 1; i > 0; i--) {
// @ts-expect-error using internals until https://github.com/vitejs/vite/pull/4640 is merged
@ -62,7 +66,11 @@ async function writeWebResponse(res: http.ServerResponse, webResponse: Response)
res.end();
}
async function writeSSRResult(result: RenderResponse, res: http.ServerResponse, statusCode: 200 | 404) {
async function writeSSRResult(
result: RenderResponse,
res: http.ServerResponse,
statusCode: 200 | 404
) {
if (result.type === 'response') {
const { response } = result;
await writeWebResponse(res, response);
@ -73,7 +81,12 @@ async function writeSSRResult(result: RenderResponse, res: http.ServerResponse,
writeHtmlResponse(res, statusCode, html);
}
async function handle404Response(origin: string, config: AstroConfig, req: http.IncomingMessage, res: http.ServerResponse) {
async function handle404Response(
origin: string,
config: AstroConfig,
req: http.IncomingMessage,
res: http.ServerResponse
) {
const site = config.site ? new URL(config.base, config.site) : undefined;
const devRoot = site ? site.pathname : '/';
const pathname = decodeURI(new URL(origin + req.url).pathname);
@ -81,12 +94,23 @@ async function handle404Response(origin: string, config: AstroConfig, req: http.
if (pathname === '/' && !pathname.startsWith(devRoot)) {
html = subpathNotUsedTemplate(devRoot, pathname);
} else {
html = notFoundTemplate({ statusCode: 404, title: 'Not found', tabTitle: '404: Not Found', pathname });
html = notFoundTemplate({
statusCode: 404,
title: 'Not found',
tabTitle: '404: Not Found',
pathname,
});
}
writeHtmlResponse(res, 404, html);
}
async function handle500Response(viteServer: vite.ViteDevServer, origin: string, req: http.IncomingMessage, res: http.ServerResponse, err: any) {
async function handle500Response(
viteServer: vite.ViteDevServer,
origin: string,
req: http.IncomingMessage,
res: http.ServerResponse,
err: any
) {
const pathname = decodeURI(new URL(origin + req.url).pathname);
const html = serverErrorTemplate({
statusCode: 500,
@ -188,12 +212,20 @@ async function handleRequest(
ssr: isBuildingToSSR(config),
});
if (paramsAndPropsRes === GetParamsAndPropsError.NoMatchingStaticPath) {
warn(logging, 'getStaticPaths', `Route pattern matched, but no matching static path found. (${pathname})`);
warn(
logging,
'getStaticPaths',
`Route pattern matched, but no matching static path found. (${pathname})`
);
log404(logging, pathname);
const routeCustom404 = getCustom404Route(config, manifest);
if (routeCustom404) {
const filePathCustom404 = new URL(`./${routeCustom404.component}`, config.root);
const preloadedCompCustom404 = await preload({ astroConfig: config, filePath: filePathCustom404, viteServer });
const preloadedCompCustom404 = await preload({
astroConfig: config,
filePath: filePathCustom404,
viteServer,
});
const result = await ssr(preloadedCompCustom404, {
astroConfig: config,
filePath: filePathCustom404,

View file

@ -34,7 +34,13 @@ function safelyReplaceImportPlaceholder(code: string) {
const configCache = new WeakMap<AstroConfig, CompilationCache>();
async function compile(config: AstroConfig, filename: string, source: string, viteTransform: TransformHook, opts: { ssr: boolean }): Promise<CompileResult> {
async function compile(
config: AstroConfig,
filename: string,
source: string,
viteTransform: TransformHook,
opts: { ssr: boolean }
): Promise<CompileResult> {
const filenameURL = new URL(`file://${filename}`);
const normalizedID = fileURLToPath(filenameURL);
const pathname = filenameURL.pathname.substr(config.root.pathname.length - 1);
@ -51,7 +57,9 @@ async function compile(config: AstroConfig, filename: string, source: string, vi
site: config.site ? new URL(config.base, config.site).toString() : undefined,
sourcefile: filename,
sourcemap: 'both',
internalURL: `/@fs${prependForwardSlash(viteID(new URL('../runtime/server/index.js', import.meta.url)))}`,
internalURL: `/@fs${prependForwardSlash(
viteID(new URL('../runtime/server/index.js', import.meta.url))
)}`,
// TODO: baseline flag
experimentalStaticExtraction: true,
preprocessStyle: async (value: string, attrs: Record<string, string>) => {
@ -59,7 +67,9 @@ async function compile(config: AstroConfig, filename: string, source: string, vi
try {
// In the static build, grab any @import as CSS dependencies for HMR.
value.replace(/(?:@import)\s(?:url\()?\s?["\'](.*?)["\']\s?\)?(?:[^;]*);?/gi, (match, spec) => {
value.replace(
/(?:@import)\s(?:url\()?\s?["\'](.*?)["\']\s?\)?(?:[^;]*);?/gi,
(match, spec) => {
rawCSSDeps.add(spec);
// If the language is CSS: prevent `@import` inlining to prevent scoping of imports.
// Otherwise: Sass, etc. need to see imports for variables, so leave in for their compiler to handle.
@ -68,7 +78,8 @@ async function compile(config: AstroConfig, filename: string, source: string, vi
} else {
return match;
}
});
}
);
const result = await transformWithVite({
value,
@ -120,7 +131,13 @@ export function invalidateCompilation(config: AstroConfig, filename: string) {
}
}
export async function cachedCompilation(config: AstroConfig, filename: string, source: string, viteTransform: TransformHook, opts: { ssr: boolean }): Promise<CompileResult> {
export async function cachedCompilation(
config: AstroConfig,
filename: string,
source: string,
viteTransform: TransformHook,
opts: { ssr: boolean }
): Promise<CompileResult> {
let cache: CompilationCache;
if (!configCache.has(config)) {
cache = new Map();

View file

@ -13,7 +13,10 @@ interface TrackCSSDependenciesOptions {
deps: Set<string>;
}
export async function trackCSSDependencies(this: RollupPluginContext, opts: TrackCSSDependenciesOptions): Promise<void> {
export async function trackCSSDependencies(
this: RollupPluginContext,
opts: TrackCSSDependenciesOptions
): Promise<void> {
const { viteDevServer, filename, deps, id } = opts;
// Dev, register CSS dependencies for HMR.
if (viteDevServer) {

View file

@ -105,10 +105,17 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
throw new Error(`Requests for Astro CSS must include an index.`);
}
const transformResult = await cachedCompilation(config, filename, source, viteTransform, { ssr: Boolean(opts?.ssr) });
const transformResult = await cachedCompilation(config, filename, source, viteTransform, {
ssr: Boolean(opts?.ssr),
});
// Track any CSS dependencies so that HMR is triggered when they change.
await trackCSSDependencies.call(this, { viteDevServer, id, filename, deps: transformResult.rawCSSDeps });
await trackCSSDependencies.call(this, {
viteDevServer,
id,
filename,
deps: transformResult.rawCSSDeps,
});
const csses = transformResult.css;
const code = csses[query.index];
@ -120,7 +127,9 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
throw new Error(`Requests for hoisted scripts must include an index`);
}
const transformResult = await cachedCompilation(config, filename, source, viteTransform, { ssr: Boolean(opts?.ssr) });
const transformResult = await cachedCompilation(config, filename, source, viteTransform, {
ssr: Boolean(opts?.ssr),
});
const scripts = transformResult.scripts;
const hoistedScript = scripts[query.index];
@ -139,13 +148,18 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
}
return {
code: hoistedScript.type === 'inline' ? hoistedScript.code! : `import "${hoistedScript.src!}";`,
code:
hoistedScript.type === 'inline'
? hoistedScript.code!
: `import "${hoistedScript.src!}";`,
};
}
}
try {
const transformResult = await cachedCompilation(config, filename, source, viteTransform, { ssr: Boolean(opts?.ssr) });
const transformResult = await cachedCompilation(config, filename, source, viteTransform, {
ssr: Boolean(opts?.ssr),
});
// Compile all TypeScript to JavaScript.
// Also, catches invalid JS/TS in the compiled output before returning.
@ -182,12 +196,19 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
const scannedFrontmatter = FRONTMATTER_PARSE_REGEXP.exec(source);
if (scannedFrontmatter) {
try {
await esbuild.transform(scannedFrontmatter[1], { loader: 'ts', sourcemap: false, sourcefile: id });
await esbuild.transform(scannedFrontmatter[1], {
loader: 'ts',
sourcemap: false,
sourcefile: id,
});
} catch (frontmatterErr: any) {
// Improve the error by replacing the phrase "unexpected end of file"
// with "unexpected end of frontmatter" in the esbuild error message.
if (frontmatterErr && frontmatterErr.message) {
frontmatterErr.message = frontmatterErr.message.replace('end of file', 'end of frontmatter');
frontmatterErr.message = frontmatterErr.message.replace(
'end of file',
'end of frontmatter'
);
}
throw frontmatterErr;
}

View file

@ -2,7 +2,11 @@ import type * as vite from 'vite';
import { STYLE_EXTENSIONS } from '../core/render/util.js';
export type TransformHook = (code: string, id: string, ssr?: boolean) => Promise<vite.TransformResult>;
export type TransformHook = (
code: string,
id: string,
ssr?: boolean
) => Promise<vite.TransformResult>;
/** Load vite:css transform() hook */
export function getViteTransform(viteConfig: vite.ResolvedConfig): TransformHook {
@ -21,7 +25,13 @@ interface TransformWithViteOptions {
}
/** Transform style using Vite hook */
export async function transformWithVite({ value, lang, transformHook, id, ssr }: TransformWithViteOptions): Promise<vite.TransformResult | null> {
export async function transformWithVite({
value,
lang,
transformHook,
id,
ssr,
}: TransformWithViteOptions): Promise<vite.TransformResult | null> {
if (!STYLE_EXTENSIONS.has(lang)) {
return null; // only preprocess langs supported by Vite
}

View file

@ -5,7 +5,11 @@ import * as path from 'path';
import esbuild from 'esbuild';
import { Plugin as VitePlugin } from 'vite';
import { isCSSRequest } from '../core/render/util.js';
import { getPageDatasByChunk, getPageDataByViteID, hasPageDataByViteID } from '../core/build/internal.js';
import {
getPageDatasByChunk,
getPageDataByViteID,
hasPageDataByViteID,
} from '../core/build/internal.js';
const PLUGIN_NAME = '@astrojs/rollup-plugin-build-css';
@ -52,7 +56,11 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
const { internals, legacy } = options;
const styleSourceMap = new Map<string, string>();
function* walkStyles(ctx: PluginContext, id: string, seen = new Set<string>()): Generator<[string, string], void, unknown> {
function* walkStyles(
ctx: PluginContext,
id: string,
seen = new Set<string>()
): Generator<[string, string], void, unknown> {
seen.add(id);
if (styleSourceMap.has(id)) {
yield [id, styleSourceMap.get(id)!];
@ -203,12 +211,19 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
// Delete CSS chunks so JS is not produced for them.
async generateBundle(opts, bundle) {
const hasPureCSSChunks = internals.pureCSSChunks.size;
const pureChunkFilenames = new Set([...internals.pureCSSChunks].map((chunk) => chunk.fileName));
const pureChunkFilenames = new Set(
[...internals.pureCSSChunks].map((chunk) => chunk.fileName)
);
const emptyChunkFiles = [...pureChunkFilenames]
.map((file) => path.basename(file))
.join('|')
.replace(/\./g, '\\.');
const emptyChunkRE = new RegExp(opts.format === 'es' ? `\\bimport\\s*"[^"]*(?:${emptyChunkFiles})";\n?` : `\\brequire\\(\\s*"[^"]*(?:${emptyChunkFiles})"\\);\n?`, 'g');
const emptyChunkRE = new RegExp(
opts.format === 'es'
? `\\bimport\\s*"[^"]*(?:${emptyChunkFiles})";\n?`
: `\\brequire\\(\\s*"[^"]*(?:${emptyChunkFiles})"\\);\n?`,
'g'
);
// Crawl the module graph to find CSS chunks to create
if (!legacy) {

View file

@ -12,7 +12,9 @@ function getSrcSetUrls(srcset: string) {
return [];
}
const srcsetParts = srcset.includes(',') ? srcset.split(',') : [srcset];
const urls = srcsetParts.map((url) => url.trim()).map((url) => (url.includes(' ') ? url.split(' ')[0] : url));
const urls = srcsetParts
.map((url) => url.trim())
.map((url) => (url.includes(' ') ? url.split(' ')[0] : url));
return urls;
}
@ -116,9 +118,20 @@ export function isHashedAsset(node: Element) {
}
}
export function resolveAssetFilePath(browserPath: string, htmlDir: string, projectRootDir: string, absolutePathPrefix?: string) {
const _browserPath = absolutePathPrefix && browserPath[0] === '/' ? '/' + npath.posix.relative(absolutePathPrefix, browserPath) : browserPath;
return npath.join(_browserPath.startsWith('/') ? projectRootDir : htmlDir, _browserPath.split('/').join(npath.sep));
export function resolveAssetFilePath(
browserPath: string,
htmlDir: string,
projectRootDir: string,
absolutePathPrefix?: string
) {
const _browserPath =
absolutePathPrefix && browserPath[0] === '/'
? '/' + npath.posix.relative(absolutePathPrefix, browserPath)
: browserPath;
return npath.join(
_browserPath.startsWith('/') ? projectRootDir : htmlDir,
_browserPath.split('/').join(npath.sep)
);
}
export function getSourceAttribute(node: Element) {

View file

@ -1,4 +1,12 @@
import { createElement, createScript, getAttribute, hasAttribute, insertBefore, remove, setAttribute } from '@web/parse5-utils';
import {
createElement,
createScript,
getAttribute,
hasAttribute,
insertBefore,
remove,
setAttribute,
} from '@web/parse5-utils';
import { promises as fs } from 'fs';
import parse5 from 'parse5';
import * as npath from 'path';
@ -15,8 +23,21 @@ import { RouteCache } from '../core/render/route-cache.js';
import { getOutputFilename } from '../core/util.js';
import { getAstroPageStyleId, getAstroStyleId } from '../vite-plugin-build-css/index.js';
import { addRollupInput } from './add-rollup-input.js';
import { findAssets, findExternalScripts, findInlineScripts, findInlineStyles, getAttributes, getTextContent } from './extract-assets.js';
import { hasSrcSet, isBuildableImage, isBuildableLink, isHoistedScript, isInSrcDirectory } from './util.js';
import {
findAssets,
findExternalScripts,
findInlineScripts,
findInlineStyles,
getAttributes,
getTextContent,
} from './extract-assets.js';
import {
hasSrcSet,
isBuildableImage,
isBuildableLink,
isHoistedScript,
isInSrcDirectory,
} from './util.js';
import { createRequest } from '../core/request.js';
// This package isn't real ESM, so have to coerce it
@ -45,7 +66,8 @@ function relativePath(from: string, to: string): string {
}
export function rollupPluginAstroScanHTML(options: PluginOptions): VitePlugin {
const { astroConfig, internals, logging, origin, allPages, routeCache, viteServer, pageNames } = options;
const { astroConfig, internals, logging, origin, allPages, routeCache, viteServer, pageNames } =
options;
// The filepath root of the src folder
const srcRoot = astroConfig.srcDir.pathname;
@ -322,7 +344,12 @@ export function rollupPluginAstroScanHTML(options: PluginOptions): VitePlugin {
// Keep track of links added so we don't do so twice.
const linkChunksAdded = new Set<string>();
const appendStyleChunksBefore = (ref: parse5.Element, pathname: string, referenceIds: string[] | undefined, attrs: Record<string, any> = {}) => {
const appendStyleChunksBefore = (
ref: parse5.Element,
pathname: string,
referenceIds: string[] | undefined,
attrs: Record<string, any> = {}
) => {
let added = false;
if (referenceIds) {
const lastNode = ref;
@ -433,7 +460,12 @@ export function rollupPluginAstroScanHTML(options: PluginOptions): VitePlugin {
if (!pageCSSAdded) {
const attrs = getAttributes(node);
delete attrs['data-astro-injected'];
pageCSSAdded = appendStyleChunksBefore(node, pathname, cssChunkMap.get(styleId), attrs);
pageCSSAdded = appendStyleChunksBefore(
node,
pathname,
cssChunkMap.get(styleId),
attrs
);
}
remove(node);
break;

View file

@ -12,7 +12,12 @@ function startsWithSrcRoot(pathname: string, srcRoot: string, srcRootWeb: string
); // Windows fix: some paths are missing leading "/"
}
export function isInSrcDirectory(node: parse5.Element, attr: string, srcRoot: string, srcRootWeb: string): boolean {
export function isInSrcDirectory(
node: parse5.Element,
attr: string,
srcRoot: string,
srcRootWeb: string
): boolean {
const value = getAttribute(node, attr);
return value ? startsWithSrcRoot(value, srcRoot, srcRootWeb) : false;
}
@ -21,7 +26,11 @@ export function isAstroInjectedLink(node: parse5.Element): boolean {
return isStylesheetLink(node) && getAttribute(node, 'data-astro-injected') === '';
}
export function isBuildableLink(node: parse5.Element, srcRoot: string, srcRootWeb: string): boolean {
export function isBuildableLink(
node: parse5.Element,
srcRoot: string,
srcRootWeb: string
): boolean {
if (isAstroInjectedLink(node)) {
return true;
}
@ -34,7 +43,11 @@ export function isBuildableLink(node: parse5.Element, srcRoot: string, srcRootWe
return startsWithSrcRoot(href, srcRoot, srcRootWeb);
}
export function isBuildableImage(node: parse5.Element, srcRoot: string, srcRootWeb: string): boolean {
export function isBuildableImage(
node: parse5.Element,
srcRoot: string,
srcRootWeb: string
): boolean {
if (getTagName(node) === 'img') {
const src = getAttribute(node, 'src');
return src ? startsWithSrcRoot(src, srcRoot, srcRootWeb) : false;

View file

@ -14,7 +14,10 @@ export declare interface Alias {
const normalize = (pathname: string) => String(pathname).split(path.sep).join(path.posix.sep);
/** Returns the results of a config file if it exists, otherwise null. */
const getExistingConfig = (searchName: string, cwd: string | undefined): tsr.TsConfigResultSuccess | null => {
const getExistingConfig = (
searchName: string,
cwd: string | undefined
): tsr.TsConfigResultSuccess | null => {
const config = tsr.tsconfigResolverSync({ cwd, searchName });
return config.exists ? config : null;
@ -35,24 +38,37 @@ const getConfigAlias = (cwd: string | undefined): Alias[] | null => {
if (!compilerOptions.baseUrl) return null;
// resolve the base url from the configuration file directory
const baseUrl = path.posix.resolve(path.posix.dirname(normalize(config.path).replace(/^\/?/, '/')), normalize(compilerOptions.baseUrl));
const baseUrl = path.posix.resolve(
path.posix.dirname(normalize(config.path).replace(/^\/?/, '/')),
normalize(compilerOptions.baseUrl)
);
/** List of compiled alias expressions. */
const aliases: Alias[] = [];
// compile any alias expressions and push them to the list
for (let [alias, values] of Object.entries(Object(compilerOptions.paths) as { [key: string]: string[] })) {
for (let [alias, values] of Object.entries(
Object(compilerOptions.paths) as { [key: string]: string[] }
)) {
values = [].concat(values as never);
/** Regular Expression used to match a given path. */
const find = new RegExp(`^${[...alias].map((segment) => (segment === '*' ? '(.+)' : segment.replace(/[\\^$*+?.()|[\]{}]/, '\\$&'))).join('')}$`);
const find = new RegExp(
`^${[...alias]
.map((segment) =>
segment === '*' ? '(.+)' : segment.replace(/[\\^$*+?.()|[\]{}]/, '\\$&')
)
.join('')}$`
);
/** Internal index used to calculate the matching id in a replacement. */
let matchId = 0;
for (let value of values) {
/** String used to replace a matched path. */
const replacement = [...path.posix.resolve(baseUrl, value)].map((segment) => (segment === '*' ? `$${++matchId}` : segment === '$' ? '$$' : segment)).join('');
const replacement = [...path.posix.resolve(baseUrl, value)]
.map((segment) => (segment === '*' ? `$${++matchId}` : segment === '$' ? '$$' : segment))
.join('');
aliases.push({ find, replacement });
}
@ -70,7 +86,10 @@ const getConfigAlias = (cwd: string | undefined): Alias[] | null => {
};
/** Returns a Vite plugin used to alias pathes from tsconfig.json and jsconfig.json. */
export default function configAliasVitePlugin(astroConfig: { root?: URL; [key: string]: unknown }): vite.PluginOption {
export default function configAliasVitePlugin(astroConfig: {
root?: URL;
[key: string]: unknown;
}): vite.PluginOption {
/** Aliases from the tsconfig.json or jsconfig.json configuration. */
const configAlias = getConfigAlias(astroConfig.root && url.fileURLToPath(astroConfig.root));
@ -94,7 +113,10 @@ export default function configAliasVitePlugin(astroConfig: { root?: URL; [key: s
const aliasedSourceId = sourceId.replace(alias.find, alias.replacement);
/** Resolved ID conditionally handled by any other resolver. (this also gives priority to all other resolvers) */
const resolvedAliasedId = await this.resolve(aliasedSourceId, importer, { skipSelf: true, ...options });
const resolvedAliasedId = await this.resolve(aliasedSourceId, importer, {
skipSelf: true,
...options,
});
// if the existing resolvers find the file, return that resolution
if (resolvedAliasedId) return resolvedAliasedId;

View file

@ -12,9 +12,15 @@ interface EnvPluginOptions {
function getPrivateEnv(viteConfig: vite.ResolvedConfig, astroConfig: AstroConfig) {
let envPrefixes: string[] = ['PUBLIC_'];
if (viteConfig.envPrefix) {
envPrefixes = Array.isArray(viteConfig.envPrefix) ? viteConfig.envPrefix : [viteConfig.envPrefix];
envPrefixes = Array.isArray(viteConfig.envPrefix)
? viteConfig.envPrefix
: [viteConfig.envPrefix];
}
const fullEnv = loadEnv(viteConfig.mode, viteConfig.envDir ?? fileURLToPath(astroConfig.root), '');
const fullEnv = loadEnv(
viteConfig.mode,
viteConfig.envDir ?? fileURLToPath(astroConfig.root),
''
);
const privateKeys = Object.keys(fullEnv).filter((key) => {
// don't expose any variables also on `process.env`
// note: this filters out `CLI_ARGS=1` passed to node!
@ -44,7 +50,9 @@ function getReferencedPrivateKeys(source: string, privateEnv: Record<string, any
return references;
}
export default function envVitePlugin({ config: astroConfig }: EnvPluginOptions): vite.PluginOption {
export default function envVitePlugin({
config: astroConfig,
}: EnvPluginOptions): vite.PluginOption {
let privateEnv: Record<string, any> | null;
let config: vite.ResolvedConfig;
let replacements: Record<string, string>;
@ -69,7 +77,10 @@ export default function envVitePlugin({ config: astroConfig }: EnvPluginOptions)
if (typeof privateEnv === 'undefined') {
privateEnv = getPrivateEnv(config, astroConfig);
if (privateEnv) {
const entries = Object.entries(privateEnv).map(([key, value]) => [`import.meta.env.${key}`, value]);
const entries = Object.entries(privateEnv).map(([key, value]) => [
`import.meta.env.${key}`,
value,
]);
replacements = Object.fromEntries(entries);
// These additional replacements are needed to match Vite
replacements = Object.assign(replacements, {

View file

@ -3,7 +3,11 @@ import { AstroConfig } from '../@types/astro.js';
import { runHookServerSetup } from '../integrations/index.js';
/** Connect Astro integrations into Vite, as needed. */
export default function astroIntegrationsContainerPlugin({ config }: { config: AstroConfig }): VitePlugin {
export default function astroIntegrationsContainerPlugin({
config,
}: {
config: AstroConfig;
}): VitePlugin {
return {
name: 'astro:integration-container',
configureServer(server) {

View file

@ -30,7 +30,9 @@ function getEsbuildLoader(fileExt: string): string {
function collectJSXRenderers(renderers: AstroRenderer[]): Map<string, AstroRenderer> {
const renderersWithJSXSupport = renderers.filter((r) => r.jsxImportSource);
return new Map(renderersWithJSXSupport.map((r) => [r.jsxImportSource, r] as [string, AstroRenderer]));
return new Map(
renderersWithJSXSupport.map((r) => [r.jsxImportSource, r] as [string, AstroRenderer])
);
}
interface TransformJSXOptions {
@ -41,7 +43,13 @@ interface TransformJSXOptions {
ssr: boolean;
}
async function transformJSX({ code, mode, id, ssr, renderer }: TransformJSXOptions): Promise<TransformResult> {
async function transformJSX({
code,
mode,
id,
ssr,
renderer,
}: TransformJSXOptions): Promise<TransformResult> {
const { jsxTransformOptions } = renderer;
const options = await jsxTransformOptions!({ mode, ssr });
const plugins = [...(options.plugins || [])];
@ -117,7 +125,13 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
sourcefile: id,
sourcemap: 'inline',
});
return transformJSX({ code: jsxCode, id, renderer: [...jsxRenderers.values()][0], mode, ssr });
return transformJSX({
code: jsxCode,
id,
renderer: [...jsxRenderers.values()][0],
mode,
ssr,
});
}
// Attempt: Multiple JSX renderers
@ -165,7 +179,13 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
const jsxRenderer = jsxRenderers.get(importSource);
// if renderer not installed for this JSX source, throw error
if (!jsxRenderer) {
error(logging, 'renderer', `${colors.yellow(id)} No renderer installed for ${importSource}. Try adding \`@astrojs/${importSource}\` to your project.`);
error(
logging,
'renderer',
`${colors.yellow(
id
)} No renderer installed for ${importSource}. Try adding \`@astrojs/${importSource}\` to your project.`
);
return null;
}
// downlevel any non-standard syntax, but preserve JSX
@ -175,7 +195,13 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
sourcefile: id,
sourcemap: 'inline',
});
return await transformJSX({ code: jsxCode, id, renderer: jsxRenderers.get(importSource) as AstroRenderer, mode, ssr });
return await transformJSX({
code: jsxCode,
id,
renderer: jsxRenderers.get(importSource) as AstroRenderer,
mode,
ssr,
});
}
// if we still cant tell, throw error
@ -185,7 +211,9 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
'renderer',
`${colors.yellow(id)}
Unable to resolve a renderer that handles this file! With more than one renderer enabled, you should include an import or use a pragma comment.
Add ${colors.cyan(IMPORT_STATEMENTS[defaultRenderer] || `import '${defaultRenderer}';`)} or ${colors.cyan(`/* jsxImportSource: ${defaultRenderer} */`)} to this file.
Add ${colors.cyan(
IMPORT_STATEMENTS[defaultRenderer] || `import '${defaultRenderer}';`
)} or ${colors.cyan(`/* jsxImportSource: ${defaultRenderer} */`)} to this file.
`
);
return null;

View file

@ -82,9 +82,13 @@ export default function markdown({ config }: AstroPluginOptions): Plugin {
// Return the file's JS representation, including all Markdown
// frontmatter and a deferred `import() of the compiled markdown content.
if (id.startsWith(VIRTUAL_MODULE_ID)) {
const sitePathname = config.site ? appendForwardSlash(new URL(config.base, config.site).pathname) : '/';
const sitePathname = config.site
? appendForwardSlash(new URL(config.base, config.site).pathname)
: '/';
const fileId = id.substring(VIRTUAL_MODULE_ID.length);
const fileUrl = fileId.includes('/pages/') ? fileId.replace(/^.*\/pages\//, sitePathname).replace(/(\/index)?\.md$/, '') : undefined;
const fileUrl = fileId.includes('/pages/')
? fileId.replace(/^.*\/pages\//, sitePathname).replace(/(\/index)?\.md$/, '')
: undefined;
const source = await fs.promises.readFile(fileId, 'utf8');
const { data: frontmatter } = matter(source);
return {
@ -160,7 +164,11 @@ export const frontmatter = ${JSON.stringify(content)};
${tsResult}`;
// Compile from `.ts` to `.js`
const { code } = await esbuild.transform(tsResult, { loader: 'ts', sourcemap: false, sourcefile: id });
const { code } = await esbuild.transform(tsResult, {
loader: 'ts',
sourcemap: false,
sourcefile: id,
});
return {
code,
map: null,

View file

@ -5,7 +5,9 @@ import { AstroConfig, InjectedScriptStage } from '../@types/astro.js';
// inject these as ESM imports into actual code, where they would not
// resolve correctly.
const SCRIPT_ID_PREFIX = `astro:scripts/`;
export const BEFORE_HYDRATION_SCRIPT_ID = `${SCRIPT_ID_PREFIX}${'before-hydration' as InjectedScriptStage}.js`;
export const BEFORE_HYDRATION_SCRIPT_ID = `${SCRIPT_ID_PREFIX}${
'before-hydration' as InjectedScriptStage
}.js`;
export const PAGE_SCRIPT_ID = `${SCRIPT_ID_PREFIX}${'page' as InjectedScriptStage}.js`;
export const PAGE_SSR_SCRIPT_ID = `${SCRIPT_ID_PREFIX}${'page-ssr' as InjectedScriptStage}.js`;
@ -45,7 +47,9 @@ export default function astroScriptsPlugin({ config }: { config: AstroConfig }):
// for the frontend AND some hydrated components exist in
// the final build. We can detect this by looking for a
// `astro/client/*` input, which signifies both conditions are met.
const hasHydratedComponents = Array.isArray(options.input) && options.input.some((input) => input.startsWith('astro/client'));
const hasHydratedComponents =
Array.isArray(options.input) &&
options.input.some((input) => input.startsWith('astro/client'));
const hasHydrationScripts = config._ctx.scripts.some((s) => s.stage === 'before-hydration');
if (hasHydratedComponents && hasHydrationScripts) {
this.emitFile({

View file

@ -214,37 +214,49 @@ describe('CSS', function () {
it('<style>', async () => {
const el = $('#svelte-css');
const classes = el.attr('class').split(' ');
const scopedClass = classes.find((name) => name !== 'svelte-css' && /^svelte-[A-Za-z0-9-]+/.test(name));
const scopedClass = classes.find(
(name) => name !== 'svelte-css' && /^svelte-[A-Za-z0-9-]+/.test(name)
);
// 1. check HTML
expect(el.attr('class')).to.include('svelte-css');
// 2. check CSS
expect(bundledCSS).to.match(new RegExp(`.svelte-css.${scopedClass}[^{]*{font-family:Comic Sans MS`));
expect(bundledCSS).to.match(
new RegExp(`.svelte-css.${scopedClass}[^{]*{font-family:Comic Sans MS`)
);
});
it('<style lang="sass">', async () => {
const el = $('#svelte-sass');
const classes = el.attr('class').split(' ');
const scopedClass = classes.find((name) => name !== 'svelte-sass' && /^svelte-[A-Za-z0-9-]+/.test(name));
const scopedClass = classes.find(
(name) => name !== 'svelte-sass' && /^svelte-[A-Za-z0-9-]+/.test(name)
);
// 1. check HTML
expect(el.attr('class')).to.include('svelte-sass');
// 2. check CSS
expect(bundledCSS).to.match(new RegExp(`.svelte-sass.${scopedClass}[^{]*{font-family:Comic Sans MS`));
expect(bundledCSS).to.match(
new RegExp(`.svelte-sass.${scopedClass}[^{]*{font-family:Comic Sans MS`)
);
});
it('<style lang="scss">', async () => {
const el = $('#svelte-scss');
const classes = el.attr('class').split(' ');
const scopedClass = classes.find((name) => name !== 'svelte-scss' && /^svelte-[A-Za-z0-9-]+/.test(name));
const scopedClass = classes.find(
(name) => name !== 'svelte-scss' && /^svelte-[A-Za-z0-9-]+/.test(name)
);
// 1. check HTML
expect(el.attr('class')).to.include('svelte-scss');
// 2. check CSS
expect(bundledCSS).to.match(new RegExp(`.svelte-scss.${scopedClass}[^{]*{font-family:Comic Sans MS`));
expect(bundledCSS).to.match(
new RegExp(`.svelte-scss.${scopedClass}[^{]*{font-family:Comic Sans MS`)
);
});
});
});
@ -285,7 +297,14 @@ describe('CSS', function () {
});
it('resolves Styles from React', async () => {
const styles = ['ReactCSS.css', 'ReactModules.module.css', 'ReactModules.module.scss', 'ReactModules.module.sass', 'ReactSass.sass', 'ReactScss.scss'];
const styles = [
'ReactCSS.css',
'ReactModules.module.css',
'ReactModules.module.scss',
'ReactModules.module.sass',
'ReactSass.sass',
'ReactScss.scss',
];
for (const style of styles) {
const href = $(`link[href$="${style}"]`).attr('href');
expect((await fixture.fetch(href)).status, style).to.equal(200);
@ -293,7 +312,11 @@ describe('CSS', function () {
});
it('resolves CSS from Svelte', async () => {
const scripts = ['SvelteCSS.svelte?svelte&type=style&lang.css', 'SvelteSass.svelte?svelte&type=style&lang.css', 'SvelteScss.svelte?svelte&type=style&lang.css'];
const scripts = [
'SvelteCSS.svelte?svelte&type=style&lang.css',
'SvelteSass.svelte?svelte&type=style&lang.css',
'SvelteScss.svelte?svelte&type=style&lang.css',
];
for (const script of scripts) {
const src = $(`script[src$="${script}"]`).attr('src');
expect((await fixture.fetch(src)).status, script).to.equal(200);

View file

@ -104,7 +104,9 @@ describe('Astro basics', () => {
expect($('body > :nth-child(3)').prop('outerHTML')).to.equal('<input type="text">');
// <Input type="select"><option>option</option></Input>
expect($('body > :nth-child(4)').prop('outerHTML')).to.equal('<select><option>option</option></select>');
expect($('body > :nth-child(4)').prop('outerHTML')).to.equal(
'<select><option>option</option></select>'
);
// <Input type="textarea">textarea</Input>
expect($('body > :nth-child(5)').prop('outerHTML')).to.equal('<textarea>textarea</textarea>');

View file

@ -75,8 +75,14 @@ describe('Component children', () => {
expect($('#ssr-only').children()).to.have.lengthOf(0);
// test 2: If client, and no children are rendered, a template is.
expect($('#client').parent().children()).to.have.lengthOf(2, 'rendered the client component and a template');
expect($('#client').parent().find('template[data-astro-template]')).to.have.lengthOf(1, 'Found 1 template');
expect($('#client').parent().children()).to.have.lengthOf(
2,
'rendered the client component and a template'
);
expect($('#client').parent().find('template[data-astro-template]')).to.have.lengthOf(
1,
'Found 1 template'
);
// test 3: If client, and children are rendered, no template is.
expect($('#client-render').parent().children()).to.have.lengthOf(1);

View file

@ -14,7 +14,10 @@ describe('<Code>', () => {
let html = await fixture.readFile('/no-lang/index.html');
const $ = cheerio.load(html);
expect($('pre')).to.have.lengthOf(1);
expect($('pre').attr('style')).to.equal('background-color: #0d1117; overflow-x: auto;', 'applies default and overflow');
expect($('pre').attr('style')).to.equal(
'background-color: #0d1117; overflow-x: auto;',
'applies default and overflow'
);
expect($('pre > code')).to.have.lengthOf(1);
// test: contains some generated spans
@ -36,7 +39,10 @@ describe('<Code>', () => {
const $ = cheerio.load(html);
expect($('pre')).to.have.lengthOf(1);
expect($('pre').attr('class')).to.equal('astro-code');
expect($('pre').attr('style')).to.equal('background-color: #2e3440ff; overflow-x: auto;', 'applies custom theme');
expect($('pre').attr('style')).to.equal(
'background-color: #2e3440ff; overflow-x: auto;',
'applies custom theme'
);
});
it('<Code wrap>', async () => {
@ -45,7 +51,9 @@ describe('<Code>', () => {
const $ = cheerio.load(html);
expect($('pre')).to.have.lengthOf(1);
// test: applies wrap overflow
expect($('pre').attr('style')).to.equal('background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;');
expect($('pre').attr('style')).to.equal(
'background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;'
);
}
{
let html = await fixture.readFile('/wrap-false/index.html');

View file

@ -9,7 +9,14 @@ const EXPECTED_CSS = {
'/one/index.html': ['/assets/'],
'/two/index.html': ['/assets/'],
};
const UNEXPECTED_CSS = ['/src/components/nav.css', '../css/typography.css', '../css/colors.css', '../css/page-index.css', '../css/page-one.css', '../css/page-two.css'];
const UNEXPECTED_CSS = [
'/src/components/nav.css',
'../css/typography.css',
'../css/colors.css',
'../css/page-index.css',
'../css/page-one.css',
'../css/page-two.css',
];
describe('CSS Bundling', function () {
let fixture;

View file

@ -10,8 +10,15 @@ describe('Astro Markdown plugins', () => {
fixture = await loadFixture({
root: './fixtures/astro-markdown-plugins/',
markdown: {
remarkPlugins: ['remark-code-titles', ['rehype-autolink-headings', { behavior: 'prepend' }]],
rehypePlugins: [['rehype-toc', { headings: ['h2', 'h3'] }], [addClasses, { 'h1,h2,h3': 'title' }], 'rehype-slug'],
remarkPlugins: [
'remark-code-titles',
['rehype-autolink-headings', { behavior: 'prepend' }],
],
rehypePlugins: [
['rehype-toc', { headings: ['h2', 'h3'] }],
[addClasses, { 'h1,h2,h3': 'title' }],
'rehype-slug',
],
},
});
await fixture.build();

View file

@ -126,7 +126,8 @@ describe('Astro Markdown Shiki', () => {
describe('Wrap', () => {
describe('wrap = true', () => {
const style = 'background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;';
const style =
'background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;';
let fixture;
before(async () => {

View file

@ -15,13 +15,21 @@ describe('Pagination', () => {
});
it('optional root page', async () => {
for (const file of ['/posts/optional-root-page/index.html', '/posts/optional-root-page/2/index.html', '/posts/optional-root-page/3/index.html']) {
for (const file of [
'/posts/optional-root-page/index.html',
'/posts/optional-root-page/2/index.html',
'/posts/optional-root-page/3/index.html',
]) {
expect(await fixture.readFile(file)).to.be.ok;
}
});
it('named root page', async () => {
for (const file of ['/posts/named-root-page/1/index.html', '/posts/named-root-page/2/index.html', '/posts/named-root-page/3/index.html']) {
for (const file of [
'/posts/named-root-page/1/index.html',
'/posts/named-root-page/2/index.html',
'/posts/named-root-page/3/index.html',
]) {
expect(await fixture.readFile(file)).to.be.ok;
}
});

View file

@ -48,7 +48,10 @@ describe('astro cli', () => {
const networkLogFlags = [['--host'], ['--host', '0.0.0.0']];
networkLogFlags.forEach(([flag, flagValue]) => {
it(`astro ${cmd} ${flag} ${flagValue ?? ''} - network log`, async () => {
const { local, network } = await cliServerLogSetupWithFixture(flagValue ? [flag, flagValue] : [flag], cmd);
const { local, network } = await cliServerLogSetupWithFixture(
flagValue ? [flag, flagValue] : [flag],
cmd
);
expect(local).to.not.be.undefined;
expect(network).to.not.be.undefined;
@ -56,11 +59,23 @@ describe('astro cli', () => {
const localURL = new URL(local);
const networkURL = new URL(network);
expect(localURL.hostname).to.be.equal(flagValue ?? 'localhost', `Expected local URL to be on localhost`);
expect(localURL.hostname).to.be.equal(
flagValue ?? 'localhost',
`Expected local URL to be on localhost`
);
// Note: our tests run in parallel so this could be 3000+!
expect(Number.parseInt(localURL.port)).to.be.greaterThanOrEqual(3000, `Expected Port to be >= 3000`);
expect(networkURL.port).to.be.equal(localURL.port, `Expected local and network ports to be equal`);
expect(isIPv4(networkURL.hostname)).to.be.equal(true, `Expected network URL to respect --host flag`);
expect(Number.parseInt(localURL.port)).to.be.greaterThanOrEqual(
3000,
`Expected Port to be >= 3000`
);
expect(networkURL.port).to.be.equal(
localURL.port,
`Expected local and network ports to be equal`
);
expect(isIPv4(networkURL.hostname)).to.be.equal(
true,
`Expected network URL to respect --host flag`
);
});
});

View file

@ -40,7 +40,10 @@ describe('Config Validation', () => {
});
it('ignores falsey "integration" values', async () => {
const result = await validateConfig({ integrations: [0, false, null, undefined] }, process.cwd());
const result = await validateConfig(
{ integrations: [0, false, null, undefined] },
process.cwd()
);
expect(result.integrations).to.deep.equal([]);
});
it('normalizes "integration" values', async () => {
@ -48,7 +51,10 @@ describe('Config Validation', () => {
expect(result.integrations).to.deep.equal([{ name: '@astrojs/a', hooks: {} }]);
});
it('flattens array "integration" values', async () => {
const result = await validateConfig({ integrations: [{ name: '@astrojs/a' }, [{ name: '@astrojs/b' }, { name: '@astrojs/c' }]] }, process.cwd());
const result = await validateConfig(
{ integrations: [{ name: '@astrojs/a' }, [{ name: '@astrojs/b' }, { name: '@astrojs/c' }]] },
process.cwd()
);
expect(result.integrations).to.deep.equal([
{ name: '@astrojs/a', hooks: {} },
{ name: '@astrojs/b', hooks: {} },
@ -56,11 +62,17 @@ describe('Config Validation', () => {
]);
});
it('blocks third-party "integration" values', async () => {
const configError = await validateConfig({ integrations: [{ name: '@my-plugin/a' }] }, process.cwd()).catch((err) => err);
const configError = await validateConfig(
{ integrations: [{ name: '@my-plugin/a' }] },
process.cwd()
).catch((err) => err);
expect(configError).to.be.instanceOf(Error);
expect(configError.message).to.include('Astro integrations are still experimental.');
});
it('allows third-party "integration" values with the --experimental-integrations flag', async () => {
await validateConfig({ integrations: [{ name: '@my-plugin/a' }], experimental: { integrations: true } }, process.cwd()).catch((err) => err);
await validateConfig(
{ integrations: [{ name: '@my-plugin/a' }], experimental: { integrations: true } },
process.cwd()
).catch((err) => err);
});
});

View file

@ -31,10 +31,17 @@ describe('config', () => {
it('can be specified via --host flag', async () => {
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
const { network } = await cliServerLogSetup(['--root', fileURLToPath(projectRootURL), '--host']);
const { network } = await cliServerLogSetup([
'--root',
fileURLToPath(projectRootURL),
'--host',
]);
const networkURL = new URL(network);
expect(isIPv4(networkURL.hostname)).to.be.equal(true, `Expected network URL to respect --host flag`);
expect(isIPv4(networkURL.hostname)).to.be.equal(
true,
`Expected network URL to respect --host flag`
);
});
});
@ -42,10 +49,18 @@ describe('config', () => {
it('can be passed via --config', async () => {
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
const configFileURL = new URL('./fixtures/config-path/config/my-config.mjs', import.meta.url);
const { network } = await cliServerLogSetup(['--root', fileURLToPath(projectRootURL), '--config', configFileURL.pathname]);
const { network } = await cliServerLogSetup([
'--root',
fileURLToPath(projectRootURL),
'--config',
configFileURL.pathname,
]);
const networkURL = new URL(network);
expect(isIPv4(networkURL.hostname)).to.be.equal(true, `Expected network URL to respect --host flag`);
expect(isIPv4(networkURL.hostname)).to.be.equal(
true,
`Expected network URL to respect --host flag`
);
});
});

View file

@ -18,7 +18,10 @@ describe('Global Fetch', () => {
it('Is available in Astro components', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
expect($('#astro-component').text()).to.equal('function', 'Fetch supported in .astro components');
expect($('#astro-component').text()).to.equal(
'function',
'Fetch supported in .astro components'
);
});
it('Is available in non-Astro components', async () => {
const html = await fixture.readFile('/index.html');
@ -31,6 +34,9 @@ describe('Global Fetch', () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
expect($('#already-imported').text()).to.equal('function', 'Existing fetch imports respected');
expect($('#custom-declaration').text()).to.equal('number', 'Custom fetch declarations respected');
expect($('#custom-declaration').text()).to.equal(
'number',
'Custom fetch declarations respected'
);
});
});

View file

@ -36,7 +36,9 @@ describe('LitElement test', function () {
expect($('my-element').html()).to.include(`<div>Testing...</div>`);
// test 3: string reactive property set
expect(stripExpressionMarkers($('my-element').html())).to.include(`<div id="str">initialized</div>`);
expect(stripExpressionMarkers($('my-element').html())).to.include(
`<div id="str">initialized</div>`
);
// test 4: boolean reactive property correctly set
// <my-element bool="false"> Lit will equate to true because it uses
@ -45,7 +47,9 @@ describe('LitElement test', function () {
// test 5: object reactive property set
// by default objects will be stringifed to [object Object]
expect(stripExpressionMarkers($('my-element').html())).to.include(`<div id="data">data: 1</div>`);
expect(stripExpressionMarkers($('my-element').html())).to.include(
`<div id="data">data: 1</div>`
);
// test 6: reactive properties are not rendered as attributes
expect($('my-element').attr('obj')).to.equal(undefined);

View file

@ -38,7 +38,9 @@ describe('Tailwind', () => {
expect(bundledCSS, 'includes responsive classes').to.match(/\.lg\\:py-3{/);
// tailwind escapes brackets, `font-[900]` compiles to `font-\[900\]`
expect(bundledCSS, 'supports arbitrary value classes').to.match(/\.font-\\\[900\\\]{font-weight:900}/);
expect(bundledCSS, 'supports arbitrary value classes').to.match(
/\.font-\\\[900\\\]{font-weight:900}/
);
});
it('maintains classes in HTML', async () => {

View file

@ -55,7 +55,8 @@ polyfill(globalThis, {
* .clean() - Async. Removes the projects dist folder.
*/
export async function loadFixture(inlineConfig) {
if (!inlineConfig || !inlineConfig.root) throw new Error("Must provide { root: './fixtures/...' }");
if (!inlineConfig || !inlineConfig.root)
throw new Error("Must provide { root: './fixtures/...' }");
// load config
let cwd = inlineConfig.root;
@ -93,12 +94,14 @@ export async function loadFixture(inlineConfig) {
return devResult;
},
config,
fetch: (url, init) => fetch(`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`, init),
fetch: (url, init) =>
fetch(`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`, init),
preview: async (opts = {}) => {
const previewServer = await preview(config, { logging, ...opts });
return previewServer;
},
readFile: (filePath) => fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.outDir), 'utf8'),
readFile: (filePath) =>
fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.outDir), 'utf8'),
readdir: (fp) => fs.promises.readdir(new URL(fp.replace(/^\//, ''), config.outDir)),
clean: () => fs.promises.rm(config.outDir, { maxRetries: 10, recursive: true, force: true }),
loadTestAdapterApp: async () => {
@ -120,7 +123,11 @@ function merge(a, b) {
const c = {};
for (const k of allKeys) {
const needsObjectMerge =
typeof a[k] === 'object' && typeof b[k] === 'object' && (Object.keys(a[k]).length || Object.keys(b[k]).length) && !Array.isArray(a[k]) && !Array.isArray(b[k]);
typeof a[k] === 'object' &&
typeof b[k] === 'object' &&
(Object.keys(a[k]).length || Object.keys(b[k]).length) &&
!Array.isArray(a[k]) &&
!Array.isArray(b[k]);
if (needsObjectMerge) {
c[k] = merge(a[k] || {}, b[k] || {});
continue;

View file

@ -28,14 +28,20 @@ export function mkdirp(dir: string) {
}
}
const { version } = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
const { version } = JSON.parse(
fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8')
);
const POSTPROCESS_FILES = ['package.json', 'astro.config.mjs', 'CHANGELOG.md']; // some files need processing after copying.
export async function main() {
logger.debug('Verbose logging turned on');
console.log(`\n${bold('Welcome to Astro!')} ${gray(`(create-astro v${version})`)}`);
console.log(`If you encounter a problem, visit ${cyan('https://github.com/withastro/astro/issues')} to search or file a new issue.\n`);
console.log(
`If you encounter a problem, visit ${cyan(
'https://github.com/withastro/astro/issues'
)} to search or file a new issue.\n`
);
let spinner = ora({ color: 'green', text: 'Prepare for liftoff.' });
@ -74,7 +80,9 @@ export async function main() {
const hash = args.commit ? `#${args.commit}` : '';
const templateTarget = options.template.includes('/') ? options.template : `withastro/astro/examples/${options.template}#latest`;
const templateTarget = options.template.includes('/')
? options.template
: `withastro/astro/examples/${options.template}#latest`;
const emitter = degit(`${templateTarget}${hash}`, {
cache: false,
@ -118,13 +126,25 @@ export async function main() {
// Warning for issue #655
if (err.message === 'zlib: unexpected end of file') {
console.log(yellow("This seems to be a cache related problem. Remove the folder '~/.degit/github/withastro' to fix this error."));
console.log(yellow('For more information check out this issue: https://github.com/withastro/astro/issues/655'));
console.log(
yellow(
"This seems to be a cache related problem. Remove the folder '~/.degit/github/withastro' to fix this error."
)
);
console.log(
yellow(
'For more information check out this issue: https://github.com/withastro/astro/issues/655'
)
);
}
// Helpful message when encountering the "could not find commit hash for ..." error
if (err.code === 'MISSING_REF') {
console.log(yellow("This seems to be an issue with degit. Please check if you have 'git' installed on your system, and install it if you don't have (https://git-scm.com)."));
console.log(
yellow(
"This seems to be an issue with degit. Please check if you have 'git' installed on your system, and install it if you don't have (https://git-scm.com)."
)
);
console.log(
yellow(
"If you do have 'git' installed, please run this command with the --verbose flag and file a new issue with the command output here: https://github.com/withastro/astro/issues"
@ -178,8 +198,13 @@ export async function main() {
)
).flat(1);
// merge and sort dependencies
packageJSON.devDependencies = { ...(packageJSON.devDependencies ?? {}), ...Object.fromEntries(integrationEntries) };
packageJSON.devDependencies = Object.fromEntries(Object.entries(packageJSON.devDependencies).sort((a, b) => a[0].localeCompare(b[0])));
packageJSON.devDependencies = {
...(packageJSON.devDependencies ?? {}),
...Object.fromEntries(integrationEntries),
};
packageJSON.devDependencies = Object.fromEntries(
Object.entries(packageJSON.devDependencies).sort((a, b) => a[0].localeCompare(b[0]))
);
await fs.promises.writeFile(fileLoc, JSON.stringify(packageJSON, undefined, 2));
break;
}
@ -196,7 +221,9 @@ export async function main() {
const component = COUNTER_COMPONENTS[integration.id as keyof typeof COUNTER_COMPONENTS];
const componentName = path.basename(component.filename, path.extname(component.filename));
const absFileLoc = path.resolve(cwd, component.filename);
importStatements.push(`import ${componentName} from '${component.filename.replace(/^src/, '..')}';`);
importStatements.push(
`import ${componentName} from '${component.filename.replace(/^src/, '..')}';`
);
components.push(`<${componentName} client:visible />`);
await fs.promises.writeFile(absFileLoc, component.content);
})
@ -225,7 +252,11 @@ export async function main() {
}
console.log(` ${i++}: ${bold(cyan('npm install'))} (or pnpm install, yarn, etc)`);
console.log(` ${i++}: ${bold(cyan('git init && git add -A && git commit -m "Initial commit"'))} (optional step)`);
console.log(
` ${i++}: ${bold(
cyan('git init && git add -A && git commit -m "Initial commit"')
)} (optional step)`
);
console.log(` ${i++}: ${bold(cyan('npm run dev'))} (or pnpm, yarn, etc)`);
console.log(`\nTo close the dev server, hit ${bold(cyan('Ctrl-C'))}`);

View file

@ -96,7 +96,12 @@ export const levels: Record<LoggerLevel, number> = {
};
/** Full logging API */
export function log(opts: LogOptions = {}, level: LoggerLevel, type: string | null, ...args: Array<any>) {
export function log(
opts: LogOptions = {},
level: LoggerLevel,
type: string | null,
...args: Array<any>
) {
const logLevel = opts.level ?? defaultLogOptions.level;
const dest = opts.dest ?? defaultLogOptions.dest;
const event: LogMessage = {

Some files were not shown because too many files have changed in this diff Show more