astro-pure
Version:
A simple, clean but powerful blog theme build by astro.
109 lines (99 loc) • 3.99 kB
text/typescript
import { existsSync } from 'node:fs'
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import type { AstroConfig, ViteUserConfig } from 'astro'
import type { UserConfig } from '../types/user-config'
const collectionNames = ['docs', 'blog'] as const
export type DefaultCollection = (typeof collectionNames)[number]
export function resolveCollectionPath(collection: DefaultCollection, srcDir: URL) {
return resolve(fileURLToPath(srcDir), `content/${collection}`)
}
function resolveVirtualModuleId<T extends string>(id: T): `\0${T}` {
return `\0${id}`
}
/** Vite plugin that exposes Starlight user config and project context via virtual modules. */
export function vitePluginUserConfig(
opts: UserConfig,
{
build,
legacy,
root,
srcDir,
trailingSlash
}: Pick<AstroConfig, 'root' | 'srcDir' | 'trailingSlash'> & {
build: Pick<AstroConfig['build'], 'format'>
legacy: Pick<AstroConfig['legacy'], 'collections'>
}
): NonNullable<ViteUserConfig['plugins']>[number] {
/**
* Resolves module IDs to a usable format:
* - Relative paths (e.g. `'./module.js'`) are resolved against `base` and formatted as an absolute path.
* - Package identifiers (e.g. `'module'`) are returned unchanged.
*
* By default, `base` is the project root directory.
*/
const resolveId = (id: string, base = root) =>
JSON.stringify(id.startsWith('.') ? resolve(fileURLToPath(base), id) : id)
/**
* Resolves a path to a Starlight file relative to this file.
* @example
* resolveLocalPath('../utils/git.ts');
* // => '"/users/houston/docs/node_modules/@astrojs/starlight/utils/git.ts"'
*/
let collectionConfigImportPath = resolve(
fileURLToPath(srcDir),
legacy.collections ? './content/config.ts' : './content.config.ts'
)
// If not using legacy collections and the config doesn't exist, fallback to the legacy location.
// We need to test this ahead of time as we cannot `try/catch` a failing import in the virtual
// module as this would fail at build time when Rollup tries to resolve a non-existent path.
if (!legacy.collections && !existsSync(collectionConfigImportPath)) {
collectionConfigImportPath = resolve(fileURLToPath(srcDir), './content/config.ts')
}
/** Map of virtual module names to their code contents as strings. */
const modules = {
'virtual:config': `export default ${JSON.stringify(opts)}`,
'virtual:project-context': `export default ${JSON.stringify({
build: { format: build.format },
legacyCollections: legacy.collections,
root,
srcDir,
trailingSlash
})}`,
/**
* Module containing imports of user-specified custom CSS files.
*/
'virtual:user-css': opts.customCss.map((id) => `import ${resolveId(id)};`).join(''),
'virtual:user-images': opts.logo
? 'src' in opts.logo
? `import src from ${resolveId(
opts.logo.src
)}; export const logos = { dark: src, light: src };`
: `import dark from ${resolveId(opts.logo.dark)}; import light from ${resolveId(
opts.logo.light
)}; export const logos = { dark, light };`
: 'export const logos = {};',
'virtual:collection-config': `let userCollections;
try {
userCollections = (await import(${resolveId('./content/config.ts', srcDir)})).collections;
} catch {}
export const collections = userCollections;`
} satisfies Record<string, string>
/** Mapping names prefixed with `\0` to their original form. */
const resolutionMap = Object.fromEntries(
(Object.keys(modules) as (keyof typeof modules)[]).map((key) => [
resolveVirtualModuleId(key),
key
])
)
return {
name: 'vite-plugin-starlight-user-config',
resolveId(id): string | void {
if (id in modules) return resolveVirtualModuleId(id)
},
load(id): string | void {
const resolution = resolutionMap[id]
if (resolution) return modules[resolution]
}
}
}