Skip to content

Commit

Permalink
feat(env): server/public variables (#10881)
Browse files Browse the repository at this point in the history
Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com>
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
  • Loading branch information
3 people committed May 6, 2024
1 parent eec5403 commit 87ea8ab
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 9 deletions.
14 changes: 13 additions & 1 deletion packages/astro/src/core/errors/errors-data.ts
Expand Up @@ -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;

/**
Expand Down
32 changes: 25 additions & 7 deletions packages/astro/src/env/vite-plugin-env-virtual-mod.ts
Expand Up @@ -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';
Expand All @@ -29,7 +31,8 @@ export function astroEnvVirtualModPlugin({
return;
}

let clientTemplates: ReturnType<typeof getClientTemplates> | null = null;
let clientTemplates: ReturnType<typeof getTemplates> | null = null;
let serverTemplates: ReturnType<typeof getTemplates> | null = null;

logger.warn('env', 'This feature is experimental. TODO:');

Expand All @@ -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;
Expand All @@ -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),
});
}
},
};
}
Expand Down Expand Up @@ -111,22 +127,24 @@ function validatePublicVariables({
return valid;
}

function getClientTemplates({
function getTemplates({
validatedVariables,
context,
}: {
validatedVariables: ReturnType<typeof validatePublicVariables>;
context: 'server' | 'client';
}) {
const contentParts: Array<string> = [];
const dtsParts: Array<string> = [];

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

Expand Down
@@ -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" }),
}
}
}
});
@@ -0,0 +1,8 @@
{
"name": "@test/astro-env-server-fail",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}
@@ -0,0 +1,3 @@
<script>
import { PUBLIC_FOO } from "astro:env/server"
</script>
@@ -0,0 +1,3 @@
{
"extends": "astro/tsconfigs/base"
}
1 change: 1 addition & 0 deletions packages/astro/test/fixtures/astro-env/astro.config.mjs
Expand Up @@ -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" }),
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion 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 })
---

<div id="server-rendered">{PUBLIC_FOO}</div>
Expand All @@ -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
</script>
</script>
50 changes: 50 additions & 0 deletions 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<ReturnType<typeof loadFixture>>} */
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);
});
});
});
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 87ea8ab

Please sign in to comment.