0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-17 23:11:29 -05:00

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
This commit is contained in:
Matt Kane 2024-12-06 09:57:04 +00:00 committed by GitHub
parent 3549203866
commit ef8798f6cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 47 additions and 68 deletions

View file

@ -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> | AstroMiddlewareInstance;
checkOrigin: boolean;
envGetSecretEnabled: boolean;
sessionConfig?: SessionConfig<any>;
sessionConfig?: ResolvedSessionConfig<any>
};
export type SSRManifestI18n = {

View file

@ -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');
}
},

View file

@ -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

View file

@ -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<TDriver extends SessionDriverName = any> {
// The cookies object.
#cookies: AstroCookies;
// The session configuration.
#config: Omit<SessionConfig<TDriver>, 'cookie'>;
#config: Omit<ResolvedSessionConfig<TDriver>, 'cookie'>;
// The cookie config
#cookieConfig?: AstroCookieSetOptions;
// The cookie name
@ -41,7 +51,7 @@ export class AstroSession<TDriver extends SessionDriverName = any> {
{
cookie: cookieConfig = DEFAULT_COOKIE_NAME,
...config
}: Exclude<SessionConfig<TDriver>, undefined>,
}: Exclude<ResolvedSessionConfig<TDriver>, undefined>,
) {
this.#cookies = cookies;
if (typeof cookieConfig === 'object') {
@ -354,7 +364,7 @@ export class AstroSession<TDriver extends SessionDriverName = any> {
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<TDriver extends SessionDriverName = any> {
}
let driver: ((config: SessionConfig<TDriver>['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<TDriver extends SessionDriverName = any> {
}
}
}
// TODO: make this sync when we drop support for Node < 18.19.0
export function resolveSessionDriver(driver: string | undefined): Promise<string> | 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;
}

View file

@ -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<TDriver extends SessionDriverName> =
? TestSessionConfig
: CustomSessionConfig;
export type ResolvedSessionConfig<TDriver extends SessionDriverName> = SessionConfig<TDriver> & {
driverModule?: () => Promise<{ default: () => Driver }>;
};
export interface ViteUserConfig extends OriginalViteUserConfig {
ssr?: ViteSSROptions;
}

View file

@ -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)};`;
}
},
};
}