-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Content collections] Load content config with full Vite setup (#6092)
* feat: use vite dev server for content config * refactor: improve export naming * chore: update `sync` to spin up server * refactor: run sync before build in cli * fix: move sync call to build setup * chore: clean up attachContent... types * chore: remove unneeded comment * chore: changeset * fix: attachContentServerListeners in unit tests * fix: allow forced contentDirExists * chore: update schema signature * fix: move content listeners to unit test * chore remove contentDirExists flag; unused * chore: stub weird unit test fix
- Loading branch information
1 parent
db2c59f
commit bf8d736
Showing
12 changed files
with
286 additions
and
236 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'astro': patch | ||
--- | ||
|
||
Ensure vite config (aliases, custom modules, etc) is respected when loading the content collection config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { cyan } from 'kleur/colors'; | ||
import { pathToFileURL } from 'node:url'; | ||
import type fsMod from 'node:fs'; | ||
import type { ViteDevServer } from 'vite'; | ||
import type { AstroSettings } from '../@types/astro.js'; | ||
import { info, LogOptions } from '../core/logger/core.js'; | ||
import { appendForwardSlash } from '../core/path.js'; | ||
import { createContentTypesGenerator } from './types-generator.js'; | ||
import { globalContentConfigObserver, getContentPaths } from './utils.js'; | ||
|
||
interface ContentServerListenerParams { | ||
fs: typeof fsMod; | ||
logging: LogOptions; | ||
settings: AstroSettings; | ||
viteServer: ViteDevServer; | ||
} | ||
|
||
export async function attachContentServerListeners({ | ||
viteServer, | ||
fs, | ||
logging, | ||
settings, | ||
}: ContentServerListenerParams) { | ||
const contentPaths = getContentPaths(settings.config); | ||
|
||
if (fs.existsSync(contentPaths.contentDir)) { | ||
info( | ||
logging, | ||
'content', | ||
`Watching ${cyan( | ||
contentPaths.contentDir.href.replace(settings.config.root.href, '') | ||
)} for changes` | ||
); | ||
await attachListeners(); | ||
} else { | ||
viteServer.watcher.on('addDir', contentDirListener); | ||
async function contentDirListener(dir: string) { | ||
if (appendForwardSlash(pathToFileURL(dir).href) === contentPaths.contentDir.href) { | ||
info(logging, 'content', `Content dir found. Watching for changes`); | ||
await attachListeners(); | ||
viteServer.watcher.removeListener('addDir', contentDirListener); | ||
} | ||
} | ||
} | ||
|
||
async function attachListeners() { | ||
const contentGenerator = await createContentTypesGenerator({ | ||
fs, | ||
settings, | ||
logging, | ||
viteServer, | ||
contentConfigObserver: globalContentConfigObserver, | ||
}); | ||
await contentGenerator.init(); | ||
info(logging, 'content', 'Types generated'); | ||
|
||
viteServer.watcher.on('add', (entry) => { | ||
contentGenerator.queueEvent({ name: 'add', entry }); | ||
}); | ||
viteServer.watcher.on('addDir', (entry) => | ||
contentGenerator.queueEvent({ name: 'addDir', entry }) | ||
); | ||
viteServer.watcher.on('change', (entry) => | ||
contentGenerator.queueEvent({ name: 'change', entry }) | ||
); | ||
viteServer.watcher.on('unlink', (entry) => { | ||
contentGenerator.queueEvent({ name: 'unlink', entry }); | ||
}); | ||
viteServer.watcher.on('unlinkDir', (entry) => | ||
contentGenerator.queueEvent({ name: 'unlinkDir', entry }) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
129 changes: 129 additions & 0 deletions
129
packages/astro/src/content/vite-plugin-content-imports.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import * as devalue from 'devalue'; | ||
import { pathToFileURL } from 'url'; | ||
import type { Plugin } from 'vite'; | ||
import type fsMod from 'node:fs'; | ||
import { AstroSettings } from '../@types/astro.js'; | ||
import { contentFileExts, CONTENT_FLAG } from './consts.js'; | ||
import { | ||
ContentConfig, | ||
globalContentConfigObserver, | ||
getContentPaths, | ||
getEntryData, | ||
getEntryInfo, | ||
getEntrySlug, | ||
parseFrontmatter, | ||
} from './utils.js'; | ||
import { escapeViteEnvReferences, getFileInfo } from '../vite-plugin-utils/index.js'; | ||
import { getEntryType } from './types-generator.js'; | ||
import { AstroError } from '../core/errors/errors.js'; | ||
import { AstroErrorData } from '../core/errors/errors-data.js'; | ||
|
||
function isContentFlagImport(viteId: string) { | ||
const { pathname, searchParams } = new URL(viteId, 'file://'); | ||
return searchParams.has(CONTENT_FLAG) && contentFileExts.some((ext) => pathname.endsWith(ext)); | ||
} | ||
|
||
export function astroContentImportPlugin({ | ||
fs, | ||
settings, | ||
}: { | ||
fs: typeof fsMod; | ||
settings: AstroSettings; | ||
}): Plugin { | ||
const contentPaths = getContentPaths(settings.config); | ||
|
||
return { | ||
name: 'astro:content-imports', | ||
async load(id) { | ||
const { fileId } = getFileInfo(id, settings.config); | ||
if (isContentFlagImport(id)) { | ||
const observable = globalContentConfigObserver.get(); | ||
|
||
// Content config should be loaded before this plugin is used | ||
if (observable.status === 'init') { | ||
throw new AstroError({ | ||
...AstroErrorData.UnknownContentCollectionError, | ||
message: 'Content config failed to load.', | ||
}); | ||
} | ||
|
||
let contentConfig: ContentConfig | undefined = | ||
observable.status === 'loaded' ? observable.config : undefined; | ||
if (observable.status === 'loading') { | ||
// Wait for config to load | ||
contentConfig = await new Promise((resolve) => { | ||
const unsubscribe = globalContentConfigObserver.subscribe((ctx) => { | ||
if (ctx.status === 'loaded') { | ||
resolve(ctx.config); | ||
unsubscribe(); | ||
} else if (ctx.status === 'error') { | ||
resolve(undefined); | ||
unsubscribe(); | ||
} | ||
}); | ||
}); | ||
} | ||
const rawContents = await fs.promises.readFile(fileId, 'utf-8'); | ||
const { | ||
content: body, | ||
data: unparsedData, | ||
matter: rawData = '', | ||
} = parseFrontmatter(rawContents, fileId); | ||
const entryInfo = getEntryInfo({ | ||
entry: pathToFileURL(fileId), | ||
contentDir: contentPaths.contentDir, | ||
}); | ||
if (entryInfo instanceof Error) return; | ||
|
||
const _internal = { filePath: fileId, rawData }; | ||
const partialEntry = { data: unparsedData, body, _internal, ...entryInfo }; | ||
// TODO: move slug calculation to the start of the build | ||
// to generate a performant lookup map for `getEntryBySlug` | ||
const slug = getEntrySlug(partialEntry); | ||
|
||
const collectionConfig = contentConfig?.collections[entryInfo.collection]; | ||
const data = collectionConfig | ||
? await getEntryData(partialEntry, collectionConfig) | ||
: unparsedData; | ||
|
||
const code = escapeViteEnvReferences(` | ||
export const id = ${JSON.stringify(entryInfo.id)}; | ||
export const collection = ${JSON.stringify(entryInfo.collection)}; | ||
export const slug = ${JSON.stringify(slug)}; | ||
export const body = ${JSON.stringify(body)}; | ||
export const data = ${devalue.uneval(data) /* TODO: reuse astro props serializer */}; | ||
export const _internal = { | ||
filePath: ${JSON.stringify(fileId)}, | ||
rawData: ${JSON.stringify(rawData)}, | ||
}; | ||
`); | ||
return { code }; | ||
} | ||
}, | ||
configureServer(viteServer) { | ||
viteServer.watcher.on('all', async (event, entry) => { | ||
if ( | ||
['add', 'unlink', 'change'].includes(event) && | ||
getEntryType(entry, contentPaths) === 'config' | ||
) { | ||
// Content modules depend on config, so we need to invalidate them. | ||
for (const modUrl of viteServer.moduleGraph.urlToModuleMap.keys()) { | ||
if (isContentFlagImport(modUrl)) { | ||
const mod = await viteServer.moduleGraph.getModuleByUrl(modUrl); | ||
if (mod) { | ||
viteServer.moduleGraph.invalidateModule(mod); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
}, | ||
async transform(code, id) { | ||
if (isContentFlagImport(id)) { | ||
// Escape before Rollup internal transform. | ||
// Base on MUCH trial-and-error, inspired by MDX integration 2-step transform. | ||
return { code: escapeViteEnvReferences(code) }; | ||
} | ||
}, | ||
}; | ||
} |
Oops, something went wrong.