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
This commit is contained in:
Drew Powers 2021-04-06 15:54:55 -06:00 committed by GitHub
parent 3adb9ea87c
commit 2b346d7a4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 2359 additions and 34 deletions

View file

@ -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',

View file

@ -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 youve 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">Im a scoped style</div>
@ -76,13 +75,13 @@ If youve 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 youre used to:
```astro
```html
<style>
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>
```
#### 🍱 Collections (beta)
Astros 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, youd 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

View 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>

View 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} />)}

View 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>

View 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>

View 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>

View 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"
}
}

View 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>

View 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>

View 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.

View 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.

View 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.

View 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.

View 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]

View 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.

View 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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,9 @@
{
"scripts": {
"build": "astro build",
"start": "astro dev"
},
"dependencies": {
"astro": "file:../../"
}
}

View 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

19
package-lock.json generated
View file

@ -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",

View file

@ -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"
},

View file

@ -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; its 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 dont 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;

View file

@ -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;
}