Skip to content

Commit 2726e7c

Browse files
chrskerrbenmccanndummdidummRich-Harris
authored
feat: allow access to public env in app.html (#8449)
* allow access to public env in app.html * Update .changeset/spotty-points-battle.md Co-authored-by: Simon H <[email protected]> * Update .changeset/spotty-points-battle.md Co-authored-by: Simon H <[email protected]> * updated to match recent changes * don't silently fail when encountering private env vars * replace vars at run time, not build time * remove <p> as it was interfering with other tests * reduce diff * reduce diff * tweak docs Co-authored-by: Ben McCann <[email protected]> Co-authored-by: Simon H <[email protected]> Co-authored-by: Rich Harris <[email protected]> Co-authored-by: Rich Harris <[email protected]>
1 parent a7136c3 commit 2726e7c

File tree

11 files changed

+58
-24
lines changed

11 files changed

+58
-24
lines changed

.changeset/spotty-points-battle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': minor
3+
---
4+
5+
feat: enable access to public env within app.html

documentation/docs/10-getting-started/30-project-structure.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ The `src` directory contains the meat of your project.
4545
- `%sveltekit.body%` — the markup for a rendered page. This should live inside a `<div>` or other element, rather than directly inside `<body>`, to prevent bugs caused by browser extensions injecting elements that are then destroyed by the hydration process. SvelteKit will warn you in development if this is not the case
4646
- `%sveltekit.assets%` — either [`paths.assets`](/docs/configuration#paths), if specified, or a relative path to [`paths.base`](/docs/configuration#paths)
4747
- `%sveltekit.nonce%` — a [CSP](/docs/configuration#csp) nonce for manually included links and scripts, if used
48+
- `%sveltekit.env.[NAME]%` - this will be replaced at render time with the `[NAME]` environment variable, which must begin with the [`publicPrefix`](https://kit.svelte.dev/docs/configuration#env) (usually `PUBLIC_`). It will fallback to `''` if not matched.
4849
- `error.html` (optional) is the page that is rendered when everything else fails. It can contain the following placeholders:
4950
- `%sveltekit.status%` — the HTTP status
5051
- `%sveltekit.error.message%` — the error message

packages/kit/src/core/config/index.js

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,33 @@ import options from './options.js';
99
* @param {string} cwd
1010
* @param {import('types').ValidatedConfig} config
1111
*/
12-
export function load_template(cwd, config) {
13-
const { appTemplate } = config.kit.files;
14-
const relative = path.relative(cwd, appTemplate);
15-
16-
if (fs.existsSync(appTemplate)) {
17-
const contents = fs.readFileSync(appTemplate, 'utf8');
18-
19-
const expected_tags = ['%sveltekit.head%', '%sveltekit.body%'];
20-
expected_tags.forEach((tag) => {
21-
if (contents.indexOf(tag) === -1) {
22-
throw new Error(`${relative} is missing ${tag}`);
23-
}
24-
});
25-
} else {
12+
export function load_template(cwd, { kit }) {
13+
const { env, files } = kit;
14+
15+
const relative = path.relative(cwd, files.appTemplate);
16+
17+
if (!fs.existsSync(files.appTemplate)) {
2618
throw new Error(`${relative} does not exist`);
2719
}
2820

29-
return fs.readFileSync(appTemplate, 'utf-8');
21+
const contents = fs.readFileSync(files.appTemplate, 'utf8');
22+
23+
const expected_tags = ['%sveltekit.head%', '%sveltekit.body%'];
24+
expected_tags.forEach((tag) => {
25+
if (contents.indexOf(tag) === -1) {
26+
throw new Error(`${relative} is missing ${tag}`);
27+
}
28+
});
29+
30+
for (const match of contents.matchAll(/%sveltekit\.env\.([^%]+)%/g)) {
31+
if (!match[1].startsWith(env.publicPrefix)) {
32+
throw new Error(
33+
`Environment variables in ${relative} must start with ${env.publicPrefix} (saw %sveltekit.env.${match[1]}%)`
34+
);
35+
}
36+
}
37+
38+
return contents;
3039
}
3140

3241
/**

packages/kit/src/core/sync/write_server.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,15 @@ export const options = {
3838
root,
3939
service_worker: ${has_service_worker},
4040
templates: {
41-
app: ({ head, body, assets, nonce }) => ${s(template)
41+
app: ({ head, body, assets, nonce, env }) => ${s(template)
4242
.replace('%sveltekit.head%', '" + head + "')
4343
.replace('%sveltekit.body%', '" + body + "')
4444
.replace(/%sveltekit\.assets%/g, '" + assets + "')
45-
.replace(/%sveltekit\.nonce%/g, '" + nonce + "')},
45+
.replace(/%sveltekit\.nonce%/g, '" + nonce + "')
46+
.replace(
47+
/%sveltekit\.env\.([^%]+)%/g,
48+
(_match, capture) => `" + (env[${s(capture)}] ?? "") + "`
49+
)},
4650
error: ({ status, message }) => ${s(error_page)
4751
.replace(/%sveltekit\.status%/g, '" + status + "')
4852
.replace(/%sveltekit\.error\.message%/g, '" + message + "')}

packages/kit/src/exports/vite/dev/index.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ export async function dev(vite, vite_config, svelte_config) {
7373
} catch (error) {
7474
manifest_error = /** @type {Error} */ (error);
7575

76-
console.error(colors.bold().red('Invalid routes'));
77-
console.error(error);
76+
console.error(colors.bold().red(manifest_error.message));
7877
vite.ws.send({
7978
type: 'error',
8079
err: {
@@ -444,8 +443,7 @@ export async function dev(vite, vite_config, svelte_config) {
444443
}
445444

446445
if (manifest_error) {
447-
console.error(colors.bold().red('Invalid routes'));
448-
console.error(manifest_error);
446+
console.error(colors.bold().red(manifest_error.message));
449447

450448
const error_page = load_error_page(svelte_config);
451449

packages/kit/src/runtime/env-public.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** @type {Record<string, string>} */
12
export let env = {};
23

34
/** @type {(environment: Record<string, string>) => void} */

packages/kit/src/runtime/server/page/render.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,8 @@ export async function render_response({
366366
head,
367367
body,
368368
assets: resolved_assets,
369-
nonce: /** @type {string} */ (csp.nonce)
369+
nonce: /** @type {string} */ (csp.nonce),
370+
env
370371
});
371372

372373
// TODO flush chunks as early as we can

packages/kit/test/apps/basics/.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ PRIVATE_DYNAMIC="accessible to server-side code/evaluated at run time"
33

44
PUBLIC_STATIC="accessible anywhere/replaced at build time"
55
PUBLIC_DYNAMIC="accessible anywhere/evaluated at run time"
6+
7+
PUBLIC_THEME="groovy"

packages/kit/test/apps/basics/src/app.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<meta name="transform-page" content="__REPLACEME__" />
88
%sveltekit.head%
99
</head>
10-
<body>
10+
<body class="%sveltekit.env.PUBLIC_THEME%">
1111
<div>%sveltekit.body%</div>
1212
<a href="/routing/link-outside-app-target/target">outside app target</a>
1313
</body>

packages/kit/test/apps/basics/test/client.test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,3 +600,10 @@ test.describe('Content negotiation', () => {
600600
await expect(page.locator('[data-testid="form-result"]')).toHaveText('form.submitted: true');
601601
});
602602
});
603+
604+
test.describe('env in app.html', () => {
605+
test('can access public env', async ({ page }) => {
606+
await page.goto('/');
607+
expect(await page.locator('body').getAttribute('class')).toContain('groovy');
608+
});
609+
});

0 commit comments

Comments
 (0)