mirror of
https://github.com/withastro/astro.git
synced 2025-03-10 23:01:26 -05:00
feat: reroute for SSR
This commit is contained in:
parent
8da194d3d6
commit
b9265f85d7
4 changed files with 163 additions and 54 deletions
|
@ -96,7 +96,7 @@ export class App {
|
|||
routes: manifest.routes.map((route) => route.routeData),
|
||||
});
|
||||
this.#baseWithoutTrailingSlash = removeTrailingForwardSlash(this.#manifest.base);
|
||||
this.#pipeline = this.#createPipeline(streaming);
|
||||
this.#pipeline = this.#createPipeline(this.#manifestData, streaming);
|
||||
this.#adapterLogger = new AstroIntegrationLogger(
|
||||
this.#logger.options,
|
||||
this.#manifest.adapterName
|
||||
|
@ -110,10 +110,11 @@ export class App {
|
|||
/**
|
||||
* Creates a pipeline by reading the stored manifest
|
||||
*
|
||||
* @param manifestData
|
||||
* @param streaming
|
||||
* @private
|
||||
*/
|
||||
#createPipeline(streaming = false) {
|
||||
#createPipeline(manifestData: ManifestData, streaming = false) {
|
||||
if (this.#manifest.checkOrigin) {
|
||||
this.#manifest.middleware = sequence(
|
||||
createOriginCheckMiddleware(),
|
||||
|
@ -121,7 +122,7 @@ export class App {
|
|||
);
|
||||
}
|
||||
|
||||
return AppPipeline.create({
|
||||
return AppPipeline.create(manifestData, {
|
||||
logger: this.#logger,
|
||||
manifest: this.#manifest,
|
||||
mode: 'production',
|
||||
|
@ -309,7 +310,7 @@ export class App {
|
|||
}
|
||||
const pathname = this.#getPathnameFromRequest(request);
|
||||
const defaultStatus = this.#getDefaultStatusCode(routeData, pathname);
|
||||
const mod = await this.#getModuleForRoute(routeData);
|
||||
const mod = await this.#pipeline.getModuleForRoute(routeData);
|
||||
|
||||
let response;
|
||||
try {
|
||||
|
@ -405,7 +406,7 @@ export class App {
|
|||
|
||||
return this.#mergeResponses(response, originalResponse, override);
|
||||
}
|
||||
const mod = await this.#getModuleForRoute(errorRouteData);
|
||||
const mod = await this.#pipeline.getModuleForRoute(errorRouteData);
|
||||
try {
|
||||
const renderContext = RenderContext.create({
|
||||
locals,
|
||||
|
@ -493,35 +494,4 @@ export class App {
|
|||
if (route.endsWith('/500')) return 500;
|
||||
return 200;
|
||||
}
|
||||
|
||||
async #getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule> {
|
||||
if (route.component === DEFAULT_404_COMPONENT) {
|
||||
return {
|
||||
page: async () =>
|
||||
({ default: () => new Response(null, { status: 404 }) }) as ComponentInstance,
|
||||
renderers: [],
|
||||
};
|
||||
}
|
||||
if (route.type === 'redirect') {
|
||||
return RedirectSinglePageBuiltModule;
|
||||
} else {
|
||||
if (this.#manifest.pageMap) {
|
||||
const importComponentInstance = this.#manifest.pageMap.get(route.component);
|
||||
if (!importComponentInstance) {
|
||||
throw new Error(
|
||||
`Unexpectedly unable to find a component instance for route ${route.route}`
|
||||
);
|
||||
}
|
||||
const pageModule = await importComponentInstance();
|
||||
return pageModule;
|
||||
} else if (this.#manifest.pageModule) {
|
||||
const importComponentInstance = this.#manifest.pageModule;
|
||||
return importComponentInstance;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Astro couldn't find the correct page to render, probably because it wasn't correctly mapped for SSR usage. This is an internal error, please file an issue."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type {
|
||||
ComponentInstance,
|
||||
ManifestData,
|
||||
ReroutePayload,
|
||||
RouteData,
|
||||
SSRElement,
|
||||
|
@ -7,21 +8,39 @@ import type {
|
|||
} from '../../@types/astro.js';
|
||||
import { Pipeline } from '../base-pipeline.js';
|
||||
import { createModuleScriptElement, createStylesheetElementSet } from '../render/ssr-element.js';
|
||||
import type { SinglePageBuiltModule } from '../build/types.js';
|
||||
import { DEFAULT_404_COMPONENT } from '../constants.js';
|
||||
import { RedirectSinglePageBuiltModule } from '../redirects/index.js';
|
||||
|
||||
export class AppPipeline extends Pipeline {
|
||||
static create({
|
||||
logger,
|
||||
manifest,
|
||||
mode,
|
||||
renderers,
|
||||
resolve,
|
||||
serverLike,
|
||||
streaming,
|
||||
}: Pick<
|
||||
AppPipeline,
|
||||
'logger' | 'manifest' | 'mode' | 'renderers' | 'resolve' | 'serverLike' | 'streaming'
|
||||
>) {
|
||||
return new AppPipeline(logger, manifest, mode, renderers, resolve, serverLike, streaming);
|
||||
#manifestData: ManifestData | undefined;
|
||||
|
||||
static create(
|
||||
manifestData: ManifestData,
|
||||
{
|
||||
logger,
|
||||
manifest,
|
||||
mode,
|
||||
renderers,
|
||||
resolve,
|
||||
serverLike,
|
||||
streaming,
|
||||
}: Pick<
|
||||
AppPipeline,
|
||||
'logger' | 'manifest' | 'mode' | 'renderers' | 'resolve' | 'serverLike' | 'streaming'
|
||||
>
|
||||
) {
|
||||
const pipeline = new AppPipeline(
|
||||
logger,
|
||||
manifest,
|
||||
mode,
|
||||
renderers,
|
||||
resolve,
|
||||
serverLike,
|
||||
streaming
|
||||
);
|
||||
pipeline.#manifestData = manifestData;
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
headElements(routeData: RouteData): Pick<SSRResult, 'scripts' | 'styles' | 'links'> {
|
||||
|
@ -47,11 +66,69 @@ export class AppPipeline extends Pipeline {
|
|||
}
|
||||
|
||||
componentMetadata() {}
|
||||
getComponentByRoute(_routeData: RouteData): Promise<ComponentInstance> {
|
||||
throw new Error('unimplemented');
|
||||
async getComponentByRoute(routeData: RouteData): Promise<ComponentInstance> {
|
||||
const module = await this.getModuleForRoute(routeData);
|
||||
return module.page();
|
||||
}
|
||||
|
||||
tryReroute(_reroutePayload: ReroutePayload): Promise<[RouteData, ComponentInstance]> {
|
||||
throw new Error('unimplemented');
|
||||
async tryReroute(payload: ReroutePayload): Promise<[RouteData, ComponentInstance]> {
|
||||
let foundRoute;
|
||||
|
||||
for (const route of this.#manifestData!.routes) {
|
||||
if (payload instanceof URL) {
|
||||
if (route.pattern.test(payload.pathname)) {
|
||||
foundRoute = route;
|
||||
break;
|
||||
}
|
||||
} else if (payload instanceof Request) {
|
||||
const url = new URL(payload.url);
|
||||
if (route.pattern.test(url.pathname)) {
|
||||
foundRoute = route;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (route.pattern.test(decodeURI(payload))) {
|
||||
foundRoute = route;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundRoute) {
|
||||
const componentInstance = await this.getComponentByRoute(foundRoute);
|
||||
return [foundRoute, componentInstance];
|
||||
} else {
|
||||
// TODO: handle error properly
|
||||
throw new Error('Route not found');
|
||||
}
|
||||
}
|
||||
|
||||
async getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule> {
|
||||
if (route.component === DEFAULT_404_COMPONENT) {
|
||||
return {
|
||||
page: async () =>
|
||||
({ default: () => new Response(null, { status: 404 }) }) as ComponentInstance,
|
||||
renderers: [],
|
||||
};
|
||||
}
|
||||
if (route.type === 'redirect') {
|
||||
return RedirectSinglePageBuiltModule;
|
||||
} else {
|
||||
if (this.manifest.pageMap) {
|
||||
const importComponentInstance = this.manifest.pageMap.get(route.component);
|
||||
if (!importComponentInstance) {
|
||||
throw new Error(
|
||||
`Unexpectedly unable to find a component instance for route ${route.route}`
|
||||
);
|
||||
}
|
||||
return await importComponentInstance();
|
||||
} else if (this.manifest.pageModule) {
|
||||
return this.manifest.pageModule;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Astro couldn't find the correct page to render, probably because it wasn't correctly mapped for SSR usage. This is an internal error, please file an issue."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -220,7 +220,6 @@ export class DevPipeline extends Pipeline {
|
|||
const componentInstance = await this.getComponentByRoute(foundRoute);
|
||||
return [foundRoute, componentInstance];
|
||||
} else {
|
||||
// TODO: handle error properly
|
||||
throw new Error('Route not found');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { describe, it, before, after } from 'node:test';
|
|||
import { loadFixture } from './test-utils.js';
|
||||
import { load as cheerioLoad } from 'cheerio';
|
||||
import assert from 'node:assert/strict';
|
||||
import testAdapter from './test-adapter.js';
|
||||
|
||||
describe('Dev reroute', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
|
@ -165,3 +166,65 @@ describe.only('Build reroute', () => {
|
|||
assert.equal($('h1').text(), 'Index');
|
||||
});
|
||||
});
|
||||
|
||||
describe('SSR reroute', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
let app;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/reroute/',
|
||||
output: 'server',
|
||||
adapter: testAdapter(),
|
||||
});
|
||||
await fixture.build();
|
||||
app = await fixture.loadTestAdapterApp();
|
||||
});
|
||||
|
||||
it('the render the index page when navigating /reroute ', async () => {
|
||||
const request = new Request('http://example.com/reroute');
|
||||
const response = await app.render(request);
|
||||
const html = await response.text();
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
assert.equal($('h1').text(), 'Index');
|
||||
});
|
||||
|
||||
it('the render the index page when navigating /blog/hello ', async () => {
|
||||
const request = new Request('http://example.com/blog/hello');
|
||||
const response = await app.render(request);
|
||||
const html = await response.text();
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
assert.equal($('h1').text(), 'Index');
|
||||
});
|
||||
|
||||
it('the render the index page when navigating /blog/salut ', async () => {
|
||||
const request = new Request('http://example.com/blog/salut');
|
||||
const response = await app.render(request);
|
||||
const html = await response.text();
|
||||
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
assert.equal($('h1').text(), 'Index');
|
||||
});
|
||||
|
||||
it('the render the index page when navigating dynamic route /dynamic/[id] ', async () => {
|
||||
const request = new Request('http://example.com/dynamic/hello');
|
||||
const response = await app.render(request);
|
||||
const html = await response.text();
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
assert.equal($('h1').text(), 'Index');
|
||||
});
|
||||
|
||||
it('the render the index page when navigating spread route /spread/[...spread] ', async () => {
|
||||
const request = new Request('http://example.com/spread/hello');
|
||||
const response = await app.render(request);
|
||||
const html = await response.text();
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
assert.equal($('h1').text(), 'Index');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue