diff options
Diffstat (limited to 'docs/core-concepts')
-rw-r--r-- | docs/core-concepts/astro-components.md | 208 | ||||
-rw-r--r-- | docs/core-concepts/astro-pages.md | 72 | ||||
-rw-r--r-- | docs/core-concepts/collections.md | 213 | ||||
-rw-r--r-- | docs/core-concepts/component-hydration.md | 37 | ||||
-rw-r--r-- | docs/core-concepts/layouts.md | 155 | ||||
-rw-r--r-- | docs/core-concepts/project-structure.md | 56 | ||||
-rw-r--r-- | docs/core-concepts/ui-renderers.md | 202 |
7 files changed, 943 insertions, 0 deletions
diff --git a/docs/core-concepts/astro-components.md b/docs/core-concepts/astro-components.md new file mode 100644 index 000000000..e6d80f976 --- /dev/null +++ b/docs/core-concepts/astro-components.md @@ -0,0 +1,208 @@ +--- +layout: ~/layouts/Main.astro +title: Astro Components +--- + + + +## ✨ `.astro` Syntax + +Astro comes with its own server-side, component-based templating language. Think of it as HTML enhanced with the full power of JavaScript. + +Learning a new syntax can be intimidating, but the `.astro` format has been carefully designed with familiarity in mind. It borrows heavily from patterns you likely already know—components, Frontmatter, and JSX-like expressions. We're confident that this guide will help you feel comfortable writing `.astro` files in no time. + +--- + +### The `.astro` format + +If you're already familiar with **HTML or JavaScript**, you'll likely feel comfortable with `.astro` files right away. + +Think of `.astro` as **component-oriented HTML**. Components are reusable, self-contained blocks of HTML and CSS that belong together. + +```html +<!-- This is a valid Astro component --> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Document</title> + </head> + <body> + <main> + <h1>Hello world!</h1> + </main> + </body> +</html> +``` + +```html +<!-- This is also a valid Astro component! --> +<main> + <h1>Hello world!</h1> +</main> +``` + +Developers have come up with a myriad of different techniques for composing blocks of HTML over the years, but far and away the most successful has been [JSX](https://reactjs.org/docs/introducing-jsx.html). + +We love JSX! In fact, `.astro` files borrow the highly-expressive expression syntax directly from JSX. + +```jsx +<!-- This is an Astro component with expressions! --> +<main> + <h1>Hello {name}!</h1> + <ul> + {items.map((item) => ( + <li>{item}</li> + ))} + </ul> + <h2 data-hint={`Use JS template strings when you need to mix-in ${"variables"}.`}>So good!</h2> +</main> +``` + +`.astro` files also borrow the concept of [Frontmatter](https://jekyllrb.com/docs/front-matter/) from Markdown. Instead of introducing a new HTML-oriented `import` and `export` syntax, `.astro` just uses JavaScript. + +```jsx +--- +// This area is TypeScript (and therefore JavaScript)! +import MyComponent from './MyComponent.astro' +--- + +<html lang="en"> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Document</title> + </head> + <body> + <MyComponent></MyComponent> + </body> +</html> +``` + +### Data and Props + +`.astro` components can define local variables inside of the Frontmatter script. These are automatically exposed to the content below. + +```jsx +--- +let name = 'world'; +--- + +<main> + <h1>Hello {name}!</h1> +</main> +``` + +`.astro` components can also accept props when they are rendered. Public props are exposed on the `Astro.props` global. + +```jsx +--- +const { greeting = 'Hello', name } = Astro.props; +--- + +<main> + <h1>{greeting} {name}!</h1> +</main> +``` + +To define the props which your component accepts, you may export a TypeScript interface or type named `Props`. + +```tsx +--- +export interface Props { + name: string; + greeting?: string; +} + +const { greeting = 'Hello', name } = Astro.props; +--- + +<main> + <h1>{greeting} {name}!</h1> +</main> +``` + +### Fragments + +At the top-level of an `.astro` file, you may render any number of elements. + +```html +<!-- Look, no Fragment! --> +<div id="a" /> +<div id="b" /> +<div id="c" /> +``` + +Inside of an expression, you must wrap multiple elements in a Fragment. Fragments must open with `<>` and close with `</>`. + +```jsx +<div> + {[0, 1, 2].map((id) => ( + <> + <div id={`a-${id}`} /> + <div id={`b-${id}`} /> + <div id={`c-${id}`} /> + </> + ))} +</div> +``` + +### `.astro` versus `.jsx` + +`.astro` files can end up looking very similar to `.jsx` files, but there are a few key differences. Here's a comparison between the two formats. + +| Feature | Astro | JSX | +| ---------------------------- | ------------------------------------------ | -------------------------------------------------- | +| File extension | `.astro` | `.jsx` or `.tsx` | +| User-Defined Components | `<Capitalized>` | `<Capitalized>` | +| Expression Syntax | `{}` | `{}` | +| Spread Attributes | `{...props}` | `{...props}` | +| Boolean Attributes | `autocomplete` === `autocomplete={true}` | `autocomplete` === `autocomplete={true}` | +| Inline Functions | `{items.map(item => <li>{item}</li>)}` | `{items.map(item => <li>{item}</li>)}` | +| IDE Support | WIP - [VS Code][code-ext] | Phenomenal | +| Requires JS import | No | Yes, `jsxPragma` (`React` or `h`) must be in scope | +| Fragments | Automatic top-level, `<>` inside functions | Wrap with `<Fragment>` or `<>` | +| Multiple frameworks per-file | Yes | No | +| Modifying `<head>` | Just use `<head>` | Per-framework (`<Head>`, `<svelte:head>`, etc) | +| Comment Style | `<!-- HTML -->` | `{/* JavaScript */}` | +| Special Characters | ` ` | `{'\xa0'}` or `{String.fromCharCode(160)}` | +| Attributes | `dash-case` | `camelCase` | + +### URL resolution + +It’s important to note that Astro **won’t** transform HTML references for you. For example, consider an `<img>` tag with a relative `src` attribute inside `src/pages/about.astro`: + +```html +<!-- ❌ Incorrect: will try and load `/about/thumbnail.png` --> +<img src="./thumbnail.png" /> +``` + +Since `src/pages/about.astro` will build to `/about/index.html`, you may not have expected that image to live at `/about/thumbnail.png`. So to fix this, choose either of two options: + +#### Option 1: Absolute URLs + +```html +<!-- ✅ Correct: references public/thumbnail.png --> +<img src="/thumbnail.png" /> +``` + +The recommended approach is to place files within `public/*`. This references a file it `public/thumbnail.png`, which will resolve to `/thumbnail.png` at the final build (since `public/` ends up at `/`). + +#### Option 2: Asset import references + +```jsx +--- +// ✅ Correct: references src/thumbnail.png +import thumbnailSrc from './thumbnail.png'; +--- + +<img src={thumbnailSrc} /> +``` + +If you’d prefer to organize assets alongside Astro components, you may import the file in JavaScript inside the component script. This works as intended but this makes `thumbnail.png` harder to reference in other parts of your app, as its final URL isn’t easily-predictable (unlike assets in `public/*`, where the final URL is guaranteed to never change). + +### TODO: Composition (Slots) + +[code-ext]: https://marketplace.visualstudio.com/items?itemName=astro-build.astro-vscode + + diff --git a/docs/core-concepts/astro-pages.md b/docs/core-concepts/astro-pages.md new file mode 100644 index 000000000..283cd8867 --- /dev/null +++ b/docs/core-concepts/astro-pages.md @@ -0,0 +1,72 @@ +--- +layout: ~/layouts/Main.astro +title: Astro Pages +--- + +**Pages** are a special type of [Astro Component](./astro-components) that handle routing, data loading, and templating for each page of your website. You can think of them like any other Astro component, just with extra responsibilities. + +Astro also supports Markdown for content-heavy pages, like blog posts and documentation. See [Markdown Content](./markdown-content.md) for more information on writing pages with Markdown. + +## File-based Routing + +Astro uses Pages to do something called **file-based routing.** Every file in your `src/pages` directory becomes a page on your site, using the file name to decide the final route. + +Astro Components (`.astro`) and Markdown Files (`.md`) are the only supported formats for pages. Other page types (like a `.jsx` React component) are not supported, but you can use anything as a UI component inside of an `.astro` page to achieve a similar result. + +### Examples + +``` +src/pages/index.astro -> mysite.com/ +src/pages/about.astro -> mysite.com/about +src/pages/about/index.astro -> mysite.com/about +src/pages/about/me.astro -> mysite.com/about/me +src/pages/posts/1.md -> mysite.com/posts/1 +``` + +## Data Loading + +Astro pages can fetch data to help generate your pages. Astro provides two different tools to pages to help you do this: **fetch()** and **top-level await.** + +```astro +--- +// Example: Astro component scripts run at build time +const response = await fetch('http://example.com/movies.json'); +const data = await response.json(); +console.log(data); +--- +<!-- Output the result to the page --> +<div>{JSON.stringify(data)}</div> +``` +### `fetch()` + +Astro pages have access to the global `fetch()` function in their setup script. `fetch()` is a native JavaScript API ([MDN](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)) that lets you make HTTP requests for things like APIs and resources. + +Even though Astro component scripts run inside of Node.js (and not in the browser) Astro provides this native API so that you can fetch data at page build time. + +### Top-level await + +`await` is another native JavaScript feature that lets you await the response of some asynchronous promise ([MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await)). Astro supports `await` in the top-level of your component script. + +## Page Templating + +All Astro components are responsible for returning HTML. Astro Pages return HTML as well, but have the unique responsibility of returning a full `<html>...</html>` page response, including `<head>` ([MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)) and `<body>` ([MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body)). + +`<!doctype html>` is optional, and will be added automatically. + +```astro +--- +// Example: HTML page skeleton +--- +<!doctype html> +<html> + <head> + <title>Document title</title> + </head> + <body> + <h1>Hello, world!</h1> + </body> +</html> +``` + + + diff --git a/docs/core-concepts/collections.md b/docs/core-concepts/collections.md new file mode 100644 index 000000000..3bdf5c098 --- /dev/null +++ b/docs/core-concepts/collections.md @@ -0,0 +1,213 @@ +--- +layout: ~/layouts/Main.astro +title: Collections +--- + +**Collections** are a special type of [Page](./astro-pages) that help you generate multiple pages from a larger set of data. Example use-cases include: + +- Pagination: `/posts/1`, `/posts/2`, etc. +- Grouping content by author: `/author/fred`, `/author/matthew`, etc. +- Grouping content by some tag: `/tags/red`, `/tags/blue`, etc. +- Working with remote data +- Mixing remote and local data + +**Use a Collection when you need to generate multiple pages from a single template.** If you just want to generate a single page (ex: a long list of every post on your site) then you can just fetch that data on a normal Astro page without using the Collection API. + +## Using Collections + +To create a new Astro Collection, you must do three things: + +1. Create a new file in the `src/pages` directory that starts with the `$` symbol. This is required to enable the Collections API. + +- Example: `src/pages/$posts.astro` -> `/posts/1`, `/posts/2`, etc. +- Example: `src/pages/$tags.astro` -> `/tags/:tag` (or `/tags/:tag/1`) + +2. Define and export the `collection` prop: `collection.data` is how you'll access the data for every page in the collection. Astro populates this prop for you automatically. It MUST be named `collection` and it must be exported. + +- Example: `const { collection } = Astro.props;` + +3. Define and export `createCollection` function: this tells Astro how to load and structure your collection data. Check out the examples below for documentation on how it should be implemented. It MUST be named `createCollection` and it must be exported. + +- Example: `export async function createCollection() { /* ... */ }` +- API Reference: [createCollection][collection-api] + +## Example: Simple Pagination + +```jsx +--- +// Define the `collection` prop. +const { collection } = Astro.props; + +// Define a `createCollection` function. +export async function createCollection() { + const allPosts = Astro.fetchContent('../posts/*.md'); // fetch local posts. + allPosts.sort((a, b) => a.title.localeCompare(b.title)); // sort by title. + return { + // Because you are not doing anything more than simple pagination, + // its fine to just return the full set of posts for the collection data. + async data() { return allPosts; }, + // number of posts loaded per page (default: 25) + pageSize: 10, + }; +} +--- +<html lang="en"> + <head> + <title>Pagination Example: Page Number {collection.page.current}</title> + </head> + <body> + {collection.data.map((post) => ( + <h1>{post.title}</h1> + <time>{formatDate(post.published_at)}</time> + <a href={post.url}>Read Post</a> + ))} + </body> +</html> +``` + +## Example: Pagination Metadata + +```jsx +--- +// In addition to `collection.data` usage illustrated above, the `collection` +// prop also provides some important metadata for you to use, like: `collection.page`, +// `collection.url`, `collection.start`, `collection.end`, and `collection.total`. +// In this example, we'll use these values to do pagination in the template. +const { collection } = Astro.props; +export async function createCollection() { /* See Previous Example */ } +--- +<html lang="en"> + <head> + <title>Pagination Example: Page Number {collection.page.current}</title> + <link rel="canonical" href={collection.url.current} /> + <link rel="prev" href={collection.url.prev} /> + <link rel="next" href={collection.url.next} /> + </head> + <body> + <main> + <h5>Results {collection.start + 1}–{collection.end + 1} of {collection.total}</h5> + {collection.data.map((post) => ( + <h1>{post.title}</h1> + <time>{formatDate(post.published_at)}</time> + <a href={post.url}>Read Post</a> + ))} + </main> + <footer> + <h4>Page {collection.page.current} / {collection.page.last}</h4> + <nav class="nav"> + <a class="prev" href={collection.url.prev || '#'}>Prev</a> + <a class="next" href={collection.url.next || '#'}>Next</a> + </nav> + </footer> + </body> +</html> +``` + +## Example: Grouping Content by Tag, Author, etc. + +```jsx +--- +// Define the `collection` prop. +const { collection } = Astro.props; + +// Define a `createCollection` function. +// In this example, we'll customize the URLs that we generate to +// create a new page to group every pokemon by first letter of their name. +export async function createCollection() { + const allPokemonResponse = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=150`); + const allPokemonResult = await allPokemonResponse.json(); + const allPokemon = allPokemonResult.results; + const allLetters = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']; + return { + // `routes` defines the total collection of routes as `params` data objects. + // In this example, we format each letter (ex: "a") to params (ex: {letter: "a"}). + routes: allLetters.map(letter => { + const params = {letter}; + return params; + }), + // `permalink` defines the final URL for each route object defined in `routes`. + // It should always match the file location (ex: `src/pages/$pokemon.astro`). + permalink: ({ params }) => `/pokemon/${params.letter}`, + // `data` is now responsible for return the data for each page. + // Luckily we had already loaded all of the data at the top of the function, + // so we just filter the data here to group pages by first letter. + // If you needed to fetch more data for each page, you can do that here as well. + async data({ params }) { + return allPokemon.filter((pokemon) => pokemon.name[0] === params.letter); + }, + // Finally, `pageSize` and `pagination` is still on by default. Because + // we don't want to paginate the already-grouped pages a second time, we'll + // disable pagination. + pageSize: Infinity, + }; +} +--- +<html lang="en"> + <head> + <title>Pokemon: {collection.params.letter}</head> + <body> + {collection.data.map((pokemon) => (<h1>{pokemon.name}</h1>))} + </body> +</html> +``` + +## Example: Individual Pages from a Collection + +```jsx +--- +// Define the `collection` prop. +const { collection } = Astro.props; + +// Define a `createCollection` function. +// In this example, we'll create a new page for every single pokemon. +export async function createCollection() { + const allPokemonResponse = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=150`); + const allPokemonResult = await allPokemonResponse.json(); + const allPokemon = allPokemonResult.results; + return { + // `routes` defines the total collection of routes as data objects. + routes: allPokemon.map((pokemon, i) => { + const params = {name: pokemon.name, index: i}; + return params; + }), + // `permalink` defines the final URL for each route object defined in `routes`. + permalink: ({ params }) => `/pokemon/${params.name}`, + // `data` is now responsible for return the data for each page. + // Luckily we had already loaded all of the data at the top of the function, + // so we just filter the data here to group pages by first letter. + // If you needed to fetch more data for each page, you can do that here as well. + // Note: data() is expected to return an array! + async data({ params }) { + return [allPokemon[params.index]]; + }, + // Note: The default pageSize is fine because technically only one data object + // is ever returned per route. We set it to Infinity in this example for completeness. + pageSize: Infinity, + }; +} +--- +<html lang="en"> + <head> + <title>Pokemon: {collection.params.name}</head> + <body> + Who's that pokemon? It's {collection.data[0].name}! + </body> +</html> +``` + +## Tips + +- If you find yourself duplicating markup across many pages and collections, you should probably be using more reusable components. + +### 📚 Further Reading + +- [Fetching data in Astro][docs-data] +- API Reference: [collection][collection-api] +- API Reference: [createCollection()][create-collection-api] +- API Reference: [Creating an RSS feed][create-collection-api] + +[docs-data]: ../README.md#-fetching-data +[collection-api]: ./api.md#collection +[create-collection-api]: ./api.md#createcollection +[example-blog]: ../examples/blog +[fetch-content]: ./api.md#fetchcontent diff --git a/docs/core-concepts/component-hydration.md b/docs/core-concepts/component-hydration.md new file mode 100644 index 000000000..b1dc6c8ad --- /dev/null +++ b/docs/core-concepts/component-hydration.md @@ -0,0 +1,37 @@ +--- +layout: ~/layouts/Main.astro +title: React, Svelte, Vue, etc. +--- + +By default, Astro generates your site with zero client-side JavaScript. If you use any frontend UI components (React, Svelte, Vue, etc.) Astro will automatically render them to HTML and strip away any client-side JavaScript. This keeps your site default-fast. + +``` +--- +import MyReactComponent from '../components/MyReactComponent.jsx'; +--- +<!-- By default: Astro renders this to HTML + and strips away all JavaScript. --> +<MyReactComponent /> +``` + +However, there are plenty of cases where you might like to include an interactive component on your page: + +- An image carousel +- An auto-complete search bar +- A mobile sidebar open/close button +- A "Buy Now" button + +With Astro, you can hydrate these components individually, without forcing the rest of the page to ship any other unnecesary JavaScript. This technique is called **partial hydration.** +## Hydrate Frontend Components + +To hydrate your components in the client, you may use any of the following techniques: + +- `<MyComponent:load />` will render the component on page load. +- `<MyComponent:idle />` will use [requestIdleCallback()][mdn-ric] to render the component as soon as main thread is free. +- `<MyComponent:visible />` will use an [IntersectionObserver][mdn-io] to render the component when the element enters the viewport. + +## Hydrate Astro Components + +Astro components (`.astro`) are HTML-only templating languages with no client-side runtime. You cannot hydrate an Astro component to run on the client (because the JavaScript front-matter only ever runs at build time). + +If you want to make your Astro component interactive on the client, you should convert it to React, Svelte, or Vue. Otherwise, you can consider adding a `<script>` tag to your Astro component that will run JavaScript on the page. diff --git a/docs/core-concepts/layouts.md b/docs/core-concepts/layouts.md new file mode 100644 index 000000000..3c24373b5 --- /dev/null +++ b/docs/core-concepts/layouts.md @@ -0,0 +1,155 @@ +--- +layout: ~/layouts/Main.astro +title: Layouts +--- + +**Layouts** are a special type of [Component](./astro-components) that help you share and reuse common page layouts within your project. + +Layouts are just like any other reusable Astro component. There's no new syntax or APIs to learn. However, reusable page layouts are such a common pattern in web development that we created this guide to help you use them. + +## Usage + +Astro layouts support props, slots, and all of the other features of Astro components. Layouts are just normal components, after all! + +Unlike other components, layouts will often contain the full page `<html>`, `<head>` and `<body>` (often referred to as the **page shell**). + +It's a common pattern to put all of your layout components in a single `src/layouts` directory. + +## Example + +```astro +--- +// src/layouts/BaseLayout.astro +const {title} = Astro.props; +--- +<html> + <head> + <title>Example Layout: {title}</title> + </head> + <body> + <!-- Adds a navigation bar to every page. --> + <nav> + <a href="#">Home</a> + <a href="#">Posts</a> + <a href="#">Contact</a> + </nav> + <!-- slot: your page content will be injected here. --> + <slot /> + </body> +</html> +``` + +📚 The `<slot />` element lets Astro components define where any children elements (passed to the layout) should go. Learn more about how `<slot/>` works in our [Astro Component guide.](/docs/core-concepts/astro-components.md) + +Once you have your first layout, You can use it like you would any other component on your page. Remember that your layout contains your page `<html>`, `<head>`, and `<body>`. You only need to provide the custom page content. + +```astro +--- +// src/pages/index.astro +import BaseLayout from '../layouts/BaseLayout.astro' +--- +<BaseLayout title="Homepage"> + <h1>Hello, world!</h1> + <p>This is my page content. It will be nested inside a layout.</p> +</BaseLayout> +``` + + +## Nesting Layouts + +You can nest layouts when you want to create more specific page types without copy-pasting. It is common in Astro to have one generic `BaseLayout` and then many more specific layouts (`PostLayout`, `ProductLayout`, etc.) that reuse and build on top of it. + + +```astro +--- +// src/layouts/PostLayout.astro +import BaseLayout from '../layouts/BaseLayout.astro' +const {title, author} = Astro.props; +--- + <!-- This layout reuses BaseLayout (see example above): --> +<BaseLayout title={title}> + <!-- Adds new post-specific content to every page. --> + <div>Post author: {author}</div> + <!-- slot: your page content will be injected here. --> + <slot /> +</BaseLayout> +``` + +## Composing Layouts + +Sometimes, you need more granular control over your page. For instance, you may want to add SEO or social `meta` tags on some pages, but not others. You could implement this with a prop on your layout (`<BaseLayout addMeta={true} ...`) but at some point it may be easier to compose your layouts without nesting. + +Instead of defining your entire `<html>` page as one big layout, you can define the `head` and `body` contents as smaller, separate components. This lets you compose multiple layouts together in unique ways on every page. + +```astro +--- +// src/layouts/BaseHead.astro +const {title, description} = Astro.props; +--- +<meta charset="UTF-8"> +<title>{title}</title> +<meta name="description" content={description}> +<link rel="preconnect" href="https://fonts.gstatic.com"> +<link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet"> +``` + +Notice how this layout doesn't include your page shell, and only includes some generic elements that should go in your `<head>`. This lets you combine multiple layout components together, to include things + +```astro +--- +// src/pages/index.astro +import BaseHead from '../layouts/BaseHead.astro'; +import OpenGraphMeta from '../layouts/OpenGraphMeta.astro'; +--- +<html> + <head> + <!-- Now, you have complete control over the head, per-page. --> + <BaseHead title="Page Title" description="Page Description" /> + <OpenGraphMeta /> + <!-- You can even add custom, one-off elements as needed. --> + <link rel="alternate" type="application/rss+xml" href="/feed/posts.xml"> + </head> + <body> + <!-- ... --> + </body> +</html> +``` + +The one downside to this approach is that you'll need to define the `<html>`, `<head>`, and `<body>` elements on every page yourself. This is needed to construct the page because the layout components no longer contain the full page shell. + +## Markdown Layouts + +Layouts are essential for Markdown files. Markdown files can declare a layout in the file frontmatter. Each Markdown file will be rendered to HTML and then injected into the layout's `<slot />` location. + +````markdown +--- +title: Blog Post +layout: ../layouts/PostLayout.astro +--- +This blog post will be **rendered** inside of the `<PostLayout />` layout. +```` + +Markdown pages always pass a `content` prop to their layout, which is useful to grab information about the page, title, metadata, table of contents headers, and more. + +``` +--- +// src/layouts/PostLayout.astro +const { content } = Astro.props; +--- +<html> + <head> + <title>{content.title}</title> + </head> + <body> + <h1>{content.title}</h1> + <h2>{content.description}</h2> + <img src={content.image} alt=""> + <article> + <!-- slot: Markdown content goes here! --> + <slot /> + </article> + </body> +</html> +``` + +📚 Learn more about Astro's markdown support in our [Markdown guide](/docs/guides/markdown-content.md). diff --git a/docs/core-concepts/project-structure.md b/docs/core-concepts/project-structure.md new file mode 100644 index 000000000..28adca041 --- /dev/null +++ b/docs/core-concepts/project-structure.md @@ -0,0 +1,56 @@ +--- +layout: ~/layouts/Main.astro +title: Project Structure +--- + +Astro includes an opinionated folder layout for your project. Every Astro project must include these directories and files: + +- `src/*` - Your project source code (components, pages, etc.) +- `public/*` - Your non-code assets (fonts, icons, etc.) +- `package.json` - A project manifest. + +The easiest way to set up your new project is with `npm init astro`. Check out our [Installation Guide](/docs/quick-start.md) for a walkthrough of how to set up your project automatically (with `npm init astro`) or manually. +## Project Structure + +``` +├── src/ +│ ├── components/ +│ ├── layouts/ +│ └── pages/ +│ └── index.astro +├── public/ +└── package.json +``` + +### `src/` + +The src folder is where most of your project source code lives. This includes: + +- [Astro Components](/docs/core-concepts/astro-components.md) +- [Pages](/docs/core-concepts/astro-pages.md) +- [Markdown](/docs/core-concepts/astro-pages.md) +- [Layouts](/docs/core-concepts/astro-pages.md) +- [Frontend JS Components](/docs/core-concepts/component-hydration.md) +- [Styling (CSS, Sass)](/docs/guides/styling.md) + +Astro has complete control over how these files get processed, optimized, and bundled in your final site build. Some files (like Astro components) never make it to the browser directly and are instead rendered to HTML. Other files (like CSS) are sent to the browser but may be bundled with other CSS files depending on how your site uses. + +### `src/components` + +[Components](/docs/core-concepts/astro-components.md) are reusable units of UI for your HTML pages. It is recommended (but not required) that you put your components in this directory. How you organize them within this directory is up to you. + +Your non-Astro UI components (React, Preact, Svelte, Vue, etc.) can also live in the `src/components` directory. Astro will automatically render all components to HTML unless you've enabled a frontend component via partial hydration. + +### `src/layouts` + +[Layouts](/docs/core-concepts/layouts.md) are reusable components for HTML page layouts. It is recommended (but not required) that you put your layout components in this directory. How you organize them within this directory is up to you. + +### `src/pages` + +[Pages](/docs/core-concepts/astro-pages.md) contain all pages (`.astro` and `.md` supported) for your website. It is **required** that you put your pages in this directory. + +### `public/` + +For most users, the majority of your files will live inside of the `src/` directory so that Astro can properly handle and optimize them in your final build. By contrast, the `public/` directory is the place for any files to live outside of the Astro build process. + +If you put a file into the public folder, it will not be processed by Astro. Instead it will be copied into the build folder untouched. This can be useful for assets like images and fonts, or when you need to include a specific file like `robots.txt` or `manifest.webmanifest`.
\ No newline at end of file diff --git a/docs/core-concepts/ui-renderers.md b/docs/core-concepts/ui-renderers.md new file mode 100644 index 000000000..a95b0dbdf --- /dev/null +++ b/docs/core-concepts/ui-renderers.md @@ -0,0 +1,202 @@ +--- +layout: ~/layouts/Main.astro +title: UI Renderers +--- + +Astro is designed to support your favorite UI frameworks. [React](https://npm.im/@astrojs/renderer-react), [Svelte](https://npm.im/@astrojs/renderer-svelte), [Vue](https://npm.im/@astrojs/renderer-vue), and [Preact](https://npm.im/@astrojs/renderer-preact) are all built-in to Astro and supported out of the box. No configuration is needed to enable these. + +Internally, each framework is supported via a framework **renderer.** A renderer is a type of Astro plugin that adds support for a framework. Some are built-in, but you can also provide your own third-party renderers to add Astro support for new frameworks. + +## What is a renderer? + +A renderer is an NPM package that has two responsiblities: + +1. _render a component to a static string of HTML_ at build time +2. _rehydrate that HTML to create an interactive component_ on the client. + +Take a look at any one of Astro's built-in [`renderers`](https://github.com/snowpackjs/astro/tree/main/packages/renderers) to see this in action. We'll go into more detail in the following sections. + +## Add a renderer to Astro + +Astro enables a few popular framework renderers by default. If you want to add a new renderer to your project, you first need to set the built-in renderers that you care about. + +```js +// astro.config.js +export default { + renderers: [ + // Add the framework renderers that you want to enable for your project. + // If you set an empty array here, no UI frameworks will work. + // '@astrojs/renderer-svelte', + // '@astrojs/renderer-vue', + // '@astrojs/renderer-react', + // '@astrojs/renderer-preact', + ], +}; +``` + +To add a new custom renderer, install the npm package dependency in your project and then update the `renderers` array to include it: + +```js +// astro.config.js +export default { + renderers: ['my-custom-renderer', '@astrojs/renderer-svelte', '@astrojs/renderer-vue', '@astrojs/renderer-react', '@astrojs/renderer-preact'], +}; +``` + +#### Managing Framework Versions + +In Astro, the renderer plugin defines which version of your framework to use with Astro. This should be set to as wide of a range as possible, but often will be pinned to a specific major version: + +- `@astrojs/renderer-vue`: `"vue": "^3.0.0"` +- `@astrojs/renderer-react`: `"react": "^17.0.0"` +- See all: https://github.com/snowpackjs/astro/tree/main/packages/renderers + +This is required because the renderer itself also uses these packages and requires a specific API to work. For example, If the user updated from Vue 2 to Vue 3 (or vice versa) then the renderer itself would break since the `vue` package would have changed. + +**What if I want to use a beta framework (ex: react@next)?** Check to see if the renderer has a `@next` version that you could manually install and use. If one doesn't exist, feel free to request it: https://github.com/snowpackjs/astro/issues/new/choose + +**What if I need to override the framework version in my project?** You can use the "resolutions" feature of many npm package managers to override or pin the framework version for your entire project. Just be sure to select a version that is compatible with your renderer: + +- **yarn:** https://classic.yarnpkg.com/en/docs/selective-version-resolutions/ +- **pnpm:** https://pnpm.io/package_json#pnpmoverrides +- **npm:** see https://stackoverflow.com/questions/15806152/how-do-i-override-nested-npm-dependency-versions + +## Building Your Own Renderer + +> **Building a renderer?** We'd love for you to contribute renderers for popular frameworks back to the Astro repo. Feel free to open an issue or pull request to discuss. + +A simple renderer only needs a few files: + +``` +/my-custom-renderer/ +├── package.json +├── index.js +├── server.js +└── client.js +``` + +### Package Manifest (`package.json`) + +A renderer should include any framework dependencies as package dependencies. For example, `@astrojs/renderer-react` includes `react` & `react-dom` as dependencies in the `package.json` manifest. + +```js +// package.json +"name": "@astrojs/renderer-react", +"dependencies": { + "react": "^17.0.0", + "react-dom": "^17.0.0" +} +``` + +This means that Astro users don't need to install the UI framework packages themselves. The renderer is the only package that your users will need to install. + +### Renderer Entrypoint (`index.js`) + +The main entrypoint of a renderer is a simple JS file which exports a manifest for the renderer. The required values are `name`, `server`, and `client`. + +Additionally, this entrypoint can define a [Snowpack plugin](https://www.snowpack.dev/guides/plugins) that should be used to load non-JavaScript files. + +```js +export default { + name: '@astrojs/renderer-xxx', // the renderer name + client: './client.js', // relative path to the client entrypoint + server: './server.js', // relative path to the server entrypoint + snowpackPlugin: '@snowpack/plugin-xxx', // optional, the name of a snowpack plugin to inject + snowpackPluginOptions: { example: true }, // optional, any options to be forwarded to the snowpack plugin + knownEntrypoint: ['framework'], // optional, entrypoint modules that will be used by compiled source +}; +``` + +### Server Entrypoint (`server.js`) + +The server entrypoint of a renderer is responsible for checking if a component should use this renderer, and if so, how that component should be rendered to a string of static HTML. + +```js +export default { + // should Component use this renderer? + check(Component, props, childHTML) {}, + // Component => string of static HTML + renderToStaticMarkup(Component, props, childHTML) {}, +}; +``` + +#### `check` + +`check` is a function that determines whether a Component should be "claimed" by this renderer. + +In it's simplest form, it can check for the existence of a flag on Object-based components. + +```js +function check(Component) { + return Component.isMyFrameworkComponent; +} +``` + +In more complex scenarios, like when a Component is a `Function` without any flags, you may need to use `try/catch` to attempt a full render. This result is cached so that it only runs once per-component. + +```js +function check(Component, props, childHTML) { + try { + const { html } = renderToStaticMarkup(Component, props, childHTML); + return Boolean(html); + } catch (e) {} + return false; +} +``` + +#### `renderToStaticMarkup` + +`renderToStaticMarkup` is a function that renders a Component to a static string of HTML. There's usually a method exported by frameworks named something like `renderToString`. + +```js +import { renderToString } from 'xxx'; + +function renderToStaticMarkup(Component, props, childHTML) { + const html = renderToString(h(Component, { ...props, innerHTML: childHTML })); + return { html }; +} +``` + +Note that `childHTML` is an HTML string representing this component's children. If your framework does not support rendering HTML directly, you are welcome to use a wrapper component. By convention, Astro uses the `astro-fragment` custom element to inject `childHTML` into. Your renderer should use that, too. + +```js +import { h, renderToString } from 'xxx'; + +const Wrapper = ({ value }) => h('astro-fragment', { dangerouslySetInnerHTML: { __html: value } }); + +function renderToStaticMarkup(Component, props, childHTML) { + const html = renderToString(h(Component, props, h(Wrapper, { value: childHTML }))); + return { html }; +} +``` + +### Client Entrypoint (`client.js`) + +The client entrypoint of a renderer is responsible for rehydrating static HTML (the result of `renderToStaticMarkup`) back into a fully interactive component. Its `default` export should be a `function` which accepts the host element of the Component, an `astro-root` custom element. + +> If your framework supports non-destructive component hydration (as opposed to a destructive `render` method), be sure to use that! Following your framework's Server Side Rendering (SSR) guide should point you in the right direction. + +```js +import { hydrate } from 'xxx'; + +export default (element) => { + return (Component, props, childHTML) => { + hydrate(h(Component, { ...props, innerHTML: childHTML }), element); + }; +}; +``` + +Note that `childHTML` is an HTML string representing this component's children. If your framework does not support rendering HTML directly, you should use the same wrapper component you used for the server entrypoint. + +```js +import { h, hydrate } from 'xxx'; +import SharedWrapper from './SharedWrapper.js'; + +export default (element) => { + return (Component, props, childHTML) => { + hydrate(h(Component, props, h(SharedWrapper, { value: childHTML })), element); + }; +}; +``` + +[astro-config]: ./config.md |