Skip to content

Commit

Permalink
feat: allow access to public env in app.html (#8449)
Browse files Browse the repository at this point in the history
* allow access to public env in app.html

* Update .changeset/spotty-points-battle.md

Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>

* Update .changeset/spotty-points-battle.md

Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>

* 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 <322311+benmccann@users.noreply.github.com>
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Co-authored-by: Rich Harris <richard.a.harris@gmail.com>
Co-authored-by: Rich Harris <hello@rich-harris.dev>
  • Loading branch information
5 people authored Jan 19, 2023
1 parent a7136c3 commit 2726e7c
Show file tree
Hide file tree
Showing 11 changed files with 58 additions and 24 deletions.
5 changes: 5 additions & 0 deletions .changeset/spotty-points-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': minor
---

feat: enable access to public env within app.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ The `src` directory contains the meat of your project.
- `%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
- `%sveltekit.assets%` — either [`paths.assets`](/docs/configuration#paths), if specified, or a relative path to [`paths.base`](/docs/configuration#paths)
- `%sveltekit.nonce%` — a [CSP](/docs/configuration#csp) nonce for manually included links and scripts, if used
- `%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.
- `error.html` (optional) is the page that is rendered when everything else fails. It can contain the following placeholders:
- `%sveltekit.status%` — the HTTP status
- `%sveltekit.error.message%` — the error message
Expand Down
39 changes: 24 additions & 15 deletions packages/kit/src/core/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,33 @@ import options from './options.js';
* @param {string} cwd
* @param {import('types').ValidatedConfig} config
*/
export function load_template(cwd, config) {
const { appTemplate } = config.kit.files;
const relative = path.relative(cwd, appTemplate);

if (fs.existsSync(appTemplate)) {
const contents = fs.readFileSync(appTemplate, 'utf8');

const expected_tags = ['%sveltekit.head%', '%sveltekit.body%'];
expected_tags.forEach((tag) => {
if (contents.indexOf(tag) === -1) {
throw new Error(`${relative} is missing ${tag}`);
}
});
} else {
export function load_template(cwd, { kit }) {
const { env, files } = kit;

const relative = path.relative(cwd, files.appTemplate);

if (!fs.existsSync(files.appTemplate)) {
throw new Error(`${relative} does not exist`);
}

return fs.readFileSync(appTemplate, 'utf-8');
const contents = fs.readFileSync(files.appTemplate, 'utf8');

const expected_tags = ['%sveltekit.head%', '%sveltekit.body%'];
expected_tags.forEach((tag) => {
if (contents.indexOf(tag) === -1) {
throw new Error(`${relative} is missing ${tag}`);
}
});

for (const match of contents.matchAll(/%sveltekit\.env\.([^%]+)%/g)) {
if (!match[1].startsWith(env.publicPrefix)) {
throw new Error(
`Environment variables in ${relative} must start with ${env.publicPrefix} (saw %sveltekit.env.${match[1]}%)`
);
}
}

return contents;
}

/**
Expand Down
8 changes: 6 additions & 2 deletions packages/kit/src/core/sync/write_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,15 @@ export const options = {
root,
service_worker: ${has_service_worker},
templates: {
app: ({ head, body, assets, nonce }) => ${s(template)
app: ({ head, body, assets, nonce, env }) => ${s(template)
.replace('%sveltekit.head%', '" + head + "')
.replace('%sveltekit.body%', '" + body + "')
.replace(/%sveltekit\.assets%/g, '" + assets + "')
.replace(/%sveltekit\.nonce%/g, '" + nonce + "')},
.replace(/%sveltekit\.nonce%/g, '" + nonce + "')
.replace(
/%sveltekit\.env\.([^%]+)%/g,
(_match, capture) => `" + (env[${s(capture)}] ?? "") + "`
)},
error: ({ status, message }) => ${s(error_page)
.replace(/%sveltekit\.status%/g, '" + status + "')
.replace(/%sveltekit\.error\.message%/g, '" + message + "')}
Expand Down
6 changes: 2 additions & 4 deletions packages/kit/src/exports/vite/dev/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ export async function dev(vite, vite_config, svelte_config) {
} catch (error) {
manifest_error = /** @type {Error} */ (error);

console.error(colors.bold().red('Invalid routes'));
console.error(error);
console.error(colors.bold().red(manifest_error.message));
vite.ws.send({
type: 'error',
err: {
Expand Down Expand Up @@ -444,8 +443,7 @@ export async function dev(vite, vite_config, svelte_config) {
}

if (manifest_error) {
console.error(colors.bold().red('Invalid routes'));
console.error(manifest_error);
console.error(colors.bold().red(manifest_error.message));

const error_page = load_error_page(svelte_config);

Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/runtime/env-public.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** @type {Record<string, string>} */
export let env = {};

/** @type {(environment: Record<string, string>) => void} */
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/src/runtime/server/page/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,8 @@ export async function render_response({
head,
body,
assets: resolved_assets,
nonce: /** @type {string} */ (csp.nonce)
nonce: /** @type {string} */ (csp.nonce),
env
});

// TODO flush chunks as early as we can
Expand Down
2 changes: 2 additions & 0 deletions packages/kit/test/apps/basics/.env
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ PRIVATE_DYNAMIC="accessible to server-side code/evaluated at run time"

PUBLIC_STATIC="accessible anywhere/replaced at build time"
PUBLIC_DYNAMIC="accessible anywhere/evaluated at run time"

PUBLIC_THEME="groovy"
2 changes: 1 addition & 1 deletion packages/kit/test/apps/basics/src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<meta name="transform-page" content="__REPLACEME__" />
%sveltekit.head%
</head>
<body>
<body class="%sveltekit.env.PUBLIC_THEME%">
<div>%sveltekit.body%</div>
<a href="/routing/link-outside-app-target/target">outside app target</a>
</body>
Expand Down
7 changes: 7 additions & 0 deletions packages/kit/test/apps/basics/test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -600,3 +600,10 @@ test.describe('Content negotiation', () => {
await expect(page.locator('[data-testid="form-result"]')).toHaveText('form.submitted: true');
});
});

test.describe('env in app.html', () => {
test('can access public env', async ({ page }) => {
await page.goto('/');
expect(await page.locator('body').getAttribute('class')).toContain('groovy');
});
});
8 changes: 7 additions & 1 deletion packages/kit/types/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,13 @@ export interface SSROptions {
root: SSRComponent['default'];
service_worker: boolean;
templates: {
app(values: { head: string; body: string; assets: string; nonce: string }): string;
app(values: {
head: string;
body: string;
assets: string;
nonce: string;
env: Record<string, string>;
}): string;
error(values: { message: string; status: number }): string;
};
}
Expand Down

0 comments on commit 2726e7c

Please sign in to comment.