update prettier width (#2968)
This commit is contained in:
parent
d63213f119
commit
1335797903
146 changed files with 2134 additions and 483 deletions
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"printWidth": 180,
|
||||
"printWidth": 100,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
|
|
7
.vscode/extensions.json
vendored
7
.vscode/extensions.json
vendored
|
@ -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": []
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
@ -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>,
|
||||
];
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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]}`);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'] });
|
||||
|
|
|
@ -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']) {
|
||||
|
|
|
@ -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]);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
9
packages/astro/src/core/build/types.d.ts
vendored
9
packages/astro/src/core/build/types.d.ts
vendored
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
// @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;
|
||||
// @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;
|
||||
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.
|
||||
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.
|
||||
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 ? '' : '.');
|
||||
}
|
||||
|
|
|
@ -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/
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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('-')) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)}`;
|
||||
}
|
||||
|
||||
|
|
|
@ -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".'
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 won’t get caught below
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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}
|
||||
});
|
||||
`,
|
||||
|
|
|
@ -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, '&').replace(/"/g, '"') : value);
|
||||
const toAttributeString = (value: any, shouldEscape = true) =>
|
||||
shouldEscape ? String(value).replace(/&/g, '&').replace(/"/g, '"') : 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) {
|
||||
|
|
|
@ -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 ||
|
||||
`
|
||||
|
|
|
@ -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>` : ''}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,16 +67,19 @@ 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) => {
|
||||
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.
|
||||
if (lang === '.css') {
|
||||
return createImportPlaceholder(spec);
|
||||
} else {
|
||||
return match;
|
||||
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.
|
||||
if (lang === '.css') {
|
||||
return createImportPlaceholder(spec);
|
||||
} 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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 can’t 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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>');
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -55,7 +55,8 @@ polyfill(globalThis, {
|
|||
* .clean() - Async. Removes the project’s 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;
|
||||
|
|
|
@ -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'))}`);
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue