Skip to content

Commit 2ba0db5

Browse files
fix(dev): inject of scripts with non-runnable pipelines (#15811)
Co-authored-by: astrobot-houston <fred+astrobot@astro.build>
1 parent 01db4f3 commit 2ba0db5

3 files changed

Lines changed: 137 additions & 2 deletions

File tree

.changeset/hip-wings-tie.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes integration-injected scripts (e.g. Alpine.js via `injectScript()`) not being loaded in the dev server when using non-runnable environment adapters like `@astrojs/cloudflare`.
6+

packages/astro/src/vite-plugin-routes/index.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { createDefaultAstroMetadata } from '../vite-plugin-astro/metadata.js';
1616
import type { PluginMetadata } from '../vite-plugin-astro/types.js';
1717
import { ASTRO_VITE_ENVIRONMENT_NAMES } from '../core/constants.js';
1818
import { isAstroServerEnvironment } from '../environments.js';
19+
import { PAGE_SCRIPT_ID } from '../vite-plugin-scripts/index.js';
1920

2021
type Payload = {
2122
settings: AstroSettings;
@@ -30,6 +31,32 @@ const ASTRO_ROUTES_MODULE_ID_RESOLVED = '\0' + ASTRO_ROUTES_MODULE_ID;
3031

3132
const KNOWN_FILE_EXTENSIONS = ['.astro', '.js', '.ts'];
3233

34+
/**
35+
* In dev mode, populate route scripts with integration-injected scripts from settings.
36+
* This ensures non-runnable environments (e.g. Cloudflare's workerd) can access
37+
* scripts injected via `injectScript()` during `astro:config:setup`.
38+
*/
39+
export function getDevRouteScripts(
40+
command: 'dev' | 'build',
41+
scripts: AstroSettings['scripts'],
42+
): SerializedRouteInfo['scripts'] {
43+
if (command !== 'dev') return [];
44+
const result: SerializedRouteInfo['scripts'] = [];
45+
const hasPageScripts = scripts.some((s) => s.stage === 'page');
46+
if (hasPageScripts) {
47+
result.push({
48+
type: 'external',
49+
value: `/@id/${PAGE_SCRIPT_ID}`,
50+
});
51+
}
52+
for (const script of scripts) {
53+
if (script.stage === 'head-inline') {
54+
result.push({ stage: script.stage, children: script.content });
55+
}
56+
}
57+
return result;
58+
}
59+
3360
export default async function astroPluginRoutes({
3461
settings,
3562
logger,
@@ -44,7 +71,7 @@ export default async function astroPluginRoutes({
4471
return {
4572
file: '',
4673
links: [],
47-
scripts: [],
74+
scripts: getDevRouteScripts(command, settings.scripts),
4875
styles: [],
4976
routeData: serializeRouteData(r, settings.config.trailingSlash),
5077
};
@@ -77,7 +104,7 @@ export default async function astroPluginRoutes({
77104
return {
78105
file: fileURLToPath(file),
79106
links: [],
80-
scripts: [],
107+
scripts: getDevRouteScripts(command, settings.scripts),
81108
styles: [],
82109
routeData: serializeRouteData(r, settings.config.trailingSlash),
83110
};
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import assert from 'node:assert/strict';
2+
import { describe, it } from 'node:test';
3+
import { getDevRouteScripts } from '../dist/vite-plugin-routes/index.js';
4+
5+
describe('getDevRouteScripts', () => {
6+
it('returns empty array when command is build', () => {
7+
const scripts = [{ stage: 'page', content: 'console.log("page")' }];
8+
const result = getDevRouteScripts('build', scripts);
9+
assert.deepEqual(result, []);
10+
});
11+
12+
it('returns empty array when no scripts are provided in dev mode', () => {
13+
const result = getDevRouteScripts('dev', []);
14+
assert.deepEqual(result, []);
15+
});
16+
17+
it('includes external page script entry when page-stage scripts exist', () => {
18+
const scripts = [{ stage: 'page', content: 'import "alpinejs"' }];
19+
const result = getDevRouteScripts('dev', scripts);
20+
21+
assert.equal(result.length, 1);
22+
assert.deepEqual(result[0], {
23+
type: 'external',
24+
value: '/@id/astro:scripts/page.js',
25+
});
26+
});
27+
28+
it('collapses multiple page scripts into a single external entry', () => {
29+
const scripts = [
30+
{ stage: 'page', content: 'import "alpinejs"' },
31+
{ stage: 'page', content: 'import "other"' },
32+
];
33+
const result = getDevRouteScripts('dev', scripts);
34+
35+
const pageEntries = result.filter((s) => 'type' in s && s.type === 'external');
36+
assert.equal(pageEntries.length, 1);
37+
});
38+
39+
it('includes head-inline scripts with their content', () => {
40+
const scripts = [{ stage: 'head-inline', content: 'console.log("inline")' }];
41+
const result = getDevRouteScripts('dev', scripts);
42+
43+
assert.equal(result.length, 1);
44+
assert.deepEqual(result[0], {
45+
stage: 'head-inline',
46+
children: 'console.log("inline")',
47+
});
48+
});
49+
50+
it('includes both page and head-inline scripts together', () => {
51+
const scripts = [
52+
{ stage: 'page', content: 'import "alpinejs"' },
53+
{ stage: 'head-inline', content: 'console.log("inline1")' },
54+
{ stage: 'head-inline', content: 'console.log("inline2")' },
55+
];
56+
const result = getDevRouteScripts('dev', scripts);
57+
58+
assert.equal(result.length, 3);
59+
assert.deepEqual(result[0], {
60+
type: 'external',
61+
value: '/@id/astro:scripts/page.js',
62+
});
63+
assert.deepEqual(result[1], {
64+
stage: 'head-inline',
65+
children: 'console.log("inline1")',
66+
});
67+
assert.deepEqual(result[2], {
68+
stage: 'head-inline',
69+
children: 'console.log("inline2")',
70+
});
71+
});
72+
73+
it('ignores before-hydration and page-ssr stage scripts', () => {
74+
const scripts = [
75+
{ stage: 'before-hydration', content: 'console.log("hydration")' },
76+
{ stage: 'page-ssr', content: 'console.log("ssr")' },
77+
];
78+
const result = getDevRouteScripts('dev', scripts);
79+
80+
assert.deepEqual(result, []);
81+
});
82+
83+
it('ignores non-relevant stages while still collecting page and head-inline', () => {
84+
const scripts = [
85+
{ stage: 'before-hydration', content: 'console.log("hydration")' },
86+
{ stage: 'page', content: 'import "alpinejs"' },
87+
{ stage: 'page-ssr', content: 'console.log("ssr")' },
88+
{ stage: 'head-inline', content: 'window.__config = {}' },
89+
];
90+
const result = getDevRouteScripts('dev', scripts);
91+
92+
assert.equal(result.length, 2);
93+
assert.deepEqual(result[0], {
94+
type: 'external',
95+
value: '/@id/astro:scripts/page.js',
96+
});
97+
assert.deepEqual(result[1], {
98+
stage: 'head-inline',
99+
children: 'window.__config = {}',
100+
});
101+
});
102+
});

0 commit comments

Comments
 (0)