From 87ea8abaee314225621d79400f943f98a49c1b85 Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Mon, 6 May 2024 16:00:15 +0200 Subject: [PATCH] feat(env): server/public variables (#10881) Co-authored-by: Bjorn Lu Co-authored-by: Emanuele Stoppa --- packages/astro/src/core/errors/errors-data.ts | 14 +++++- .../src/env/vite-plugin-env-virtual-mod.ts | 32 +++++++++--- .../astro-env-server-fail/astro.config.mjs | 12 +++++ .../astro-env-server-fail/package.json | 8 +++ .../src/pages/index.astro | 3 ++ .../astro-env-server-fail/tsconfig.json | 3 ++ .../test/fixtures/astro-env/astro.config.mjs | 1 + .../fixtures/astro-env/src/pages/index.astro | 5 +- .../astro/test/units/env/env-public.test.js | 50 +++++++++++++++++++ pnpm-lock.yaml | 6 +++ 10 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 packages/astro/test/fixtures/astro-env-server-fail/astro.config.mjs create mode 100644 packages/astro/test/fixtures/astro-env-server-fail/package.json create mode 100644 packages/astro/test/fixtures/astro-env-server-fail/src/pages/index.astro create mode 100644 packages/astro/test/fixtures/astro-env-server-fail/tsconfig.json create mode 100644 packages/astro/test/units/env/env-public.test.js diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts index d32ed14c1ea9..b7e8ad925ce6 100644 --- a/packages/astro/src/core/errors/errors-data.ts +++ b/packages/astro/src/core/errors/errors-data.ts @@ -1164,7 +1164,19 @@ export const i18nNotEnabled = { export const EnvInvalidVariables = { name: 'EnvInvalidVariable', title: 'Invalid Environment variable', - message: (variables: string) => `The following environment variable does not match the type and constraints defined in \`experimental.env.schema\`:\n\n${variables}\n`, + message: (variables: string) => + `The following environment variable does not match the type and constraints defined in \`experimental.env.schema\`:\n\n${variables}\n`, +} satisfies ErrorData; + +/** + * @docs + * @description + * Module is only available server-side + */ +export const EnvServerOnlyModule = { + name: 'EnvServerOnlyModule', + title: 'Module is only available server-side', + message: (name: string) => `The "${name}" module is only available server-side.`, } satisfies ErrorData; /** diff --git a/packages/astro/src/env/vite-plugin-env-virtual-mod.ts b/packages/astro/src/env/vite-plugin-env-virtual-mod.ts index f75ca390bbfc..5366600c3621 100644 --- a/packages/astro/src/env/vite-plugin-env-virtual-mod.ts +++ b/packages/astro/src/env/vite-plugin-env-virtual-mod.ts @@ -4,7 +4,9 @@ import type { Logger } from '../core/logger/core.js'; import { ENV_TYPES_FILE, RESOLVED_VIRTUAL_CLIENT_MODULE_ID, + RESOLVED_VIRTUAL_SERVER_MODULE_ID, VIRTUAL_CLIENT_MODULE_ID, + VIRTUAL_SERVER_MODULE_ID, } from './constants.js'; import type { EnvSchema } from './schema.js'; import { validateEnvVariable } from './validators.js'; @@ -29,7 +31,8 @@ export function astroEnvVirtualModPlugin({ return; } - let clientTemplates: ReturnType | null = null; + let clientTemplates: ReturnType | null = null; + let serverTemplates: ReturnType | null = null; logger.warn('env', 'This feature is experimental. TODO:'); @@ -47,8 +50,9 @@ export function astroEnvVirtualModPlugin({ '' ); const validatedVariables = validatePublicVariables({ schema, loadedEnv }); - clientTemplates = getClientTemplates({ validatedVariables }); - generateDts({ settings, fs, content: clientTemplates.dts }); + clientTemplates = getTemplates({ validatedVariables, context: 'client' }); + serverTemplates = getTemplates({ validatedVariables, context: 'server' }); + generateDts({ settings, fs, content: `${clientTemplates.dts}\n\n${serverTemplates.dts}` }); }, buildEnd() { clientTemplates = null; @@ -57,11 +61,23 @@ export function astroEnvVirtualModPlugin({ if (id === VIRTUAL_CLIENT_MODULE_ID) { return RESOLVED_VIRTUAL_CLIENT_MODULE_ID; } + if (id === VIRTUAL_SERVER_MODULE_ID) { + return RESOLVED_VIRTUAL_SERVER_MODULE_ID + } }, - load(id) { + load(id, options) { if (id === RESOLVED_VIRTUAL_CLIENT_MODULE_ID) { return clientTemplates!.content; } + if (id === RESOLVED_VIRTUAL_SERVER_MODULE_ID) { + if (options?.ssr) { + return serverTemplates!.content; + } + throw new AstroError({ + ...AstroErrorData.EnvServerOnlyModule, + message: AstroErrorData.EnvServerOnlyModule.message(VIRTUAL_SERVER_MODULE_ID), + }); + } }, }; } @@ -111,22 +127,24 @@ function validatePublicVariables({ return valid; } -function getClientTemplates({ +function getTemplates({ validatedVariables, + context, }: { validatedVariables: ReturnType; + context: 'server' | 'client'; }) { const contentParts: Array = []; const dtsParts: Array = []; - for (const { key, type, value } of validatedVariables.filter((e) => e.context === 'client')) { + for (const { key, type, value } of validatedVariables.filter((e) => e.context === context)) { contentParts.push(`export const ${key} = ${JSON.stringify(value)};`); dtsParts.push(`export const ${key}: ${type};`); } const content = contentParts.join('\n'); - const dts = `declare module "astro:env/client" { + const dts = `declare module "astro:env/${context}" { ${dtsParts.join('\n ')} }`; diff --git a/packages/astro/test/fixtures/astro-env-server-fail/astro.config.mjs b/packages/astro/test/fixtures/astro-env-server-fail/astro.config.mjs new file mode 100644 index 000000000000..b3a8a98202a6 --- /dev/null +++ b/packages/astro/test/fixtures/astro-env-server-fail/astro.config.mjs @@ -0,0 +1,12 @@ +import { defineConfig, envField } from 'astro/config'; + +// https://astro.build/config +export default defineConfig({ + experimental: { + env: { + schema: { + PUBLIC_FOO: envField.string({ context: "server", access: "public", optional: true, default: "ABC" }), + } + } + } +}); diff --git a/packages/astro/test/fixtures/astro-env-server-fail/package.json b/packages/astro/test/fixtures/astro-env-server-fail/package.json new file mode 100644 index 000000000000..e37f684f8568 --- /dev/null +++ b/packages/astro/test/fixtures/astro-env-server-fail/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/astro-env-server-fail", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/astro-env-server-fail/src/pages/index.astro b/packages/astro/test/fixtures/astro-env-server-fail/src/pages/index.astro new file mode 100644 index 000000000000..be3d43c366af --- /dev/null +++ b/packages/astro/test/fixtures/astro-env-server-fail/src/pages/index.astro @@ -0,0 +1,3 @@ + diff --git a/packages/astro/test/fixtures/astro-env-server-fail/tsconfig.json b/packages/astro/test/fixtures/astro-env-server-fail/tsconfig.json new file mode 100644 index 000000000000..d78f81ec4e8e --- /dev/null +++ b/packages/astro/test/fixtures/astro-env-server-fail/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "astro/tsconfigs/base" +} diff --git a/packages/astro/test/fixtures/astro-env/astro.config.mjs b/packages/astro/test/fixtures/astro-env/astro.config.mjs index 2be42f0f5cbc..a7e3c9ca9938 100644 --- a/packages/astro/test/fixtures/astro-env/astro.config.mjs +++ b/packages/astro/test/fixtures/astro-env/astro.config.mjs @@ -7,6 +7,7 @@ export default defineConfig({ schema: { PUBLIC_FOO: envField.string({ context: "client", access: "public", optional: true, default: "ABC" }), PUBLIC_BAR: envField.string({ context: "client", access: "public", optional: true, default: "DEF" }), + PUBLIC_BAZ: envField.string({ context: "server", access: "public", optional: true, default: "GHI" }), } } } diff --git a/packages/astro/test/fixtures/astro-env/src/pages/index.astro b/packages/astro/test/fixtures/astro-env/src/pages/index.astro index c3ea5b0ed7b6..4ea369a1f640 100644 --- a/packages/astro/test/fixtures/astro-env/src/pages/index.astro +++ b/packages/astro/test/fixtures/astro-env/src/pages/index.astro @@ -1,5 +1,8 @@ --- import { PUBLIC_FOO } from "astro:env/client" +import { PUBLIC_BAZ } from "astro:env/server" + +console.log({ PUBLIC_BAZ }) ---
{PUBLIC_FOO}
@@ -9,4 +12,4 @@ import { PUBLIC_FOO } from "astro:env/client" import { PUBLIC_BAR } from "astro:env/client" document.getElementById("client-rendered").innerText = PUBLIC_BAR - \ No newline at end of file + diff --git a/packages/astro/test/units/env/env-public.test.js b/packages/astro/test/units/env/env-public.test.js new file mode 100644 index 000000000000..9f31a747dada --- /dev/null +++ b/packages/astro/test/units/env/env-public.test.js @@ -0,0 +1,50 @@ +import assert from 'node:assert/strict'; +import { before, describe, it } from 'node:test'; +import { loadFixture } from '../../test-utils.js'; +import { EnvServerOnlyModule } from '../../../dist/core/errors/errors-data.js'; +import { AstroError } from '../../../dist/core/errors/errors.js'; + +describe('astro:env public variables', () => { + /** @type {Awaited>} */ + let fixture; + + describe('Client variables', () => { + before(async () => { + fixture = await loadFixture({ + root: './fixtures/astro-env/', + }); + await fixture.build(); + }); + + it('builds without throwing', async () => { + assert.equal(true, true); + }); + + it('includes client/public env in build', async () => { + let indexHtml = await fixture.readFile('/index.html'); + + assert.equal(indexHtml.includes('ABC'), true); + assert.equal(indexHtml.includes('DEF'), true); + }); + + it('does not include server/public env in build', async () => { + let indexHtml = await fixture.readFile('/index.html'); + + assert.equal(indexHtml.includes('GHI'), false); + }); + }); + + describe('Server variables', () => { + before(async () => { + fixture = await loadFixture({ + root: './fixtures/astro-env-server-fail/', + }); + }); + + it('throws if server module is called on the client', async () => { + const error = await fixture.build().catch(err => err); + assert.equal(error instanceof AstroError, true) + assert.equal(error.name, EnvServerOnlyModule.name); + }); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed2b0dd90371..d9c32a1db716 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2024,6 +2024,12 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/astro-env-server-fail: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/test/fixtures/astro-envs: dependencies: '@astrojs/vue':