Skip to content

Commit

Permalink
Blog Support 1/3: Data fetching (#62)
Browse files Browse the repository at this point in the history
* Add example blog

* Add author data

* Improve navigation

* Style nav

* Add friendly error message

* Throw error if import glob used for non-Markdown files

* Use import.meta.collection() API instead

* README fixes
  • Loading branch information
drwpow committed Apr 6, 2021
1 parent 3adb9ea commit 2b346d7
Show file tree
Hide file tree
Showing 36 changed files with 2,359 additions and 34 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = {
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-var-requires': 'off',
'no-console': 'warn',
'no-shadow': 'error',
'prefer-const': 'off',
'prefer-rest-params': 'off',
Expand Down
94 changes: 76 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ A next-generation static-site generator with partial hydration. Use your favorit
npm install astro
```

TODO: astro boilerplate

## 🧞 Development

Add a `dev` npm script to your `/package.json` file:
Expand Down Expand Up @@ -54,19 +52,20 @@ export default {

By default, Astro outputs zero client-side JS. If you'd like to include an interactive component in the client output, you may use any of the following techniques.

- `MyComponent:load` will render `MyComponent` on page load
- `MyComponent:idle` will use `requestIdleCallback` to render `MyComponent` as soon as main thread is free
- `MyComponent:visible` will use an `IntersectionObserver` to render `MyComponent` when the element enters the viewport
- `<MyComponent />` will render an HTML-only version of `MyComponent` (default)
- `<MyComponent:load />` will render `MyComponent` on page load
- `<MyComponent:idle />` will use [requestIdleCallback()][request-idle-cb] to render `MyComponent` as soon as main thread is free
- `<MyComponent:visible />` will use an [IntersectionObserver][intersection-observer] to render `MyComponent` when the element enters the viewport

### 💅 Styling

If you‘ve used [Svelte][svelte]’s styles before, Astro works almost the same way. In any `.astro` file, start writing styles in a `<style>` tag like so:

```astro
```html
<style>
.scoped {
font-weight: bold;
}
.scoped {
font-weight: bold;
}
</style>

<div class="scoped">I’m a scoped style</div>
Expand All @@ -76,13 +75,13 @@ If you‘ve used [Svelte][svelte]’s styles before, Astro works almost the same

Astro also supports [Sass][sass] out-of-the-box; no configuration needed:

```astro
```html
<style lang="scss">
@use "../tokens" as *;
@use "../tokens" as *;
.title {
color: $color.gray;
}
.title {
color: $color.gray;
}
</style>

<h1 class="title">Title</h1>
Expand Down Expand Up @@ -117,14 +116,71 @@ _Note: a Tailwind config file is currently required to enable Tailwind in Astro,

Then write Tailwind in your project just like you‘re used to:

```astro
```html
<style>
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>
```

#### 🍱 Collections (beta)

Astro’s Collections API is useful for grabbing collections of content. Currently only `*.md` files are supported.

##### 🔽 Markdown

```jsx
// pages/blog.astro
---
import PostPreview from '../components/PostPreview.astro';

const blogPosts = import.meta.collections('./post/*.md');
---

<main>
<h1>Blog Posts</h1>
{blogPosts.map((post) => (
<PostPreview post={post} />
)}
</main>
```
This will load all markdown files located in `/pages/post/*.md`, compile them into an array, then expose them to the page.
If you were to inspect the array, you‘d find the following schema:
```js
const blogPosts = [
{
content: string, // Markdown converted to HTML
// all other frontmatter data
},
// …
];
```
##### 🧑‍🍳 Advanced usage
All of the following options are supported under the 2nd parameter of `import.meta.collections()`:
```js
const collection = import.meta.collections('./post/*.md', {
/** If `page` is omitted, all results are returned */
page: 1, // ⚠️ starts at 1, not 0
/** How many items should be returned per-page (ignored if `page` is missing; default: 25) */
perPage: 25,
/** How items should be sorted (default: no sort) */
sort(a, b) {
return new Date(b.date) - new Date(a.date); // sort newest first, by `date` in frontmatter
}
/** Should items be filtered by their frontmatter data? */
filter(post) {
return post.tag === 'movie'; // (optional) only return posts tagged "movie"
}
});
```
## 🚀 Build & Deployment
Add a `build` npm script to your `/package.json` file:
Expand All @@ -148,6 +204,8 @@ Now upload the contents of `/_site_` to your favorite static site host.
[autoprefixer]: https://github.com/postcss/autoprefixer
[browserslist]: https://github.com/browserslist/browserslist
[intersection-observer]: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
[request-idle-cb]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
[sass]: https://sass-lang.com/
[svelte]: https://svelte.dev
[tailwind]: https://tailwindcss.com
31 changes: 31 additions & 0 deletions examples/blog/astro/components/AuthorCard.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
export let author;
---

<style lang="scss">
.card {
display: flex;
align-items: center;
}

.avatar {
width: 1.5rem;
height: 1.5rem;
margin-right: 0.5rem;
object-fit: cover;
border-radius: 50%;
overflow: hidden;

img {
width: 100%;
height: 100%;
}
}
</style>

<div class="card">
<div class="avatar">
<img class="avatar" src={author.img} alt={author.name} />
</div>
{author.name}
</div>
32 changes: 32 additions & 0 deletions examples/blog/astro/components/MainHead.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
// props
export let title: string;
export let description: string;
export let image: string | undefined;
export let type: string | undefined;
// internal data
const OG_TYPES = {
'movie': 'video.movie',
'television': 'video.tv_show'
}
---

<!-- Common -->
<meta charset="UTF-8" />
<title>{title}</title>
<meta name="description" content={description} />
<link rel="stylesheet" href="/global.css" />

<!-- OpenGraph -->
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
{image && (<meta property="og:image" content={image} />)}
{OG_TYPES[type] && (<meta property="og:type" content={OG_TYPES[type]} />)}

<!-- Twitter -->
<meta name="twitter:card" content={image ? 'summary_large_image' : 'summary'} />
<meta name="twitter:site" content="@astro" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
{image && (<meta name="twitter:image" content={image} />)}
44 changes: 44 additions & 0 deletions examples/blog/astro/components/Nav.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<style lang="scss">
.header {
display: flex;
align-items: center;
padding: 2rem;
}

.title {
margin: 0;
font-size: 1em;
margin-right: 2rem;
}

.nav {
display: flex;
}

ul {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}

li {
margin: 0;
}

a {
display: block;
margin-left: 1rem;
margin-right: 1rem;
}
</style>

<nav class="header">
<h1 class="title">Muppet Blog</h1>
<ul class="nav">
<li><a href="/">All Posts</a></li>
<li><a href="/?tag=movies">Movies</a></li>
<li><a href="/?tag=television">Television</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
12 changes: 12 additions & 0 deletions examples/blog/astro/components/Pagination.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
export let currentPage: number;
export let maxPages: number;
---

<nav>
<a href="">Prev</a>
<a href="?p=1">1</a>
<a href="?p=2">2</a>
<a href="?p=3">3</a>
<a href="?p=2">Next</a>
</nav>
58 changes: 58 additions & 0 deletions examples/blog/astro/components/PostPreview.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
export let post;
export let author;
import AuthorCard from './AuthorCard.astro';
function formatDate(date) {
return new Date(date).toUTCString();
}
---

<style lang="scss">
.post {
display: grid;
grid-template-columns: 8rem auto;
grid-gap: 1.5rem;
margin-top: 1.5rem;
margin-bottom: 1.5rem;
}

.thumb {
width: 8rem;
height: 8rem;
object-fit: cover;
border-radius: 0.25rem;
overflow: hidden;

img {
width: 100%;
height: 100%;
}
}

h1 {
font-weight: 700;
font-size: 1em;
margin-bottom: 0;
}

time {
display: block;
margin-top: 0.5em;
margin-bottom: 0.5em;
}
</style>

<article class="post">
<div class="thumb">
<img src={post.image} alt={post.title} />
</div>
<div class="data">
<h1>{post.title}</h1>
<AuthorCard author={author} />
<time>{formatDate(post.date)}</time>
<p>{post.description}</p>
<a href={post.url}>Read</a>
</div>
</article>
27 changes: 27 additions & 0 deletions examples/blog/astro/data/authors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"animal": {
"name": "Animal",
"email": "[email protected]",
"img": "/images/animal.jpg"
},
"kermit": {
"name": "Kermit the Frog",
"email": "[email protected]",
"img": "/images/kermit.jpg"
},
"ms-piggy": {
"name": "Animal",
"email": "[email protected]",
"img": "/images/ms-piggy.jpg"
},
"gonzo": {
"name": "Gonzo",
"email": "[email protected]",
"img": "/images/gonzo.jpg"
},
"rizzo": {
"name": "Rizzo the Rat",
"email": "[email protected]",
"img": "/images/rizzo.jpg"
}
}
31 changes: 31 additions & 0 deletions examples/blog/astro/layouts/post.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
import AuthorCard from '../components/AuthorCard.astro';
import MainHead from '../components/MainHead.astro';
import Nav from '../components/Nav.astro';
export let content;
import authorData from '../data/authors.json';
---

<html>
<head>
<title>{content.title}</title>
<MainHead title={content.title} description={content.description} image={content.image} />
</head>

<body>
<Nav />

<main class="wrapper">
<h1>{content.title}</h1>
<AuthorCard author={authorData[content.author]} />
<article>
<slot />
</article>
</main>

<footer>
</footer>
</body>
</html>
Loading

0 comments on commit 2b346d7

Please sign in to comment.