Blog Support 1/3: Data fetching (#62)
* 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
|
@ -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',
|
||||
|
|
94
README.md
|
@ -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:
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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:
|
||||
|
@ -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
examples/blog/astro/components/AuthorCard.astro
Normal file
|
@ -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
examples/blog/astro/components/MainHead.astro
Normal file
|
@ -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
examples/blog/astro/components/Nav.astro
Normal file
|
@ -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
examples/blog/astro/components/Pagination.astro
Normal file
|
@ -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
examples/blog/astro/components/PostPreview.astro
Normal file
|
@ -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
examples/blog/astro/data/authors.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"animal": {
|
||||
"name": "Animal",
|
||||
"email": "animal@muppets.co",
|
||||
"img": "/images/animal.jpg"
|
||||
},
|
||||
"kermit": {
|
||||
"name": "Kermit the Frog",
|
||||
"email": "kermit@muppets.co",
|
||||
"img": "/images/kermit.jpg"
|
||||
},
|
||||
"ms-piggy": {
|
||||
"name": "Animal",
|
||||
"email": "mspiggy@muppets.co",
|
||||
"img": "/images/ms-piggy.jpg"
|
||||
},
|
||||
"gonzo": {
|
||||
"name": "Gonzo",
|
||||
"email": "thegonz@muppets.co",
|
||||
"img": "/images/gonzo.jpg"
|
||||
},
|
||||
"rizzo": {
|
||||
"name": "Rizzo the Rat",
|
||||
"email": "rizzo@muppets.co",
|
||||
"img": "/images/rizzo.jpg"
|
||||
}
|
||||
}
|
31
examples/blog/astro/layouts/post.astro
Normal file
|
@ -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>
|
43
examples/blog/astro/pages/index.astro
Normal file
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
import MainHead from '../components/MainHead.astro';
|
||||
import Nav from '../components/Nav.astro';
|
||||
import PostPreview from '../components/PostPreview.astro';
|
||||
import Pagination from '../components/Pagination.astro';
|
||||
|
||||
// posts
|
||||
import authorData from '../data/authors.json';
|
||||
|
||||
const postData = import.meta.collection('./post/*.md');
|
||||
|
||||
const PER_PAGE = 10;
|
||||
postData.sort((a, b) => new Date(b.date) - new Date(a.date)); // new -> old
|
||||
|
||||
const start = 0;
|
||||
const currentPage = 1;
|
||||
const maxPages = 1;
|
||||
const posts = postData.splice(start, PER_PAGE);
|
||||
|
||||
// page
|
||||
let title = 'Muppet Blog: Home';
|
||||
let description = 'An example blog on Astro';
|
||||
---
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>{title}</title>
|
||||
<MainHead title={title} description={description} />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Nav />
|
||||
|
||||
<main class="wrapper">
|
||||
<h1>Recent posts</h1>
|
||||
{posts.map((post) => <PostPreview post={post} author={authorData[post.author]} />)}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<Pagination currentPage={currentPage} maxPages={maxPages} />
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
11
examples/blog/astro/pages/post/muppet-babies.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
layout: ../../layouts/post.astro
|
||||
title: Muppet Babies
|
||||
tag: television
|
||||
date: 1984-09-15
|
||||
image: /images/muppet-babies.jpg
|
||||
author: ms-piggy
|
||||
description: Muppet Babies,is an American animated television series that aired from September 15, 1984, to November 2, 1991, on CBS.
|
||||
---
|
||||
|
||||
Jim Henson's Muppet Babies, commonly known by the shortened title Muppet Babies, is an American animated television series that aired from September 15, 1984, to November 2, 1991, on CBS. The show portrays childhood versions of the Muppets living together in a nursery under the care of a human woman identified only as Nanny (the whereabouts of their parents are never addressed), who appears in almost every episode, but her face is never visible; only the babies' view of her pink skirt, purple sweater, and distinctive green and white striped socks is shown. The idea of presenting the Muppets as children first appeared in a dream sequence in The Muppets Take Manhattan (1984), released two months before Muppet Babies debuted, in which Miss Piggy imagined what it would be like if she and Kermit the Frog had grown up together.
|
13
examples/blog/astro/pages/post/muppet-treasure-island.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
layout: ../../layouts/post.astro
|
||||
title: Muppet Treasure Island
|
||||
tag: movie
|
||||
date: 1996-02-16
|
||||
image: /images/muppet-treasure-island.png
|
||||
author: ms-piggy
|
||||
description: Muppet Treasure Island is a 1996 American musical adventure comedy film directed by Brian Henson.
|
||||
---
|
||||
|
||||
Muppet Treasure Island is a 1996 American musical adventure comedy film directed by Brian Henson. It is the fifth theatrical film in The Muppets franchise.
|
||||
|
||||
Adapted from the 1883 novel Treasure Island by Robert Louis Stevenson, similarly to its predecessor The Muppet Christmas Carol (1992), the key roles were played by live-action actors, with the Muppets in supporting roles. It stars Tim Curry as Long John Silver, and Kevin Bishop as Jim Hawkins alongside Muppet performers Dave Goelz, Steve Whitmire, Jerry Nelson, Kevin Clash, and Frank Oz portraying various roles.
|
11
examples/blog/astro/pages/post/muppets-from-space.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
layout: ../../layouts/post.astro
|
||||
title: Muppets from Space
|
||||
tag: movie
|
||||
date: 1999-07-14
|
||||
image: /images/muppets-from-space.jpg
|
||||
author: rizzo
|
||||
description: Muppets from Space is a 1999 American science fiction comedy film directed by Tim Hill (in his feature directorial debut) and written by Jerry Juhl, Joseph Mazzarino, and Ken Kaufman.
|
||||
---
|
||||
|
||||
Muppets from Space is a 1999 American science fiction comedy film directed by Tim Hill (in his feature directorial debut) and written by Jerry Juhl, Joseph Mazzarino, and Ken Kaufman. The sixth theatrical film in The Muppets franchise, it is the first Muppets film to not be a musical and the first film since the death of Muppets creator Jim Henson to have an original Muppets-focused plot. It stars Jeffrey Tambor, F. Murray Abraham, David Arquette, Pat Hingle, Rob Schneider, Andie MacDowell, Josh Charles, Hulk Hogan, and Ray Liotta, alongside Muppet performers Dave Goelz, Steve Whitmire, Bill Barretta, and Frank Oz. In the film, Gonzo attempts to discover his origins after having nightmares. After he and Rizzo the Rat are captured by government officials during his search, Kermit the Frog and the rest of the Muppet gang must save them.
|
11
examples/blog/astro/pages/post/muppets-most-wanted.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
layout: ../../layouts/post.astro
|
||||
title: Muppets Most Wanted
|
||||
tag: movie
|
||||
date: 2014-03-11
|
||||
image: /images/muppets-most-wanted.jpg
|
||||
author: animal
|
||||
description: Muppets Most Wanted is a 2014 American musical crime comedy film and the eighth theatrical film featuring the Muppets.
|
||||
---
|
||||
|
||||
Muppets Most Wanted is a 2014 American musical crime comedy film and the eighth theatrical film featuring the Muppets.[4] Directed by James Bobin and written by Bobin and Nicholas Stoller, the film is a direct sequel to The Muppets (2011) and stars Ricky Gervais, Ty Burrell and Tina Fey, as well as Muppets performers Steve Whitmire, Eric Jacobson, Dave Goelz, Bill Barretta, David Rudman, Matt Vogel and Peter Linz.[5] In the film, the Muppets become involved in an international crime caper perpetrated by Constantine, a criminal with a strong resemblance to Kermit, while on a world tour in Europe.[6] As of 2021, it is the most recent theatrical Muppets film.
|
11
examples/blog/astro/pages/post/the-muppet-christmas-carol.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
layout: ../../layouts/post.astro
|
||||
title: The Muppet Christmas Carol
|
||||
tag: movie
|
||||
date: 1992-12-11
|
||||
image: /images/the-muppet-christmas-carol.jpg
|
||||
author: kermit
|
||||
description: The Muppet Christmas Carol is a 1992 American Christmas musical fantasy comedy drama film directed by Brian Henson (in his feature directorial debut) from a screenplay by Jerry Juhl.
|
||||
---
|
||||
|
||||
The Muppet Christmas Carol is a 1992 American Christmas musical fantasy comedy drama film directed by Brian Henson (in his feature directorial debut) from a screenplay by Jerry Juhl. The fourth theatrical film in The Muppets franchise, it was the first film to be produced following the deaths of Muppets creator Jim Henson and performer Richard Hunt, with the film being dedicated to both of them. Adapted from the 1843 novella A Christmas Carol by Charles Dickens, it stars Michael Caine as Ebenezer Scrooge, alongside Muppet performers Dave Goelz, Steve Whitmire, Jerry Nelson, and Frank Oz portraying various roles. Although artistic license is taken to suit the aesthetic of the Muppets, The Muppet Christmas Carol otherwise follows Dickens's original story closely.[4]
|
11
examples/blog/astro/pages/post/the-muppet-show.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
layout: ../../layouts/post.astro
|
||||
title: The Muppet Show
|
||||
tag: television
|
||||
date: 1976-09-13
|
||||
image: /images/the-muppet-show.jpg
|
||||
author: kermit
|
||||
description: The Muppet Show is a comedy television series created by Jim Henson and featuring the Muppets.
|
||||
---
|
||||
|
||||
The Muppet Show is a comedy television series created by Jim Henson and featuring the Muppets. The series originated as two pilot episodes produced by Henson for ABC in 1974 and 1975, respectively. While neither episode was moved forward as a series and other networks in the United States rejected Henson's proposals, British producer Lew Grade expressed interest in the project and agreed to co-produce The Muppet Show for ATV. Five seasons, totalling 120 episodes, were broadcast on ATV and other ITV franchises in the United Kingdom and in first-run syndication through CBS in the US from 1976 to 1981. The programme was taped at Elstree Studios, England.
|
11
examples/blog/astro/pages/post/the-muppets.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
layout: ../../layouts/post.astro
|
||||
title: The Muppets
|
||||
tag: movie
|
||||
date: 2011-11-04
|
||||
image: /images/the-muppets.jpg
|
||||
author: gonzo
|
||||
description: The Muppets is a 2011 American musical comedy film directed by James Bobin, written by Jason Segel and Nicholas Stoller, and the seventh theatrical film featuring the Muppets.
|
||||
---
|
||||
|
||||
The Muppets is a 2011 American musical comedy film directed by James Bobin, written by Jason Segel and Nicholas Stoller, and the seventh theatrical film featuring the Muppets.[6] The film stars Segel, Amy Adams, Chris Cooper, and Rashida Jones, as well as Muppets performers Steve Whitmire, Eric Jacobson, Dave Goelz, Bill Barretta, David Rudman, Matt Vogel, and Peter Linz. Bret McKenzie served as music supervisor, writing four of the film's five original songs, while Christophe Beck composed the film's score.[7] In the film, devoted Muppet fan Walter, his human brother Gary and Gary's girlfriend Mary help Kermit the Frog reunite the disbanded Muppets, as they must raise $10 million to save the Muppet Theater from Tex Richman, a businessman who plans to demolish the studio to drill for oil.
|
1773
examples/blog/package-lock.json
generated
Normal file
9
examples/blog/package.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"scripts": {
|
||||
"build": "astro build",
|
||||
"start": "astro dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "file:../../"
|
||||
}
|
||||
}
|
11
examples/blog/public/global.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
max-width: 1400px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
}
|
BIN
examples/blog/public/images/animal.jpg
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
examples/blog/public/images/gonzo.jpg
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
examples/blog/public/images/kermit.jpg
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
examples/blog/public/images/ms-piggy.jpg
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
examples/blog/public/images/muppet-babies.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
examples/blog/public/images/muppet-treasure-island.png
Normal file
After Width: | Height: | Size: 228 KiB |
BIN
examples/blog/public/images/muppets-from-space.jpg
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
examples/blog/public/images/muppets-most-wanted.jpg
Normal file
After Width: | Height: | Size: 179 KiB |
BIN
examples/blog/public/images/rizzo.jpg
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
examples/blog/public/images/the-muppet-christmas-carol.jpg
Normal file
After Width: | Height: | Size: 145 KiB |
BIN
examples/blog/public/images/the-muppet-show.jpg
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
examples/blog/public/images/the-muppets.jpg
Normal file
After Width: | Height: | Size: 31 KiB |
19
package-lock.json
generated
|
@ -1964,6 +1964,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"globalyzer": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
|
||||
"integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q=="
|
||||
},
|
||||
"globby": {
|
||||
"version": "11.0.2",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz",
|
||||
|
@ -1978,6 +1983,11 @@
|
|||
"slash": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"globrex": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
|
||||
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="
|
||||
},
|
||||
"good-listener": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
|
||||
|
@ -3763,6 +3773,15 @@
|
|||
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
|
||||
"optional": true
|
||||
},
|
||||
"tiny-glob": {
|
||||
"version": "0.2.8",
|
||||
"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.8.tgz",
|
||||
"integrity": "sha512-vkQP7qOslq63XRX9kMswlby99kyO5OvKptw7AMwBVMjXEI7Tb61eoI5DydyEMOseyGS5anDN1VPoVxEvH01q8w==",
|
||||
"requires": {
|
||||
"globalyzer": "0.1.0",
|
||||
"globrex": "^0.1.2"
|
||||
}
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
"sass": "^1.32.8",
|
||||
"snowpack": "^3.2.2",
|
||||
"svelte": "^3.35.0",
|
||||
"tiny-glob": "^0.2.8",
|
||||
"vue": "^3.0.11",
|
||||
"yargs-parser": "^20.2.7"
|
||||
},
|
||||
|
|
|
@ -5,6 +5,7 @@ import type { JsxItem, TransformResult } from '../@types/astro';
|
|||
|
||||
import eslexer from 'es-module-lexer';
|
||||
import esbuild from 'esbuild';
|
||||
import glob from 'tiny-glob/sync.js';
|
||||
import path from 'path';
|
||||
import { walk } from 'estree-walker';
|
||||
import babelParser from '@babel/parser';
|
||||
|
@ -35,6 +36,15 @@ function internalImport(internalPath: string) {
|
|||
return `/_astro_internal/${internalPath}`;
|
||||
}
|
||||
|
||||
/** Is this an import.meta.* built-in? You can pass an optional 2nd param to see if the name matches as well. */
|
||||
function isImportMetaDeclaration(declaration: VariableDeclarator, metaName?: string): boolean {
|
||||
const { init } = declaration;
|
||||
if (!init || init.type !== 'CallExpression' || init.callee.type !== 'MemberExpression' || init.callee.object.type !== 'MetaProperty') return false;
|
||||
// optional: if metaName specified, match that
|
||||
if (metaName && (init.callee.property.type !== 'Identifier' || init.callee.property.name !== metaName)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Retrieve attributes from TemplateNode */
|
||||
function getAttributes(attrs: Attribute[]): Record<string, string> {
|
||||
let result: Record<string, string> = {};
|
||||
|
@ -272,6 +282,10 @@ interface CodegenState {
|
|||
dynamicImports: DynamicImportMap;
|
||||
}
|
||||
|
||||
// cache filesystem pings
|
||||
const miniGlobCache = new Map<string, Map<string, string[]>>();
|
||||
|
||||
/** Compile/prepare Astro frontmatter scripts */
|
||||
function compileModule(module: Script, state: CodegenState, compileOptions: CompileOptions) {
|
||||
const { extensions = defaultExtensions } = compileOptions;
|
||||
|
||||
|
@ -279,8 +293,11 @@ function compileModule(module: Script, state: CodegenState, compileOptions: Comp
|
|||
const componentProps: VariableDeclarator[] = [];
|
||||
const componentExports: ExportNamedDeclaration[] = [];
|
||||
|
||||
const collectionImports = new Map<string, string>();
|
||||
|
||||
let script = '';
|
||||
let propsStatement = '';
|
||||
let dataStatement = '';
|
||||
const componentPlugins = new Set<ValidExtensionPlugins>();
|
||||
|
||||
if (module) {
|
||||
|
@ -293,12 +310,17 @@ function compileModule(module: Script, state: CodegenState, compileOptions: Comp
|
|||
let i = body.length;
|
||||
while (--i >= 0) {
|
||||
const node = body[i];
|
||||
if (node.type === 'ImportDeclaration') {
|
||||
componentImports.push(node);
|
||||
body.splice(i, 1);
|
||||
}
|
||||
if (/^Export/.test(node.type)) {
|
||||
if (node.type === 'ExportNamedDeclaration' && node.declaration?.type === 'VariableDeclaration') {
|
||||
switch (node.type) {
|
||||
case 'ImportDeclaration': {
|
||||
componentImports.push(node);
|
||||
body.splice(i, 1); // remove node
|
||||
break;
|
||||
}
|
||||
case 'ExportNamedDeclaration': {
|
||||
if (node.declaration?.type !== 'VariableDeclaration') {
|
||||
// const replacement = extract_exports(node);
|
||||
break;
|
||||
}
|
||||
const declaration = node.declaration.declarations[0];
|
||||
if ((declaration.id as Identifier).name === '__layout' || (declaration.id as Identifier).name === '__content') {
|
||||
componentExports.push(node);
|
||||
|
@ -306,8 +328,31 @@ function compileModule(module: Script, state: CodegenState, compileOptions: Comp
|
|||
componentProps.push(declaration);
|
||||
}
|
||||
body.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
case 'VariableDeclaration': {
|
||||
for (const declaration of node.declarations) {
|
||||
// only select import.meta.collection() calls here. this utility filters those out for us.
|
||||
if (!isImportMetaDeclaration(declaration, 'collection')) continue;
|
||||
if (declaration.id.type !== 'Identifier') continue;
|
||||
const { id, init } = declaration;
|
||||
if (!id || !init || init.type !== 'CallExpression') continue;
|
||||
|
||||
// gather data
|
||||
const namespace = id.name;
|
||||
|
||||
// TODO: support more types (currently we can; it’s just a matter of parsing out the expression)
|
||||
if ((init as any).arguments[0].type !== 'StringLiteral') {
|
||||
throw new Error(`[import.meta.collection] Only string literals allowed, ex: \`import.meta.collection('./post/*.md')\`\n ${state.filename}`);
|
||||
}
|
||||
const spec = (init as any).arguments[0].value;
|
||||
if (typeof spec === 'string') collectionImports.set(namespace, spec);
|
||||
|
||||
// remove node
|
||||
body.splice(i, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// const replacement = extract_exports(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,14 +384,65 @@ function compileModule(module: Script, state: CodegenState, compileOptions: Comp
|
|||
}
|
||||
propsStatement += `,`;
|
||||
}
|
||||
propsStatement += `} = props;`;
|
||||
propsStatement += `} = props;\n`;
|
||||
}
|
||||
script = propsStatement + babelGenerator(program).code;
|
||||
|
||||
// handle importing data
|
||||
for (const [namespace, spec] of collectionImports.entries()) {
|
||||
// only allow for .md files
|
||||
if (!spec.endsWith('.md')) {
|
||||
throw new Error(`Only *.md pages are supported for import.meta.collection(). Attempted to load "${spec}"`);
|
||||
}
|
||||
|
||||
// locate files
|
||||
try {
|
||||
let found: string[];
|
||||
|
||||
// use cache
|
||||
let cachedLookups = miniGlobCache.get(state.filename);
|
||||
if (!cachedLookups) {
|
||||
cachedLookups = new Map();
|
||||
miniGlobCache.set(state.filename, cachedLookups);
|
||||
}
|
||||
if (cachedLookups.get(spec)) {
|
||||
found = cachedLookups.get(spec) as string[];
|
||||
} else {
|
||||
found = glob(spec, { cwd: path.dirname(state.filename), filesOnly: true });
|
||||
cachedLookups.set(spec, found);
|
||||
miniGlobCache.set(state.filename, cachedLookups);
|
||||
}
|
||||
|
||||
// throw error, purge cache if no results found
|
||||
if (!found.length) {
|
||||
cachedLookups.delete(spec);
|
||||
miniGlobCache.set(state.filename, cachedLookups);
|
||||
throw new Error(`No files matched "${spec}" from ${state.filename}`);
|
||||
}
|
||||
|
||||
const data = found.map((importPath) => {
|
||||
if (importPath.startsWith('http') || importPath.startsWith('.')) return importPath;
|
||||
return `./` + importPath;
|
||||
});
|
||||
|
||||
// add static imports (probably not the best, but async imports don‘t work just yet)
|
||||
data.forEach((importPath, j) => {
|
||||
state.importExportStatements.add(`const ${namespace}_${j} = import('${importPath}').then((m) => ({ ...m.__content, url: '${importPath.replace(/\.md$/, '')}' }));`);
|
||||
});
|
||||
|
||||
// expose imported data to Astro script
|
||||
dataStatement += `const ${namespace} = await Promise.all([${found.map((_, j) => `${namespace}_${j}`).join(',')}]);\n`;
|
||||
} catch (err) {
|
||||
throw new Error(`No files matched "${spec}" from ${state.filename}`);
|
||||
}
|
||||
}
|
||||
|
||||
script = propsStatement + dataStatement + babelGenerator(program).code;
|
||||
}
|
||||
|
||||
return { script, componentPlugins };
|
||||
}
|
||||
|
||||
/** Compile styles */
|
||||
function compileCss(style: Style, state: CodegenState) {
|
||||
walk(style, {
|
||||
enter(node: TemplateNode) {
|
||||
|
@ -363,6 +459,7 @@ function compileCss(style: Style, state: CodegenState) {
|
|||
});
|
||||
}
|
||||
|
||||
/** Compile page markup */
|
||||
function compileHtml(enterNode: TemplateNode, state: CodegenState, compileOptions: CompileOptions) {
|
||||
const { components, css, importExportStatements, dynamicImports, filename } = state;
|
||||
const { astroConfig } = compileOptions;
|
||||
|
|
|
@ -35,9 +35,10 @@ function configDefaults(userConfig?: any): any {
|
|||
function normalizeConfig(userConfig: any, root: string): AstroConfig {
|
||||
const config: any = { ...(userConfig || {}) };
|
||||
|
||||
config.projectRoot = new URL(config.projectRoot + '/', root);
|
||||
config.astroRoot = new URL(config.astroRoot + '/', root);
|
||||
config.public = new URL(config.public + '/', root);
|
||||
const fileProtocolRoot = `file://${root}/`;
|
||||
config.projectRoot = new URL(config.projectRoot + '/', fileProtocolRoot);
|
||||
config.astroRoot = new URL(config.astroRoot + '/', fileProtocolRoot);
|
||||
config.public = new URL(config.public + '/', fileProtocolRoot);
|
||||
|
||||
return config as AstroConfig;
|
||||
}
|
||||
|
@ -48,13 +49,11 @@ export async function loadConfig(rawRoot: string | undefined): Promise<AstroConf
|
|||
rawRoot = process.cwd();
|
||||
}
|
||||
|
||||
let config: any;
|
||||
|
||||
const root = pathResolve(rawRoot);
|
||||
const fileProtocolRoot = `file://${root}/`;
|
||||
const astroConfigPath = pathJoin(root, 'astro.config.mjs');
|
||||
|
||||
// load
|
||||
let config: any;
|
||||
if (existsSync(astroConfigPath)) {
|
||||
config = configDefaults((await import(astroConfigPath)).default);
|
||||
} else {
|
||||
|
@ -65,7 +64,7 @@ export async function loadConfig(rawRoot: string | undefined): Promise<AstroConf
|
|||
validateConfig(config);
|
||||
|
||||
// normalize
|
||||
config = normalizeConfig(config, fileProtocolRoot);
|
||||
config = normalizeConfig(config, root);
|
||||
|
||||
return config as AstroConfig;
|
||||
}
|
||||
|
|