mirror of
https://github.com/withastro/astro.git
synced 2025-03-31 23:31:30 -05:00
wip: allow integrations to refresh contel layer
This commit is contained in:
parent
89bab1e707
commit
b8b5ca2934
5 changed files with 72 additions and 25 deletions
|
@ -3298,6 +3298,11 @@ export interface SSRLoadedRenderer extends Pick<AstroRenderer, 'name' | 'clientE
|
|||
ssr: SSRLoadedRendererValue;
|
||||
}
|
||||
|
||||
export interface RefreshContentOptions {
|
||||
loaders?: Array<string>;
|
||||
context?: Record<string, any>;
|
||||
}
|
||||
|
||||
export type HookParameters<
|
||||
Hook extends keyof AstroIntegration['hooks'],
|
||||
Fn = AstroIntegration['hooks'][Hook],
|
||||
|
@ -3341,6 +3346,7 @@ declare global {
|
|||
server: vite.ViteDevServer;
|
||||
logger: AstroIntegrationLogger;
|
||||
toolbar: ReturnType<typeof getToolbarServerCommunicationHelpers>;
|
||||
refreshContent?: (options: RefreshContentOptions) => Promise<void>;
|
||||
}) => void | Promise<void>;
|
||||
'astro:server:start': (options: {
|
||||
address: AddressInfo;
|
||||
|
|
|
@ -3,7 +3,7 @@ import { isAbsolute } from 'node:path';
|
|||
import { fileURLToPath } from 'node:url';
|
||||
import type { FSWatcher } from 'vite';
|
||||
import xxhash from 'xxhash-wasm';
|
||||
import type { AstroSettings } from '../@types/astro.js';
|
||||
import type { AstroSettings, RefreshContentOptions } from '../@types/astro.js';
|
||||
import { AstroUserError } from '../core/errors/errors.js';
|
||||
import type { Logger } from '../core/logger/core.js';
|
||||
import {
|
||||
|
@ -44,12 +44,6 @@ export class ContentLayer {
|
|||
this.#watcher = watcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the content layer is currently loading content
|
||||
*/
|
||||
get loading() {
|
||||
return this.#loading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch for changes to the content config and trigger a sync when it changes.
|
||||
|
@ -71,22 +65,6 @@ export class ContentLayer {
|
|||
this.#unsubscribe?.();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the `load()` method of each collection's loader, which will load the data and save it in the data store.
|
||||
* The loader itself is responsible for deciding whether this will clear and reload the full collection, or
|
||||
* perform an incremental update. After the data is loaded, the data store is written to disk.
|
||||
*/
|
||||
async sync() {
|
||||
if (this.#loading) {
|
||||
return;
|
||||
}
|
||||
this.#loading = true;
|
||||
try {
|
||||
await this.#doSync();
|
||||
} finally {
|
||||
this.#loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async #getGenerateDigest() {
|
||||
if (this.#generateDigest) {
|
||||
|
@ -108,10 +86,12 @@ export class ContentLayer {
|
|||
collectionName,
|
||||
loaderName = 'content',
|
||||
parseData,
|
||||
refreshContextData,
|
||||
}: {
|
||||
collectionName: string;
|
||||
loaderName: string;
|
||||
parseData: LoaderContext['parseData'];
|
||||
refreshContextData?: Record<string, unknown>;
|
||||
}): Promise<LoaderContext> {
|
||||
return {
|
||||
collection: collectionName,
|
||||
|
@ -122,10 +102,17 @@ export class ContentLayer {
|
|||
parseData,
|
||||
generateDigest: await this.#getGenerateDigest(),
|
||||
watcher: this.#watcher,
|
||||
refreshContextData
|
||||
};
|
||||
}
|
||||
|
||||
async #doSync() {
|
||||
/**
|
||||
* Run the `load()` method of each collection's loader, which will load the data and save it in the data store.
|
||||
* The loader itself is responsible for deciding whether this will clear and reload the full collection, or
|
||||
* perform an incremental update. After the data is loaded, the data store is written to disk.
|
||||
*/
|
||||
async sync(options?: RefreshContentOptions) {
|
||||
|
||||
const contentConfig = globalContentConfigObserver.get();
|
||||
const logger = this.#logger.forkIntegrationLogger('content');
|
||||
if (contentConfig?.status !== 'loaded') {
|
||||
|
@ -171,6 +158,14 @@ export class ContentLayer {
|
|||
}
|
||||
}
|
||||
|
||||
// If loaders are specified, only sync the specified loaders
|
||||
if (
|
||||
options?.loaders &&
|
||||
(typeof collection.loader !== 'object' || !options.loaders.includes(collection.loader.name))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const collectionWithResolvedSchema = { ...collection, schema };
|
||||
|
||||
const parseData: LoaderContext['parseData'] = async ({ id, data, filePath = '' }) => {
|
||||
|
@ -204,6 +199,7 @@ export class ContentLayer {
|
|||
collectionName: name,
|
||||
parseData,
|
||||
loaderName: collection.loader.name,
|
||||
refreshContextData: options?.context
|
||||
});
|
||||
|
||||
if (typeof collection.loader === 'function') {
|
||||
|
|
|
@ -31,6 +31,9 @@ export interface LoaderContext {
|
|||
|
||||
/** When running in dev, this is a filesystem watcher that can be used to trigger updates */
|
||||
watcher?: FSWatcher;
|
||||
|
||||
/** If the loader has been triggered by an integration, this may optionally contain extra data set by that integration */
|
||||
refreshContextData?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface Loader {
|
||||
|
|
|
@ -12,6 +12,7 @@ import type {
|
|||
ContentEntryType,
|
||||
DataEntryType,
|
||||
HookParameters,
|
||||
RefreshContentOptions,
|
||||
RouteData,
|
||||
RouteOptions,
|
||||
} from '../@types/astro.js';
|
||||
|
@ -22,6 +23,7 @@ import { mergeConfig } from '../core/config/index.js';
|
|||
import type { AstroIntegrationLogger, Logger } from '../core/logger/core.js';
|
||||
import { isServerLikeOutput } from '../core/util.js';
|
||||
import { validateSupportedFeatures } from './features-validation.js';
|
||||
import { globalContentLayer } from '../content/content-layer.js';
|
||||
|
||||
async function withTakingALongTimeMsg<T>({
|
||||
name,
|
||||
|
@ -370,6 +372,14 @@ export async function runHookServerSetup({
|
|||
server: ViteDevServer;
|
||||
logger: Logger;
|
||||
}) {
|
||||
let refreshContent: undefined | ((options: RefreshContentOptions) => Promise<void>);
|
||||
if (config.experimental?.contentLayer) {
|
||||
refreshContent = async (options: RefreshContentOptions) => {
|
||||
const contentLayer = await globalContentLayer.get();
|
||||
await contentLayer.sync(options);
|
||||
};
|
||||
}
|
||||
|
||||
for (const integration of config.integrations) {
|
||||
if (integration?.hooks?.['astro:server:setup']) {
|
||||
await withTakingALongTimeMsg({
|
||||
|
@ -379,6 +389,7 @@ export async function runHookServerSetup({
|
|||
server,
|
||||
logger: getLogger(integration, logger),
|
||||
toolbar: getToolbarServerCommunicationHelpers(server),
|
||||
refreshContent,
|
||||
}),
|
||||
logger,
|
||||
});
|
||||
|
|
|
@ -3,7 +3,38 @@ import { defineConfig } from 'astro/config';
|
|||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
export default defineConfig({
|
||||
integrations: [mdx()],
|
||||
integrations: [mdx(), {
|
||||
name: '@astrojs/my-integration',
|
||||
hooks: {
|
||||
'astro:server:setup': async ({ server, refreshContent }) => {
|
||||
server.middlewares.use('/_refresh', async (req, res) => {
|
||||
if(req.method !== 'POST') {
|
||||
res.statusCode = 405
|
||||
res.end('Method Not Allowed');
|
||||
return
|
||||
}
|
||||
let body = '';
|
||||
req.on('data', chunk => {
|
||||
body += chunk.toString();
|
||||
});
|
||||
req.on('end', async () => {
|
||||
try {
|
||||
const webhookBody = JSON.parse(body);
|
||||
await refreshContent({
|
||||
context: { webhookBody },
|
||||
loaders: ['increment-loader']
|
||||
});
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ message: 'Content refreshed successfully' }));
|
||||
} catch (error) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Failed to refresh content' }));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}],
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: {
|
||||
|
|
Loading…
Add table
Reference in a new issue