Skip to content

Commit fa73fbb

Browse files
authored
Fix build failure when using astro:config/client in a client script (#16014)
1 parent fdd2c5a commit fa73fbb

9 files changed

Lines changed: 126 additions & 37 deletions

File tree

.changeset/lucky-teams-play.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes a build error where using `astro:config/client` inside a `<script>` tag would cause Rollup to fail with "failed to resolve import `virtual:astro:routes` from `virtual:astro:manifest`"

packages/astro/src/core/create-vite.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export async function createVite(
136136
}),
137137
vitePluginStaticPaths(),
138138
await astroPluginRoutes({ routesList, settings, logger, fsMod: fs, command }),
139-
astroVirtualManifestPlugin(),
139+
astroVirtualManifestPlugin({ settings }),
140140
vitePluginEnvironment({ settings, astroPkgsConfig, command }),
141141
pluginPage({ routesList }),
142142
pluginPages({ routesList }),

packages/astro/src/manifest/serialized.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ export function serializedManifestPlugin({
7272
server.watcher.on('change', (path) => reloadManifest(path, server));
7373
},
7474

75+
// Restrict to server environments only since the generated code imports
76+
// server-only virtual modules (virtual:astro:routes, virtual:astro:pages)
77+
applyToEnvironment(environment) {
78+
return (
79+
environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.astro ||
80+
environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr ||
81+
environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender
82+
);
83+
},
84+
7585
resolveId: {
7686
filter: {
7787
id: new RegExp(`^${SERIALIZED_MANIFEST_ID}$`),

packages/astro/src/manifest/virtual-module.ts

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,59 @@ import type { Plugin } from 'vite';
22
import { AstroError, AstroErrorData } from '../core/errors/index.js';
33
import { SERIALIZED_MANIFEST_ID } from './serialized.js';
44
import { ASTRO_VITE_ENVIRONMENT_NAMES } from '../core/constants.js';
5+
import { fromRoutingStrategy, toFallbackType, toRoutingStrategy } from '../core/app/common.js';
6+
import type { AstroSettings } from '../types/astro.js';
57

68
const VIRTUAL_SERVER_ID = 'astro:config/server';
79
const RESOLVED_VIRTUAL_SERVER_ID = '\0' + VIRTUAL_SERVER_ID;
810
const VIRTUAL_CLIENT_ID = 'astro:config/client';
911
const RESOLVED_VIRTUAL_CLIENT_ID = '\0' + VIRTUAL_CLIENT_ID;
1012

11-
export default function virtualModulePlugin(): Plugin {
13+
export default function virtualModulePlugin({ settings }: { settings: AstroSettings }): Plugin {
14+
// Pre-compute the client config values from settings so that astro:config/client
15+
// doesn't need to import from virtual:astro:manifest (which pulls in server-only
16+
// virtual modules like virtual:astro:routes and virtual:astro:pages that are
17+
// restricted to server environments via applyToEnvironment).
18+
const config = settings.config;
19+
20+
let i18nCode = 'const i18n = undefined;';
21+
if (config.i18n) {
22+
// Apply the same toRoutingStrategy → fromRoutingStrategy roundtrip that the
23+
// serialized manifest uses, to ensure consistent routing config values.
24+
const strategy = toRoutingStrategy(config.i18n.routing, config.i18n.domains);
25+
const fallbackType = toFallbackType(config.i18n.routing);
26+
const routing = fromRoutingStrategy(strategy, fallbackType);
27+
i18nCode = `const i18n = {
28+
defaultLocale: ${JSON.stringify(config.i18n.defaultLocale)},
29+
locales: ${JSON.stringify(config.i18n.locales)},
30+
routing: ${JSON.stringify(routing)},
31+
fallback: ${JSON.stringify(config.i18n.fallback)}
32+
};`;
33+
}
34+
35+
let imageCode = 'const image = undefined;';
36+
if (config.image) {
37+
imageCode = `const image = {
38+
objectFit: ${JSON.stringify(config.image.objectFit)},
39+
objectPosition: ${JSON.stringify(config.image.objectPosition)},
40+
layout: ${JSON.stringify(config.image.layout)},
41+
};`;
42+
}
43+
44+
const clientConfigCode = `
45+
${i18nCode}
46+
${imageCode}
47+
const base = ${JSON.stringify(config.base)};
48+
const trailingSlash = ${JSON.stringify(config.trailingSlash)};
49+
const site = ${JSON.stringify(config.site)};
50+
const compressHTML = ${JSON.stringify(config.compressHTML)};
51+
const build = {
52+
format: ${JSON.stringify(config.build.format)},
53+
};
54+
55+
export { base, i18n, trailingSlash, site, compressHTML, build, image };
56+
`;
57+
1258
return {
1359
name: 'astro-manifest-plugin',
1460
resolveId: {
@@ -30,41 +76,11 @@ export default function virtualModulePlugin(): Plugin {
3076
},
3177
handler(id) {
3278
if (id === RESOLVED_VIRTUAL_CLIENT_ID) {
33-
// There's nothing wrong about using `/client` on the server
34-
const code = `
35-
import { manifest } from '${SERIALIZED_MANIFEST_ID}'
36-
import { fromRoutingStrategy } from 'astro/app';
37-
38-
let i18n = undefined;
39-
if (manifest.i18n) {
40-
i18n = {
41-
defaultLocale: manifest.i18n.defaultLocale,
42-
locales: manifest.i18n.locales,
43-
routing: fromRoutingStrategy(manifest.i18n.strategy, manifest.i18n.fallbackType),
44-
fallback: manifest.i18n.fallback
45-
};
46-
}
47-
48-
let image = undefined;
49-
if (manifest.image) {
50-
image = {
51-
objectFit: manifest.image.objectFit,
52-
objectPosition: manifest.image.objectPosition,
53-
layout: manifest.image.layout,
54-
};
55-
}
56-
57-
const base = manifest.base;
58-
const trailingSlash = manifest.trailingSlash;
59-
const site = manifest.site;
60-
const compressHTML = manifest.compressHTML;
61-
const build = {
62-
format: manifest.buildFormat,
63-
};
64-
65-
export { base, i18n, trailingSlash, site, compressHTML, build, image };
66-
`;
67-
return { code };
79+
// astro:config/client inlines values directly from settings instead of
80+
// importing from virtual:astro:manifest to avoid pulling server-only
81+
// virtual modules (virtual:astro:routes, virtual:astro:pages) into the
82+
// client environment where they are not available.
83+
return { code: clientConfigCode };
6884
}
6985
if (id === RESOLVED_VIRTUAL_SERVER_ID) {
7086
if (this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client) {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { defineConfig } from "astro/config";
2+
3+
// https://astro.build/config
4+
export default defineConfig({
5+
site: "https://example.com",
6+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "@test/astro-manifest-client-script",
3+
"version": "0.0.0",
4+
"private": true,
5+
"dependencies": {
6+
"astro": "workspace:*"
7+
}
8+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
---
3+
<!DOCTYPE html>
4+
<html lang="en">
5+
<head>
6+
<meta charset="UTF-8"/>
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
8+
<title>Document</title>
9+
</head>
10+
<body>
11+
<h1>Hello, World!</h1>
12+
<p id="base"></p>
13+
<script>
14+
import { base } from "astro:config/client";
15+
document.getElementById('base').textContent = base;
16+
</script>
17+
</body>
18+
</html>

packages/astro/test/serializeManifest.test.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,26 @@ describe('astro:config/client', () => {
8989
});
9090
});
9191

92+
describe('astro:config/client in a client script', () => {
93+
/** @type {import('./test-utils').Fixture} */
94+
let fixture;
95+
96+
describe('when build', () => {
97+
before(async () => {
98+
fixture = await loadFixture({
99+
root: './fixtures/astro-manifest-client-script/',
100+
adapter: testAdapter(),
101+
output: 'server',
102+
});
103+
});
104+
105+
it('should build without errors when astro:config/client is used in a client script', async () => {
106+
const error = await fixture.build().catch((err) => err);
107+
assert.equal(error, undefined, `Build failed with: ${error?.message}`);
108+
});
109+
});
110+
});
111+
92112
describe('astro:config/server', () => {
93113
/** @type {import('./test-utils').Fixture} */
94114
let fixture;

pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)