diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 11804e5cab..6314dc4c5e 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -33,6 +33,7 @@ import markdownVitePlugin from '../vite-plugin-markdown/index.js'; import astroScannerPlugin from '../vite-plugin-scanner/index.js'; import astroScriptsPlugin from '../vite-plugin-scripts/index.js'; import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js'; +import vitePluginSessions from '../vite-plugin-sessions/index.js'; import { vitePluginSSRManifest } from '../vite-plugin-ssr-manifest/index.js'; import type { SSRManifest } from './app/types.js'; import type { Logger } from './logger/core.js'; @@ -143,6 +144,7 @@ export async function createVite( astroLoadFallbackPlugin({ fs, root: settings.config.root }), astroVitePlugin({ settings, logger }), astroScriptsPlugin({ settings }), + vitePluginSessions({ settings }), // The server plugin is for dev only and having it run during the build causes // the build to run very slow as the filewatcher is triggered often. command === 'dev' && vitePluginAstroServer({ settings, logger, fs, manifest, ssrManifest }), // ssrManifest is only required in dev mode, where it gets created before a Vite instance is created, and get passed to this function diff --git a/packages/astro/src/core/session.ts b/packages/astro/src/core/session.ts index 23cdf9b4f7..bdb786b4ba 100644 --- a/packages/astro/src/core/session.ts +++ b/packages/astro/src/core/session.ts @@ -344,7 +344,7 @@ export class AstroSession { * Ensures the storage is initialized. * This is called automatically when a storage operation is needed. */ - async #ensureStorage():Promise { + async #ensureStorage(): Promise { if (this.#storage) { return this.#storage; } @@ -363,13 +363,22 @@ export class AstroSession { } let driver: ((config: SessionConfig['options']) => Driver) | null = null; - const entry = - builtinDrivers[this.#config.driver as keyof typeof builtinDrivers] || this.#config.driver; - try { - // Try to load the driver from the built-in unstorage drivers. - // Otherwise, assume it's a custom driver and load by name. + const isBuiltin = this.#config.driver in builtinDrivers; + // Try to load the driver from the built-in unstorage drivers. + // Otherwise, assume it's a custom driver and load by name. + const driverPackage: string = isBuiltin + ? builtinDrivers[this.#config.driver as keyof typeof builtinDrivers] + : this.#config.driver; - driver = await import(entry).then((r) => r.default || r); + try { + // If driver is not a builtin, or in development or test, load the driver directly. + if (!isBuiltin || process.env.NODE_ENV === 'development' || process.env.NODE_TEST_CONTEXT) { + driver = await import(/* @vite-ignore */ driverPackage).then((r) => r.default || r); + } else { + // In production, load via the virtual module as it will be bundled by Vite + // @ts-expect-error - virtual module + driver = await import('@astro-session-driver').then((r) => r.default || r); + } } catch (err: any) { // If the driver failed to load, throw an error. if (err.code === 'ERR_MODULE_NOT_FOUND') { @@ -377,7 +386,7 @@ export class AstroSession { { ...SessionStorageInitError, message: SessionStorageInitError.message( - err.message.includes(`Cannot find package '${entry}'`) + err.message.includes(`Cannot find package '${driverPackage}'`) ? 'The driver module could not be found.' : err.message, this.#config.driver, diff --git a/packages/astro/src/vite-plugin-sessions/index.ts b/packages/astro/src/vite-plugin-sessions/index.ts new file mode 100644 index 0000000000..293c39bf00 --- /dev/null +++ b/packages/astro/src/vite-plugin-sessions/index.ts @@ -0,0 +1,43 @@ +import { fileURLToPath } from 'node:url'; +import { builtinDrivers } from 'unstorage'; +import type { Plugin as VitePlugin } from 'vite'; +import type { AstroSettings } from '../types/astro.js'; + +const SESSION_DRIVER_ID = '@astro-session-driver'; +const RESOLVED_SESSION_DRIVER_ID = `\0${SESSION_DRIVER_ID}`; + +export default function vitePluginSessions({ settings }: { settings: AstroSettings }): VitePlugin { + return { + name: 'astro:vite-plugin-sessions', + enforce: 'pre', + async resolveId(source) { + if (source === SESSION_DRIVER_ID) { + return RESOLVED_SESSION_DRIVER_ID; + } + // Resolve the driver entrypoint so that we bundle it + if (source.startsWith('unstorage/drivers/')) { + return fileURLToPath(import.meta.resolve(source)); + } + }, + async load(id) { + if (id === RESOLVED_SESSION_DRIVER_ID) { + let driver = settings.config.experimental?.session?.driver; + if (driver && driver in builtinDrivers) { + if (driver === 'fs') { + // fs tries to bundle chokidar (and therefore fsevents), which is a binary + driver = 'fsLite'; + } + driver = builtinDrivers[driver as keyof typeof builtinDrivers]; + } + if (!driver) { + return `export default function driver() { return null }`; + } + const resolved = await this.resolve(driver, undefined, { skipSelf: false }); + if (!resolved) { + throw new Error(`Could not resolve session driver ${driver}`); + } + return `export { default } from ${JSON.stringify(resolved?.id)};`; + } + }, + }; +} diff --git a/packages/astro/test/fixtures/sessions/astro.config.mjs b/packages/astro/test/fixtures/sessions/astro.config.mjs index 2d15912f3a..ba03682073 100644 --- a/packages/astro/test/fixtures/sessions/astro.config.mjs +++ b/packages/astro/test/fixtures/sessions/astro.config.mjs @@ -9,7 +9,7 @@ export default defineConfig({ output: 'server', experimental: { session: { - driver: 'fs', + driver: 'fsLite', options: { base: join(tmpdir(), 'sessions'), },