Allow createCollection() to fetch remote data (#400)
* Allow createCollection() to fetch remote data Fixes #378 * Update docs * revert isomorphic-fetch, see if ci passes Co-authored-by: Fred K. Schott <fkschott@gmail.com>
This commit is contained in:
parent
2d7abd3ab4
commit
c374a549b5
6 changed files with 62 additions and 11 deletions
5
.changeset/five-ducks-knock.md
Normal file
5
.changeset/five-ducks-knock.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Bugfix: createCollection() API can be used without fetchContent()
|
|
@ -91,7 +91,7 @@ When using the [Collections API][docs-collections], `createCollection()` is an a
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| :---------- | :---------------------------: | :--------------------------------------------------------------------------------------------------------- |
|
| :---------- | :---------------------------: | :--------------------------------------------------------------------------------------------------------- |
|
||||||
| `data` | `async ({ params }) => any[]` | **Required.** Load data with this function to be returned. |
|
| `data` | `async ({ params }) => any[]` | **Required.** Load an array of data with this function to be returned. |
|
||||||
| `pageSize` | `number` | Specify number of items per page (default: `25`). |
|
| `pageSize` | `number` | Specify number of items per page (default: `25`). |
|
||||||
| `routes` | `params[]` | **Required for URL Params.** Return an array of all possible URL `param` values in `{ name: value }` form. |
|
| `routes` | `params[]` | **Required for URL Params.** Return an array of all possible URL `param` values in `{ name: value }` form. |
|
||||||
| `permalink` | `({ params }) => string` | **Required for URL Params.** Given a `param` object of `{ name: value }`, generate the final URL.\* |
|
| `permalink` | `({ params }) => string` | **Required for URL Params.** Given a `param` object of `{ name: value }`, generate the final URL.\* |
|
||||||
|
|
|
@ -249,7 +249,7 @@ function compileModule(module: Script, state: CodegenState, compileOptions: Comp
|
||||||
} else if (node.declaration.type === 'FunctionDeclaration') {
|
} else if (node.declaration.type === 'FunctionDeclaration') {
|
||||||
// case 2: createCollection (export async function)
|
// case 2: createCollection (export async function)
|
||||||
if (!node.declaration.id || node.declaration.id.name !== 'createCollection') break;
|
if (!node.declaration.id || node.declaration.id.name !== 'createCollection') break;
|
||||||
createCollection = module.content.substring(node.declaration.start || 0, node.declaration.end || 0);
|
createCollection = module.content.substring(node.start || 0, node.end || 0);
|
||||||
|
|
||||||
// remove node
|
// remove node
|
||||||
body.splice(i, 1);
|
body.splice(i, 1);
|
||||||
|
@ -365,8 +365,7 @@ function compileModule(module: Script, state: CodegenState, compileOptions: Comp
|
||||||
imports += importStatement + '\n';
|
imports += importStatement + '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
createCollection =
|
createCollection = imports + createCollection.substring(0, declaration.start || 0) + globResult.code + createCollection.substring(declaration.end || 0);
|
||||||
imports + '\nexport ' + createCollection.substring(0, declaration.start || 0) + globResult.code + createCollection.substring(declaration.end || 0);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,16 @@ import { existsSync, promises as fs } from 'fs';
|
||||||
import { fileURLToPath, pathToFileURL } from 'url';
|
import { fileURLToPath, pathToFileURL } from 'url';
|
||||||
import { posix as path } from 'path';
|
import { posix as path } from 'path';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
import { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, SnowpackConfig, NotFoundError } from 'snowpack';
|
import {
|
||||||
|
loadConfiguration,
|
||||||
|
logger as snowpackLogger,
|
||||||
|
NotFoundError,
|
||||||
|
SnowpackDevServer,
|
||||||
|
ServerRuntime as SnowpackServerRuntime,
|
||||||
|
SnowpackConfig,
|
||||||
|
startServer as startSnowpackServer,
|
||||||
|
} from 'snowpack';
|
||||||
import { CompileError } from '@astrojs/parser';
|
import { CompileError } from '@astrojs/parser';
|
||||||
import { loadConfiguration, logger as snowpackLogger, startServer as startSnowpackServer } from 'snowpack';
|
|
||||||
import { canonicalURL, getSrcPath, stopTimer } from './build/util.js';
|
import { canonicalURL, getSrcPath, stopTimer } from './build/util.js';
|
||||||
import { debug, info } from './logger.js';
|
import { debug, info } from './logger.js';
|
||||||
import { configureSnowpackLogger } from './snowpack-logger.js';
|
import { configureSnowpackLogger } from './snowpack-logger.js';
|
||||||
|
@ -94,20 +101,22 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro
|
||||||
|
|
||||||
if (mod.exports.createCollection) {
|
if (mod.exports.createCollection) {
|
||||||
const createCollection: CreateCollection = await mod.exports.createCollection();
|
const createCollection: CreateCollection = await mod.exports.createCollection();
|
||||||
|
const VALID_KEYS = new Set(['data', 'routes', 'permalink', 'pageSize', 'rss']);
|
||||||
for (const key of Object.keys(createCollection)) {
|
for (const key of Object.keys(createCollection)) {
|
||||||
if (key !== 'data' && key !== 'routes' && key !== 'permalink' && key !== 'pageSize' && key !== 'rss') {
|
if (!VALID_KEYS.has(key)) {
|
||||||
throw new Error(`[createCollection] unknown option: "${key}"`);
|
throw new Error(`[createCollection] unknown option: "${key}". Expected one of ${[...VALID_KEYS].join(', ')}.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let { data: loadData, routes, permalink, pageSize, rss: createRSS } = createCollection;
|
let { data: loadData, routes, permalink, pageSize, rss: createRSS } = createCollection;
|
||||||
|
if (!loadData) throw new Error(`[createCollection] must return \`data()\` function to create a collection.`);
|
||||||
if (!pageSize) pageSize = 25; // can’t be 0
|
if (!pageSize) pageSize = 25; // can’t be 0
|
||||||
let currentParams: Params = {};
|
let currentParams: Params = {};
|
||||||
|
|
||||||
// params
|
// params
|
||||||
if (routes || permalink) {
|
if (routes || permalink) {
|
||||||
if (!routes || !permalink) {
|
if (!routes) throw new Error('[createCollection] `permalink` requires `routes` as well.');
|
||||||
throw new Error('createCollection() must have both routes and permalink options. Include both together, or omit both.');
|
if (!permalink) throw new Error('[createCollection] `routes` requires `permalink` as well.');
|
||||||
}
|
|
||||||
let requestedParams = routes.find((p) => {
|
let requestedParams = routes.find((p) => {
|
||||||
const baseURL = (permalink as any)({ params: p });
|
const baseURL = (permalink as any)({ params: p });
|
||||||
additionalURLs.add(baseURL);
|
additionalURLs.add(baseURL);
|
||||||
|
@ -120,6 +129,8 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro
|
||||||
}
|
}
|
||||||
|
|
||||||
let data: any[] = await loadData({ params: currentParams });
|
let data: any[] = await loadData({ params: currentParams });
|
||||||
|
if (!data) throw new Error(`[createCollection] \`data()\` returned nothing (empty data)"`);
|
||||||
|
if (!Array.isArray(data)) data = [data]; // note: this is supposed to be a little friendlier to the user, but should we error out instead?
|
||||||
|
|
||||||
// handle RSS
|
// handle RSS
|
||||||
if (createRSS) {
|
if (createRSS) {
|
||||||
|
|
|
@ -42,4 +42,16 @@ Collections('generates pagination successfully', async ({ runtime }) => {
|
||||||
assert.equal(next.length, 1); // this should be on-page
|
assert.equal(next.length, 1); // this should be on-page
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Collections('can load remote data', async ({ runtime }) => {
|
||||||
|
const result = await runtime.load('/remote');
|
||||||
|
if (result.error) throw new Error(result.error);
|
||||||
|
const $ = doc(result.contents);
|
||||||
|
|
||||||
|
const PACKAGES_TO_TEST = ['canvas-confetti', 'preact', 'svelte'];
|
||||||
|
|
||||||
|
for (const pkg of PACKAGES_TO_TEST) {
|
||||||
|
assert.ok($(`#pkg-${pkg}`).length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Collections.run();
|
Collections.run();
|
||||||
|
|
24
packages/astro/test/fixtures/astro-collection/src/pages/$remote.astro
vendored
Normal file
24
packages/astro/test/fixtures/astro-collection/src/pages/$remote.astro
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
export let collection: any;
|
||||||
|
|
||||||
|
export async function createCollection() {
|
||||||
|
const data = await Promise.all([
|
||||||
|
fetch('https://api.skypack.dev/v1/package/canvas-confetti').then((res) => res.json()),
|
||||||
|
fetch('https://api.skypack.dev/v1/package/preact').then((res) => res.json()),
|
||||||
|
fetch('https://api.skypack.dev/v1/package/svelte').then((res) => res.json()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
async data() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{collection.data.map((pkg) => (
|
||||||
|
<div id={`pkg-${pkg.name}`}>{pkg.name}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
Loading…
Reference in a new issue