diff --git a/.changeset/cool-rings-tell.md b/.changeset/cool-rings-tell.md new file mode 100644 index 000000000000..11c2fd981272 --- /dev/null +++ b/.changeset/cool-rings-tell.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes a case where accessing `astro:env` APIs or `import.meta.env` inside the content config file would not work diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index cad0dbc3c356..1431c698e858 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -14,6 +14,7 @@ import { } from '../content/index.js'; import { createEnvLoader } from '../env/env-loader.js'; import { astroEnv } from '../env/vite-plugin-env.js'; +import { importMetaEnv } from '../env/vite-plugin-import-meta-env.js'; import astroInternationalization from '../i18n/vite-plugin-i18n.js'; import astroPrefetch from '../prefetch/vite-plugin-prefetch.js'; import astroDevToolbar from '../toolbar/vite-plugin-dev-toolbar.js'; @@ -23,7 +24,6 @@ import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.j import { vitePluginAstroServer } from '../vite-plugin-astro-server/index.js'; import astroVitePlugin from '../vite-plugin-astro/index.js'; import configAliasVitePlugin from '../vite-plugin-config-alias/index.js'; -import envVitePlugin from '../vite-plugin-env/index.js'; import vitePluginFileURL from '../vite-plugin-fileurl/index.js'; import astroHeadPlugin from '../vite-plugin-head/index.js'; import astroHmrReloadPlugin from '../vite-plugin-hmr-reload/index.js'; @@ -124,7 +124,7 @@ export async function createVite( }); const srcDirPattern = glob.convertPathToPattern(fileURLToPath(settings.config.srcDir)); - const envLoader = createEnvLoader(); + const envLoader = createEnvLoader(mode, settings.config); // Start with the Vite configuration that Astro core needs const commonConfig: vite.InlineConfig = { @@ -148,8 +148,8 @@ export async function createVite( // 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 - envVitePlugin({ envLoader }), - astroEnv({ settings, mode, sync, envLoader }), + importMetaEnv({ envLoader }), + astroEnv({ settings, sync, envLoader }), markdownVitePlugin({ settings, logger }), htmlVitePlugin(), astroPostprocessVitePlugin(), diff --git a/packages/astro/src/vite-plugin-env/README.md b/packages/astro/src/env/README.md similarity index 79% rename from packages/astro/src/vite-plugin-env/README.md rename to packages/astro/src/env/README.md index 305cff024240..349ca140ffa3 100644 --- a/packages/astro/src/vite-plugin-env/README.md +++ b/packages/astro/src/env/README.md @@ -1,4 +1,8 @@ -# vite-plugin-env +# env + +The content of this directory is for `astro:env` features, except for `vite-plugin-import-meta-env.ts`. + +# vite-plugin-import-meta-env Improves Vite's [Env Variables](https://vite.dev/guide/env-and-mode.html#env-files) support to include **private** env variables during Server-Side Rendering (SSR) but never in client-side rendering (CSR). diff --git a/packages/astro/src/env/env-loader.ts b/packages/astro/src/env/env-loader.ts index d3893c6f901d..02f396652339 100644 --- a/packages/astro/src/env/env-loader.ts +++ b/packages/astro/src/env/env-loader.ts @@ -43,17 +43,12 @@ function getPrivateEnv( return privateEnv; } -export const createEnvLoader = () => { - let privateEnv: Record = {}; +export const createEnvLoader = (mode: string, config: AstroConfig) => { + const loaded = loadEnv(mode, config.vite.envDir ?? fileURLToPath(config.root), ''); + const privateEnv = getPrivateEnv(loaded, config); return { - load: (mode: string, config: AstroConfig) => { - const loaded = loadEnv(mode, config.vite.envDir ?? fileURLToPath(config.root), ''); - privateEnv = getPrivateEnv(loaded, config); - return loaded; - }, - getPrivateEnv: () => { - return privateEnv; - }, + get: () => loaded, + getPrivateEnv: () => privateEnv, }; }; diff --git a/packages/astro/src/env/vite-plugin-env.ts b/packages/astro/src/env/vite-plugin-env.ts index 1d028392430a..e01d8f3d6abc 100644 --- a/packages/astro/src/env/vite-plugin-env.ts +++ b/packages/astro/src/env/vite-plugin-env.ts @@ -14,17 +14,44 @@ import { getEnvFieldType, validateEnvVariable } from './validators.js'; interface AstroEnvPluginParams { settings: AstroSettings; - mode: string; sync: boolean; envLoader: EnvLoader; } -export function astroEnv({ settings, mode, sync, envLoader }: AstroEnvPluginParams): Plugin { +export function astroEnv({ settings, sync, envLoader }: AstroEnvPluginParams): Plugin { const { schema, validateSecrets } = settings.config.env; let isDev: boolean; let templates: { client: string; server: string; internal: string } | null = null; + function ensureTemplateAreLoaded() { + if (templates !== null) { + return; + } + + const loadedEnv = envLoader.get(); + + if (!isDev) { + for (const [key, value] of Object.entries(loadedEnv)) { + if (value !== undefined) { + process.env[key] = value; + } + } + } + + const validatedVariables = validatePublicVariables({ + schema, + loadedEnv, + validateSecrets, + sync, + }); + + templates = { + ...getTemplates(schema, validatedVariables, isDev ? loadedEnv : null), + internal: `export const schema = ${JSON.stringify(schema)};`, + }; + } + return { name: 'astro-env-plugin', enforce: 'pre', @@ -32,27 +59,7 @@ export function astroEnv({ settings, mode, sync, envLoader }: AstroEnvPluginPara isDev = command !== 'build'; }, buildStart() { - const loadedEnv = envLoader.load(mode, settings.config); - - if (!isDev) { - for (const [key, value] of Object.entries(loadedEnv)) { - if (value !== undefined) { - process.env[key] = value; - } - } - } - - const validatedVariables = validatePublicVariables({ - schema, - loadedEnv, - validateSecrets, - sync, - }); - - templates = { - ...getTemplates(schema, validatedVariables, isDev ? loadedEnv : null), - internal: `export const schema = ${JSON.stringify(schema)};`, - }; + ensureTemplateAreLoaded(); }, buildEnd() { templates = null; @@ -64,10 +71,12 @@ export function astroEnv({ settings, mode, sync, envLoader }: AstroEnvPluginPara }, load(id, options) { if (id === resolveVirtualModuleId(VIRTUAL_MODULES_IDS.client)) { + ensureTemplateAreLoaded(); return templates!.client; } if (id === resolveVirtualModuleId(VIRTUAL_MODULES_IDS.server)) { if (options?.ssr) { + ensureTemplateAreLoaded(); return templates!.server; } throw new AstroError({ @@ -76,6 +85,7 @@ export function astroEnv({ settings, mode, sync, envLoader }: AstroEnvPluginPara }); } if (id === resolveVirtualModuleId(VIRTUAL_MODULES_IDS.internal)) { + ensureTemplateAreLoaded(); return templates!.internal; } }, diff --git a/packages/astro/src/vite-plugin-env/index.ts b/packages/astro/src/env/vite-plugin-import-meta-env.ts similarity index 97% rename from packages/astro/src/vite-plugin-env/index.ts rename to packages/astro/src/env/vite-plugin-import-meta-env.ts index 76d295089fc7..0740f7cb4498 100644 --- a/packages/astro/src/vite-plugin-env/index.ts +++ b/packages/astro/src/env/vite-plugin-import-meta-env.ts @@ -1,7 +1,7 @@ import { transform } from 'esbuild'; import MagicString from 'magic-string'; import type * as vite from 'vite'; -import type { EnvLoader } from '../env/env-loader.js'; +import type { EnvLoader } from './env-loader.js'; interface EnvPluginOptions { envLoader: EnvLoader; @@ -65,7 +65,7 @@ async function replaceDefine( }; } -export default function envVitePlugin({ envLoader }: EnvPluginOptions): vite.Plugin { +export function importMetaEnv({ envLoader }: EnvPluginOptions): vite.Plugin { let privateEnv: Record; let defaultDefines: Record; let isDev: boolean; diff --git a/packages/astro/test/astro-sync.test.js b/packages/astro/test/astro-sync.test.js index e9ab90cf7569..3e5a9bcbf330 100644 --- a/packages/astro/test/astro-sync.test.js +++ b/packages/astro/test/astro-sync.test.js @@ -209,10 +209,15 @@ describe('astro sync', () => { assert.fail(); } }); - it('Does not throw if a virtual module is imported in content.config.ts', async () => { + it('Does not throw if a virtual module is imported in content.config.ts or import.meta.env is not loaded', async () => { try { await fixture.load('./fixtures/astro-env-content-collections/'); fixture.clean(); + fs.writeFileSync( + new URL('./fixtures/astro-env-content-collections/.env', import.meta.url), + 'BAR=abc', + 'utf-8', + ); await fixture.whenSyncing(); assert.ok(true); } catch { diff --git a/packages/astro/test/fixtures/astro-env-content-collections/src/content.config.ts b/packages/astro/test/fixtures/astro-env-content-collections/src/content.config.ts index 0fc4439357be..3276a6789e71 100644 --- a/packages/astro/test/fixtures/astro-env-content-collections/src/content.config.ts +++ b/packages/astro/test/fixtures/astro-env-content-collections/src/content.config.ts @@ -1,11 +1,14 @@ import { defineCollection, z } from "astro:content"; import { FOO } from "astro:env/client" -console.log({ FOO }) +console.log({ FOO, BAR: import.meta.env.BAR }) export const collections = { foo: defineCollection({ - type: "data", + loader: () => [{ + id: 'x', + title: import.meta.env.BAR + }], schema: z.object({ title: z.string() })