From ef8798f6cb410ec4067893d2a9cfcdf9a536791d Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 6 Dec 2024 09:57:04 +0000 Subject: [PATCH] fix: [sessions] import storage driver in manifest (#12654) * wip * wip * Export manifest in middleware * Changeset conf * Pass session to edge middleware * Support initial session data * Persist edge session on redirect * Remove middleware-related changes * Refactor * Remove vite plugin * Format * Simplify import * Handle missing config * Handle async resolution --- packages/astro/src/core/app/types.ts | 4 +- .../src/core/build/plugins/plugin-manifest.ts | 8 ++- packages/astro/src/core/create-vite.ts | 2 - packages/astro/src/core/session.ts | 51 ++++++++++++------- packages/astro/src/types/public/config.ts | 6 ++- .../astro/src/vite-plugin-sessions/index.ts | 44 ---------------- 6 files changed, 47 insertions(+), 68 deletions(-) delete mode 100644 packages/astro/src/vite-plugin-sessions/index.ts diff --git a/packages/astro/src/core/app/types.ts b/packages/astro/src/core/app/types.ts index 16aaa54117..dde34a9c84 100644 --- a/packages/astro/src/core/app/types.ts +++ b/packages/astro/src/core/app/types.ts @@ -1,7 +1,7 @@ import type { RoutingStrategies } from '../../i18n/utils.js'; import type { ComponentInstance, SerializedRouteData } from '../../types/astro.js'; import type { AstroMiddlewareInstance } from '../../types/public/common.js'; -import type { Locales, SessionConfig } from '../../types/public/config.js'; +import type { Locales, ResolvedSessionConfig, SessionConfig } from '../../types/public/config.js'; import type { RouteData, SSRComponentMetadata, @@ -70,7 +70,7 @@ export type SSRManifest = { middleware?: () => Promise | AstroMiddlewareInstance; checkOrigin: boolean; envGetSecretEnabled: boolean; - sessionConfig?: SessionConfig; + sessionConfig?: ResolvedSessionConfig }; export type SSRManifestI18n = { diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts index 07ac03065d..70a0947c48 100644 --- a/packages/astro/src/core/build/plugins/plugin-manifest.ts +++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts @@ -22,6 +22,7 @@ import { type BuildInternals, cssOrder, mergeInlineCss } from '../internal.js'; import type { AstroBuildPlugin } from '../plugin.js'; import type { StaticBuildOptions } from '../types.js'; import { makePageDataKey } from './util.js'; +import { resolveSessionDriver } from '../../session.js'; const manifestReplace = '@@ASTRO_MANIFEST_REPLACE@@'; const replaceExp = new RegExp(`['"]${manifestReplace}['"]`, 'g'); @@ -29,7 +30,7 @@ const replaceExp = new RegExp(`['"]${manifestReplace}['"]`, 'g'); export const SSR_MANIFEST_VIRTUAL_MODULE_ID = '@astrojs-manifest'; export const RESOLVED_SSR_MANIFEST_VIRTUAL_MODULE_ID = '\0' + SSR_MANIFEST_VIRTUAL_MODULE_ID; -function vitePluginManifest(_options: StaticBuildOptions, internals: BuildInternals): VitePlugin { +function vitePluginManifest(options: StaticBuildOptions, internals: BuildInternals): VitePlugin { return { name: '@astro/plugin-build-manifest', enforce: 'post', @@ -52,11 +53,16 @@ function vitePluginManifest(_options: StaticBuildOptions, internals: BuildIntern `import { deserializeManifest as _deserializeManifest } from 'astro/app'`, `import { _privateSetManifestDontUseThis } from 'astro:ssr-manifest'`, ]; + + const resolvedDriver = await resolveSessionDriver(options.settings.config.experimental?.session?.driver); + const contents = [ `const manifest = _deserializeManifest('${manifestReplace}');`, + `if (manifest.sessionConfig) manifest.sessionConfig.driverModule = ${resolvedDriver ? `() => import(${JSON.stringify(resolvedDriver)})` : 'null'};`, `_privateSetManifestDontUseThis(manifest);`, ]; const exports = [`export { manifest }`]; + return [...imports, ...contents, ...exports].join('\n'); } }, diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 29f6e43c14..7b680c4055 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -33,7 +33,6 @@ 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'; @@ -144,7 +143,6 @@ 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 1e477465e7..51088f793b 100644 --- a/packages/astro/src/core/session.ts +++ b/packages/astro/src/core/session.ts @@ -1,6 +1,16 @@ import { stringify, unflatten } from 'devalue'; -import { type BuiltinDriverOptions, type Driver, type Storage, builtinDrivers, createStorage } from 'unstorage'; -import type { SessionConfig, SessionDriverName } from '../types/public/config.js'; +import { + type BuiltinDriverOptions, + type Driver, + type Storage, + builtinDrivers, + createStorage, +} from 'unstorage'; +import type { + ResolvedSessionConfig, + SessionConfig, + SessionDriverName, +} from '../types/public/config.js'; import type { AstroCookies } from './cookies/cookies.js'; import type { AstroCookieSetOptions } from './cookies/cookies.js'; import { SessionStorageInitError, SessionStorageSaveError } from './errors/errors-data.js'; @@ -15,7 +25,7 @@ export class AstroSession { // The cookies object. #cookies: AstroCookies; // The session configuration. - #config: Omit, 'cookie'>; + #config: Omit, 'cookie'>; // The cookie config #cookieConfig?: AstroCookieSetOptions; // The cookie name @@ -41,7 +51,7 @@ export class AstroSession { { cookie: cookieConfig = DEFAULT_COOKIE_NAME, ...config - }: Exclude, undefined>, + }: Exclude, undefined>, ) { this.#cookies = cookies; if (typeof cookieConfig === 'object') { @@ -354,7 +364,7 @@ export class AstroSession { return this.#storage; } // Use fsLite rather than fs, because fs can't be bundled. Add a default base path if not provided. - if(this.#config.driver === 'fs' || this.#config.driver === 'fsLite') { + if (this.#config.driver === 'fs' || this.#config.driver === 'fsLite') { this.#config.options ??= {}; this.#config.driver = 'fsLite'; (this.#config.options as BuiltinDriverOptions['fsLite']).base ??= '.astro/session'; @@ -370,21 +380,13 @@ export class AstroSession { } let driver: ((config: SessionConfig['options']) => Driver) | null = null; - 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; + const driverPackage = await resolveSessionDriver(this.#config.driver); 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); + if (this.#config.driverModule) { + driver = (await this.#config.driverModule()).default; + } else if (driverPackage) { + driver = (await import(driverPackage)).default; } } catch (err: any) { // If the driver failed to load, throw an error. @@ -431,3 +433,16 @@ export class AstroSession { } } } +// TODO: make this sync when we drop support for Node < 18.19.0 +export function resolveSessionDriver(driver: string | undefined): Promise | string | null { + if (!driver) { + return null; + } + if (driver === 'fs') { + return import.meta.resolve(builtinDrivers.fsLite); + } + if (driver in builtinDrivers) { + return import.meta.resolve(builtinDrivers[driver as keyof typeof builtinDrivers]); + } + return driver; +} diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts index 3c9f4c8e09..3e5cfd6d8c 100644 --- a/packages/astro/src/types/public/config.ts +++ b/packages/astro/src/types/public/config.ts @@ -5,7 +5,7 @@ import type { RemarkRehype, ShikiConfig, } from '@astrojs/markdown-remark'; -import type { BuiltinDriverName, BuiltinDriverOptions, Storage } from 'unstorage'; +import type { BuiltinDriverName, BuiltinDriverOptions, Driver, Storage } from 'unstorage'; import type { UserConfig as OriginalViteUserConfig, SSROptions as ViteSSROptions } from 'vite'; import type { ImageFit, ImageLayout } from '../../assets/types.js'; import type { RemotePattern } from '../../assets/utils/remotePattern.js'; @@ -135,6 +135,10 @@ export type SessionConfig = ? TestSessionConfig : CustomSessionConfig; +export type ResolvedSessionConfig = SessionConfig & { + driverModule?: () => Promise<{ default: () => Driver }>; +}; + export interface ViteUserConfig extends OriginalViteUserConfig { ssr?: ViteSSROptions; } diff --git a/packages/astro/src/vite-plugin-sessions/index.ts b/packages/astro/src/vite-plugin-sessions/index.ts deleted file mode 100644 index b2c27a4dd5..0000000000 --- a/packages/astro/src/vite-plugin-sessions/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -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 - // fsLite is identical except it doesn't include a filesystem watcher (which we don't need) - 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)};`; - } - }, - }; -}