UNPKG

astro

Version:

Astro is a modern site builder with web best practices, performance, and DX front-of-mind.

281 lines (280 loc) • 10.6 kB
import nodeFs from "node:fs"; import { fileURLToPath } from "node:url"; import { dataToEsm } from "@rollup/pluginutils"; import { isRunnableDevEnvironment, normalizePath } from "vite"; import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js"; import { AstroError, AstroErrorData } from "../core/errors/index.js"; import { rootRelativePath } from "../core/viteUtils.js"; import { isAstroClientEnvironment } from "../environments.js"; import { createDefaultAstroMetadata } from "../vite-plugin-astro/metadata.js"; import { ASSET_IMPORTS_FILE, ASSET_IMPORTS_RESOLVED_STUB_ID, ASSET_IMPORTS_VIRTUAL_ID, CONTENT_MODULE_FLAG, CONTENT_RENDER_FLAG, DATA_STORE_MANIFEST_FILE, DATA_STORE_VIRTUAL_ID, MODULES_IMPORTS_FILE, MODULES_MJS_ID, MODULES_MJS_VIRTUAL_ID, RESOLVED_DATA_STORE_VIRTUAL_ID, RESOLVED_VIRTUAL_MODULE_ID, VIRTUAL_MODULE_ID } from "./consts.js"; import { getDataStoreDir, getDataStoreFile } from "./paths.js"; import { getContentPaths, isDeferredModule } from "./utils.js"; const LARGE_DATA_STORE_THRESHOLD = 5 * 1024 * 1024; function invalidateAssetImports(viteServer, filePath) { const timestamp = Date.now(); for (const environment of Object.values(viteServer.environments)) { const modules = environment.moduleGraph.getModulesByFile(filePath); if (modules) { for (const module of modules) { environment.moduleGraph.invalidateModule(module, void 0, timestamp, true); } } if (isRunnableDevEnvironment(environment)) { const runnerModules = environment.runner.evaluatedModules.getModulesByFile(filePath); if (runnerModules) { for (const runnerModule of runnerModules) { environment.runner.evaluatedModules.invalidateModule(runnerModule); } } } } } function invalidateDataStore(viteServer, { notifyClient = true } = {}) { const environment = viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr]; const module = environment.moduleGraph.getModuleById(RESOLVED_DATA_STORE_VIRTUAL_ID); if (module) { const timestamp = Date.now(); environment.moduleGraph.invalidateModule(module, void 0, timestamp, true); } if (isRunnableDevEnvironment(environment)) { const runnerModule = environment.runner.evaluatedModules.getModuleById( RESOLVED_DATA_STORE_VIRTUAL_ID ); if (runnerModule) { environment.runner.evaluatedModules.invalidateModule(runnerModule); } } environment.hot.send("astro:content-changed", {}); if (notifyClient) { viteServer.environments.client.hot.send({ type: "full-reload", path: "*" }); } } function astroContentVirtualModPlugin({ settings, fs }) { let dataStoreDir; let dataStoreFile; let devServer; let liveConfig; let isDev = false; return { name: "astro-content-virtual-mod-plugin", enforce: "pre", config(_, env) { isDev = env.command === "serve"; if (settings.config.experimental.collectionStorage === "chunked") { dataStoreDir = getDataStoreDir(settings, env.command === "serve"); dataStoreFile = new URL(DATA_STORE_MANIFEST_FILE, dataStoreDir); } else { dataStoreFile = getDataStoreFile(settings, env.command === "serve"); } const contentPaths = getContentPaths( settings.config, void 0, settings.config.legacy?.collectionsBackwardsCompat ); if (contentPaths.liveConfig.exists) { liveConfig = normalizePath(fileURLToPath(contentPaths.liveConfig.url)); } }, buildStart() { if (devServer) { const assetImportsPath = fileURLToPath(new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir)); devServer.watcher.add(fileURLToPath(dataStoreFile)); devServer.watcher.add(assetImportsPath); invalidateDataStore(devServer, { notifyClient: false }); invalidateAssetImports(devServer, assetImportsPath); } }, resolveId: { filter: { id: new RegExp( `^(${VIRTUAL_MODULE_ID}|${DATA_STORE_VIRTUAL_ID}|${MODULES_MJS_ID}|${ASSET_IMPORTS_VIRTUAL_ID})$|(?:\\?|&)${CONTENT_MODULE_FLAG}(?:&|=|$)` ) }, async handler(id, importer) { if (id === VIRTUAL_MODULE_ID) { if (liveConfig && importer && liveConfig === normalizePath(importer)) { return this.resolve("astro/virtual-modules/live-config", importer, { skipSelf: true }); } return RESOLVED_VIRTUAL_MODULE_ID; } if (id === DATA_STORE_VIRTUAL_ID) { return RESOLVED_DATA_STORE_VIRTUAL_ID; } if (isDeferredModule(id)) { const [, query] = id.split("?"); const params = new URLSearchParams(query); const fileName = params.get("fileName"); let importPath = void 0; if (fileName && URL.canParse(fileName, settings.config.root.toString())) { importPath = fileURLToPath(new URL(fileName, settings.config.root)); } if (importPath) { return await this.resolve(`${importPath}?${CONTENT_RENDER_FLAG}`); } } if (id === MODULES_MJS_ID) { const modules = new URL(MODULES_IMPORTS_FILE, settings.dotAstroDir); if (fs.existsSync(modules)) { return fileURLToPath(modules); } return MODULES_MJS_VIRTUAL_ID; } if (id === ASSET_IMPORTS_VIRTUAL_ID) { const assetImportsFile = new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir); if (fs.existsSync(assetImportsFile)) { return fileURLToPath(assetImportsFile); } return ASSET_IMPORTS_RESOLVED_STUB_ID; } } }, load: { filter: { id: new RegExp( `^(${RESOLVED_VIRTUAL_MODULE_ID}|${RESOLVED_DATA_STORE_VIRTUAL_ID}|${ASSET_IMPORTS_RESOLVED_STUB_ID}|${MODULES_MJS_VIRTUAL_ID})$` ) }, async handler(id) { if (id === RESOLVED_VIRTUAL_MODULE_ID) { const isClient = isAstroClientEnvironment(this.environment); const code = await generateContentEntryFile({ settings, fs, isClient }); const astro = createDefaultAstroMetadata(); astro.propagation = "in-tree"; return { code, meta: { astro } }; } if (id === RESOLVED_DATA_STORE_VIRTUAL_ID) { if (!fs.existsSync(dataStoreFile)) { return { code: "export default new Map()" }; } const jsonData = await fs.promises.readFile(dataStoreFile, "utf-8"); if (settings.config.experimental.collectionStorage === "chunked") { try { const manifest = JSON.parse(jsonData); const rawImport = (fileName) => { const path = rootRelativePath( settings.config.root, new URL(`./${fileName}`, dataStoreDir) ); return `(await import(${JSON.stringify(`${path}?raw`)}))`; }; const entries = Object.entries(manifest).map( ([collection, parts]) => `${JSON.stringify(collection)}:[${parts.map(rawImport).join(",")}]` ); const code = `export default{${entries.join(",")}}`; return { code, map: { mappings: "" } }; } catch (err) { const message = "Could not parse data store manifest JSON file"; this.error({ message, id, cause: err }); } } try { const parsed = JSON.parse(jsonData); const useRuntimeJsonParse = isDev && Buffer.byteLength(jsonData) > LARGE_DATA_STORE_THRESHOLD; return { code: useRuntimeJsonParse ? `export default JSON.parse(${JSON.stringify(jsonData)})` : dataToEsm(parsed, { compact: true }), map: { mappings: "" } }; } catch (err) { const message = "Could not parse JSON file"; this.error({ message, id, cause: err }); } } if (id === ASSET_IMPORTS_RESOLVED_STUB_ID) { const assetImportsFile = new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir); return { code: fs.existsSync(assetImportsFile) ? fs.readFileSync(assetImportsFile, "utf-8") : "export default new Map()" }; } if (id === MODULES_MJS_VIRTUAL_ID) { const modules = new URL(MODULES_IMPORTS_FILE, settings.dotAstroDir); return { code: fs.existsSync(modules) ? fs.readFileSync(modules, "utf-8") : "export default new Map()" }; } } }, configureServer(server) { devServer = server; const dataStorePath = fileURLToPath(dataStoreFile); const assetImportsPath = fileURLToPath(new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir)); server.watcher.on("add", (addedPath) => { if (addedPath === dataStorePath) { invalidateDataStore(server); invalidateAssetImports(server, assetImportsPath); } }); server.watcher.on("change", (changedPath) => { if (changedPath === dataStorePath) { invalidateDataStore(server); invalidateAssetImports(server, assetImportsPath); } else if (changedPath === assetImportsPath) { invalidateAssetImports(server, assetImportsPath); } }); } }; } async function generateContentEntryFile({ settings, isClient }) { const contentPaths = getContentPaths( settings.config, void 0, settings.config.legacy?.collectionsBackwardsCompat ); const relContentDir = rootRelativePath(settings.config.root, contentPaths.contentDir); let virtualModContents; if (isClient) { throw new AstroError({ ...AstroErrorData.ServerOnlyModule, message: AstroErrorData.ServerOnlyModule.message("astro:content") }); } else { virtualModContents = nodeFs.readFileSync(contentPaths.virtualModTemplate, "utf-8").replace("@@CONTENT_DIR@@", relContentDir).replace( "/* @@LIVE_CONTENT_CONFIG@@ */", contentPaths.liveConfig.exists ? ( // Dynamic import so it extracts the chunk and avoids a circular import `const liveCollections = (await import(${JSON.stringify(fileURLToPath(contentPaths.liveConfig.url))})).collections;` ) : "const liveCollections = {};" ); } return virtualModContents; } export { astroContentVirtualModPlugin };