Skip to content

Commit

Permalink
fix: clear content layer cache if config has changed (#11591)
Browse files Browse the repository at this point in the history
* fix: clear content layer cache if config has changed

* Add test

* Watch config

* Change from review
  • Loading branch information
ascorbic authored Aug 1, 2024
1 parent 2abc38b commit aecae03
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 15 deletions.
11 changes: 9 additions & 2 deletions packages/astro/src/content/data-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ export class DataStore {
this.#saveToDiskDebounced();
}

clearAll() {
this.#collections.clear();
this.#saveToDiskDebounced();
}

has(collectionName: string, key: string) {
const collection = this.#collections.get(collectionName);
if (collection) {
Expand Down Expand Up @@ -227,8 +232,10 @@ export default new Map([${exports.join(', ')}]);
this.addAssetImports(assets, fileName),
};
}

metaStore(collectionName: string): MetaStore {
/**
* Returns a MetaStore for a given collection, or if no collection is provided, the default meta collection.
*/
metaStore(collectionName = ':meta'): MetaStore {
const collectionKey = `meta:${collectionName}`;
return {
get: (key: string) => this.get(collectionKey, key),
Expand Down
9 changes: 9 additions & 0 deletions packages/astro/src/content/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ export async function syncContentLayer({
logger.debug('Content config not loaded, skipping sync');
return;
}

const previousConfigDigest = await store.metaStore().get('config-digest');
const { digest: currentConfigDigest } = contentConfig.config;
if (currentConfigDigest && previousConfigDigest !== currentConfigDigest) {
logger.info('Content config changed, clearing cache');
store.clearAll();
await store.metaStore().set('config-digest', currentConfigDigest);
}

// xxhash is a very fast non-cryptographic hash function that is used to generate a content digest
// It uses wasm, so we need to load it asynchronously.
const { h64ToString } = await xxhash();
Expand Down
8 changes: 6 additions & 2 deletions packages/astro/src/content/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { slug as githubSlug } from 'github-slugger';
import matter from 'gray-matter';
import type { PluginContext } from 'rollup';
import { type ViteDevServer, normalizePath } from 'vite';
import xxhash from 'xxhash-wasm';
import { z } from 'zod';
import type {
AstroConfig,
Expand Down Expand Up @@ -95,7 +96,7 @@ export const contentConfigParser = z.object({
});

export type CollectionConfig = z.infer<typeof collectionConfigParser>;
export type ContentConfig = z.infer<typeof contentConfigParser>;
export type ContentConfig = z.infer<typeof contentConfigParser> & { digest?: string };

type EntryInternal = { rawData: string | undefined; filePath: string };

Expand Down Expand Up @@ -497,7 +498,10 @@ export async function loadContentConfig({

const config = contentConfigParser.safeParse(unparsedConfig);
if (config.success) {
return config.data;
// Generate a digest of the config file so we can invalidate the cache if it changes
const hasher = await xxhash();
const digest = await hasher.h64ToString(await fs.promises.readFile(configPathname, 'utf-8'));
return { ...config.data, digest };
} else {
return undefined;
}
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/content/vite-plugin-content-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export const _internal = {

// The content config could depend on collection entries via `reference()`.
// Reload the config in case of changes.
// Changes to the config file itself are handled in types-generator.ts, so we skip them here
if (entryType === 'content' || entryType === 'data') {
await reloadContentConfigObserver({ fs, settings, viteServer });
}
Expand Down
3 changes: 1 addition & 2 deletions packages/astro/src/core/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ import { levels, timerMessage } from '../logger/core.js';
import { apply as applyPolyfill } from '../polyfill.js';
import { createRouteManifest } from '../routing/index.js';
import { getServerIslandRouteData } from '../server-islands/endpoint.js';
import { clearContentLayerCache } from '../sync/index.js';
import { ensureProcessNodeEnv, isServerLikeOutput } from '../util.js';
import { collectPagesData } from './page-data.js';
import { staticBuild, viteBuild } from './static-build.js';
import type { StaticBuildOptions } from './types.js';
import { getTimeStat } from './util.js';
import { clearContentLayerCache } from '../sync/index.js';
export interface BuildOptions {
/**
* Teardown the compiler WASM instance after build. This can improve performance when
Expand All @@ -43,7 +43,6 @@ export interface BuildOptions {
* @default true
*/
teardownCompiler?: boolean;

}

/**
Expand Down
36 changes: 31 additions & 5 deletions packages/astro/src/core/dev/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { DATA_STORE_FILE } from '../../content/consts.js';
import { DataStore, globalDataStore } from '../../content/data-store.js';
import { attachContentServerListeners } from '../../content/index.js';
import { syncContentLayer } from '../../content/sync.js';
import { globalContentConfigObserver } from '../../content/utils.js';
import { telemetry } from '../../events/index.js';
import * as msg from '../messages.js';
import { ensureProcessNodeEnv } from '../util.js';
Expand Down Expand Up @@ -120,11 +121,36 @@ export default async function dev(inlineConfig: AstroInlineConfig): Promise<DevS
globalDataStore.set(store);
}

await syncContentLayer({
settings: restart.container.settings,
logger,
watcher: restart.container.viteServer.watcher,
store,
const config = globalContentConfigObserver.get();
let currentDigest: string | undefined = undefined;
// mutex to prevent multiple syncContentLayer calls
let loading = false;
if (config.status === 'loaded') {
currentDigest = config.config.digest;
loading = true;
await syncContentLayer({
settings: restart.container.settings,
logger,
watcher: restart.container.viteServer.watcher,
store,
}).finally(() => {
loading = false;
});
}

globalContentConfigObserver.subscribe(async (ctx) => {
if (!loading && ctx.status === 'loaded' && ctx.config.digest !== currentDigest) {
loading = true;
currentDigest = ctx.config.digest;
await syncContentLayer({
settings: restart.container.settings,
logger,
watcher: restart.container.viteServer.watcher,
store,
}).finally(() => {
loading = false;
});
}
});

logger.info(null, green('watching for file changes...'));
Expand Down
9 changes: 6 additions & 3 deletions packages/astro/src/core/sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ export default async function sync({
/**
* Clears the content layer and content collection cache, forcing a full rebuild.
*/
export async function clearContentLayerCache({ astroConfig, logger, fs = fsMod }: { astroConfig: AstroConfig; logger: Logger, fs?: typeof fsMod }) {
export async function clearContentLayerCache({
astroConfig,
logger,
fs = fsMod,
}: { astroConfig: AstroConfig; logger: Logger; fs?: typeof fsMod }) {
const dataStore = new URL(DATA_STORE_FILE, astroConfig.cacheDir);
if (fs.existsSync(dataStore)) {
logger.debug('content', 'clearing data store');
Expand All @@ -93,9 +97,8 @@ export async function syncInternal({
fs = fsMod,
settings,
skip,
force
force,
}: SyncOptions): Promise<void> {

if (force) {
await clearContentLayerCache({ astroConfig: settings.config, logger, fs });
}
Expand Down
11 changes: 11 additions & 0 deletions packages/astro/test/content-layer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,17 @@ describe('Content Layer', () => {
newJson = devalue.parse(await fixture.readFile('/collections.json'));
assert.equal(newJson.increment.data.lastValue, 1);
});

it('clears the store on new build if the config has changed', async () => {
let newJson = devalue.parse(await fixture.readFile('/collections.json'));
assert.equal(newJson.increment.data.lastValue, 1);
await fixture.editFile('src/content/config.ts', (prev) => {
return `${prev}\nexport const foo = 'bar';`;
});
await fixture.build();
newJson = devalue.parse(await fixture.readFile('/collections.json'));
assert.equal(newJson.increment.data.lastValue, 1);
});
});

describe('Dev', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/test/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export async function loadFixture(inlineConfig) {
typeof newContentsOrCallback === 'function'
? newContentsOrCallback(contents)
: newContentsOrCallback;
const nextChange = onNextChange();
const nextChange = devServer ? onNextChange() : Promise.resolve();
await fs.promises.writeFile(fileUrl, newContents);
await nextChange;
return reset;
Expand Down

0 comments on commit aecae03

Please sign in to comment.