Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: clear content layer cache if config has changed #11591

Merged
merged 4 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading