diff --git a/.changeset/wet-beds-act.md b/.changeset/wet-beds-act.md new file mode 100644 index 000000000000..da9367a9d9d2 --- /dev/null +++ b/.changeset/wet-beds-act.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Updates component hydration scripts to use absolute paths for script imports diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index a53c1c06239a..c630e3de3dcb 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -18,7 +18,7 @@ import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet, } from '../render/ssr-element.js'; -import { prependForwardSlash } from '../path.js'; +import { joinPaths, prependForwardSlash } from '../path.js'; export const pagesVirtualModuleId = '@astrojs-pages-virtual-entry'; export const resolvedPagesVirtualModuleId = '\0' + pagesVirtualModuleId; @@ -108,7 +108,7 @@ export class App { throw new Error(`Unable to resolve [${specifier}]`); } const bundlePath = manifest.entryModules[specifier]; - return bundlePath.startsWith('data:') ? bundlePath : prependForwardSlash(bundlePath); + return bundlePath.startsWith('data:') ? bundlePath : prependForwardSlash(joinPaths(manifest.base, bundlePath)); }, route: routeData, routeCache: this.#routeCache, diff --git a/packages/astro/src/core/app/types.ts b/packages/astro/src/core/app/types.ts index 063f33b7fc3c..6c6e879d5a17 100644 --- a/packages/astro/src/core/app/types.ts +++ b/packages/astro/src/core/app/types.ts @@ -22,6 +22,7 @@ export type SerializedRouteInfo = Omit & { export interface SSRManifest { routes: RouteInfo[]; site?: string; + base?: string; markdown: MarkdownRenderingOptions; pageMap: Map; renderers: SSRLoadedRenderer[]; diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index d68f8faf30d4..d0dec38be079 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -219,9 +219,7 @@ async function generatePath( } throw new Error(`Cannot find the built path for ${specifier}`); } - const relPath = npath.posix.relative(pathname, '/' + hashedFilePath); - const fullyRelativePath = relPath[0] === '.' ? relPath : './' + relPath; - return fullyRelativePath; + return prependForwardSlash(npath.posix.join(astroConfig.base, hashedFilePath)); }, request: createRequest({ url, headers: new Headers(), logging, ssr }), route: pageData.route, diff --git a/packages/astro/src/core/build/vite-plugin-ssr.ts b/packages/astro/src/core/build/vite-plugin-ssr.ts index 35d95e6a2617..2b2915ff0cc7 100644 --- a/packages/astro/src/core/build/vite-plugin-ssr.ts +++ b/packages/astro/src/core/build/vite-plugin-ssr.ts @@ -136,6 +136,7 @@ function buildManifest( const ssrManifest: SerializedSSRManifest = { routes, site: astroConfig.site, + base: astroConfig.base, markdown: astroConfig.markdown, pageMap: null as any, renderers: [], diff --git a/packages/astro/test/astro-client-only.test.js b/packages/astro/test/astro-client-only.test.js index 5e2cc6ce85a3..527932a01e88 100644 --- a/packages/astro/test/astro-client-only.test.js +++ b/packages/astro/test/astro-client-only.test.js @@ -22,7 +22,39 @@ describe('Client only components', () => { const script = $script.html(); // test 2: svelte renderer is on the page - expect(/import\(".\/entry.*/g.test(script)).to.be.ok; + expect(/import\("\/entry.*/g.test(script)).to.be.ok; + }); + + it('Adds the CSS to the page', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerioLoad(html); + expect($('link[rel=stylesheet]')).to.have.lengthOf(2); + }); +}); + +describe('Client only components subpath', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ + site: 'https://site.com', + base: '/blog', + root: './fixtures/astro-client-only/', + }); + await fixture.build(); + }); + + it('Loads pages using client:only hydrator', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerioLoad(html); + + // test 1: is empty + expect($('astro-root').html()).to.equal(''); + const $script = $('script'); + const script = $script.html(); + + // test 2: svelte renderer is on the page + expect(/import\("\/blog\/entry.*/g.test(script)).to.be.ok; }); it('Adds the CSS to the page', async () => { diff --git a/packages/astro/test/astro-dynamic.test.js b/packages/astro/test/astro-dynamic.test.js index 1b8b323ee6a9..9e90f073af89 100644 --- a/packages/astro/test/astro-dynamic.test.js +++ b/packages/astro/test/astro-dynamic.test.js @@ -37,6 +37,47 @@ describe('Dynamic components', () => { // test 2: correct script is being loaded. // because of bundling, we don't have access to the source import, // only the bundled import. - expect($('script').html()).to.include(`import setup from '../entry`); + expect($('script').html()).to.include(`import setup from '/entry`); + }); +}); + +describe('Dynamic components subpath', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ + site: 'https://site.com', + base: '/blog', + root: './fixtures/astro-dynamic/', + }); + await fixture.build(); + }); + + it('Loads packages that only run code in client', async () => { + const html = await fixture.readFile('/index.html'); + + const $ = cheerio.load(html); + expect($('script').length).to.eq(2); + }); + + it('Loads pages using client:media hydrator', async () => { + const root = new URL('http://example.com/media/index.html'); + const html = await fixture.readFile('/media/index.html'); + const $ = cheerio.load(html); + + // test 1: static value rendered + expect($('script').length).to.equal(2); // One for each + }); + + it('Loads pages using client:only hydrator', async () => { + const html = await fixture.readFile('/client-only/index.html'); + const $ = cheerio.load(html); + + // test 1: is empty. + expect($('').html()).to.equal(''); + // test 2: correct script is being loaded. + // because of bundling, we don't have access to the source import, + // only the bundled import. + expect($('script').html()).to.include(`import setup from '/blog/entry`); }); });