UNPKG

astro

Version:

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

96 lines (95 loc) • 4.45 kB
import { realpathSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { SessionStorageInitError } from "../errors/errors-data.js"; import { AstroError } from "../errors/index.js"; import { normalizePath } from "../viteUtils.js"; import { normalizeSessionDriverConfig } from "./utils.js"; export const VIRTUAL_SESSION_DRIVER_ID = "virtual:astro:session-driver"; const RESOLVED_VIRTUAL_SESSION_DRIVER_ID = "\0" + VIRTUAL_SESSION_DRIVER_ID; export function vitePluginSessionDriver({ settings }) { return { name: VIRTUAL_SESSION_DRIVER_ID, enforce: "pre", resolveId: { filter: { id: new RegExp(`^${VIRTUAL_SESSION_DRIVER_ID}$`) }, handler() { return RESOLVED_VIRTUAL_SESSION_DRIVER_ID; } }, load: { filter: { id: new RegExp(`^${RESOLVED_VIRTUAL_SESSION_DRIVER_ID}$`) }, async handler() { const session = settings.config.session; if (session === false || !session?.driver) { return { code: "export default null;" }; } const driver = normalizeSessionDriverConfig(session.driver, session.options); const importerPath = fileURLToPath(import.meta.url); const resolved = await this.resolve(driver.entrypoint, importerPath); if (!resolved) { throw new AstroError({ ...SessionStorageInitError, message: SessionStorageInitError.message(`Failed to resolve session driver: ${driver.entrypoint}`, driver.entrypoint) }); } return { code: `import { default as _default } from ${JSON.stringify(resolved.id)};\nexport * from ${JSON.stringify(resolved.id)};\nexport default _default;` }; } } }; } // When no session driver will be present at request time, swap Astro's own // `core/session/provider.js` for `core/session/provider-disabled.js` so // Rollup tree-shakes `runtime.js` (and `unstorage`) out of the SSR bundle. // // This covers `session: false`, `session: undefined`, and a `session` // object without a driver. By the time this plugin resolves, adapters have // already wired their default driver into `config.session.driver` (during // `astro:config:setup`), so `session?.driver` reflects the final decision — // the same signal `vitePluginSessionDriver` uses to emit `default null`. // Swapping in the no-op provider is behavior-preserving: the real provider // already resolves `Astro.session` to `undefined` when no driver factory // exists, so the only difference is the dead runtime is dropped. // // To avoid hijacking unrelated `./session/provider.js` paths in user code // or third-party deps, we resolve each candidate specifier through Vite // and only redirect when it resolves to Astro's own provider file. const PROVIDER_FILENAME = "provider.js"; const DISABLED_PROVIDER_FILENAME = "provider-disabled.js"; function canonicalizePath(filePath) { try { // Resolve symlinks, which Node does for `import.meta.url` but Vite only // does when `resolve.preserveSymlinks` is false, and normalize to forward // slashes so the two sides compare equal on Windows. return normalizePath(realpathSync(filePath)); } catch { // Not a file on disk (bare specifier, virtual module), so there is no // symlink to resolve. return normalizePath(filePath); } } export function vitePluginSessionProvider({ settings }) { const providerPath = canonicalizePath(fileURLToPath(new URL(`./${PROVIDER_FILENAME}`, import.meta.url))); const disabledProviderPath = canonicalizePath(fileURLToPath(new URL(`./${DISABLED_PROVIDER_FILENAME}`, import.meta.url))); return { name: "astro:session-provider", enforce: "pre", async resolveId(id, importer) { // Keep the real provider only when a driver will be present. const session = settings.config.session; const hasSessionDriver = session !== false && !!session?.driver; if (hasSessionDriver) return null; // Cheap prefilter to avoid resolving every import in the graph. // Only specifiers that *could* point at Astro's provider file // proceed to the (expensive) full resolution + identity check. if (!normalizePath(id).endsWith(`/session/${PROVIDER_FILENAME}`)) return null; // Fast path: caller already passed Astro's absolute provider path. if (canonicalizePath(id) === providerPath) return disabledProviderPath; if (!importer) return null; const resolved = await this.resolve(id, importer, { skipSelf: true }); if (resolved && canonicalizePath(resolved.id) === providerPath) { return disabledProviderPath; } return null; } }; }