wip stable url
This commit is contained in:
parent
40efae6550
commit
dc1e82d622
16 changed files with 81 additions and 16 deletions
|
@ -107,6 +107,7 @@ export class App {
|
||||||
},
|
},
|
||||||
routeCache: new RouteCache(this.#logging),
|
routeCache: new RouteCache(this.#logging),
|
||||||
site: this.#manifest.site,
|
site: this.#manifest.site,
|
||||||
|
base: this.#manifest.base,
|
||||||
ssr: true,
|
ssr: true,
|
||||||
streaming,
|
streaming,
|
||||||
});
|
});
|
||||||
|
|
|
@ -526,6 +526,7 @@ async function generatePath(
|
||||||
},
|
},
|
||||||
routeCache,
|
routeCache,
|
||||||
site: manifest.site,
|
site: manifest.site,
|
||||||
|
base: manifest.base,
|
||||||
ssr,
|
ssr,
|
||||||
streaming: true,
|
streaming: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { AstroCookies, attachCookiesToResponse } from '../cookies/index.js';
|
||||||
import { AstroError, AstroErrorData } from '../errors/index.js';
|
import { AstroError, AstroErrorData } from '../errors/index.js';
|
||||||
import { warn } from '../logger/core.js';
|
import { warn } from '../logger/core.js';
|
||||||
import { callMiddleware } from '../middleware/callMiddleware.js';
|
import { callMiddleware } from '../middleware/callMiddleware.js';
|
||||||
|
import { createRouteUrl } from '../routing/url.js';
|
||||||
|
|
||||||
const clientAddressSymbol = Symbol.for('astro.clientAddress');
|
const clientAddressSymbol = Symbol.for('astro.clientAddress');
|
||||||
const clientLocalsSymbol = Symbol.for('astro.locals');
|
const clientLocalsSymbol = Symbol.for('astro.locals');
|
||||||
|
@ -31,6 +32,7 @@ type CreateAPIContext = {
|
||||||
request: Request;
|
request: Request;
|
||||||
params: Params;
|
params: Params;
|
||||||
site?: string;
|
site?: string;
|
||||||
|
routeUrl: URL;
|
||||||
props: Record<string, any>;
|
props: Record<string, any>;
|
||||||
adapterName?: string;
|
adapterName?: string;
|
||||||
};
|
};
|
||||||
|
@ -44,6 +46,7 @@ export function createAPIContext({
|
||||||
request,
|
request,
|
||||||
params,
|
params,
|
||||||
site,
|
site,
|
||||||
|
routeUrl,
|
||||||
props,
|
props,
|
||||||
adapterName,
|
adapterName,
|
||||||
}: CreateAPIContext): APIContext {
|
}: CreateAPIContext): APIContext {
|
||||||
|
@ -62,7 +65,7 @@ export function createAPIContext({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
url: new URL(request.url),
|
url: routeUrl,
|
||||||
get clientAddress() {
|
get clientAddress() {
|
||||||
if (!(clientAddressSymbol in request)) {
|
if (!(clientAddressSymbol in request)) {
|
||||||
if (adapterName) {
|
if (adapterName) {
|
||||||
|
@ -102,11 +105,18 @@ export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>
|
||||||
ctx: RenderContext,
|
ctx: RenderContext,
|
||||||
onRequest?: MiddlewareHandler<MiddlewareResult> | undefined
|
onRequest?: MiddlewareHandler<MiddlewareResult> | undefined
|
||||||
): Promise<EndpointCallResult> {
|
): Promise<EndpointCallResult> {
|
||||||
|
const routeUrl = createRouteUrl(ctx.route, {
|
||||||
|
params: ctx.params,
|
||||||
|
base: env.base,
|
||||||
|
site: env.site ?? new URL(ctx.request.url).origin,
|
||||||
|
});
|
||||||
|
|
||||||
const context = createAPIContext({
|
const context = createAPIContext({
|
||||||
request: ctx.request,
|
request: ctx.request,
|
||||||
params: ctx.params,
|
params: ctx.params,
|
||||||
props: ctx.props,
|
props: ctx.props,
|
||||||
site: env.site,
|
site: env.site,
|
||||||
|
routeUrl,
|
||||||
adapterName: env.adapterName,
|
adapterName: env.adapterName,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ function createContext({ request, params }: CreateContext) {
|
||||||
params: params ?? {},
|
params: params ?? {},
|
||||||
props: {},
|
props: {},
|
||||||
site: undefined,
|
site: undefined,
|
||||||
|
routeUrl: new URL(request.url)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import type {
|
||||||
} from '../@types/astro';
|
} from '../@types/astro';
|
||||||
import { callMiddleware } from './middleware/callMiddleware.js';
|
import { callMiddleware } from './middleware/callMiddleware.js';
|
||||||
import { renderPage } from './render/core.js';
|
import { renderPage } from './render/core.js';
|
||||||
|
import { createRouteUrl } from './routing/url.js';
|
||||||
|
|
||||||
type EndpointResultHandler = (
|
type EndpointResultHandler = (
|
||||||
originalRequest: Request,
|
originalRequest: Request,
|
||||||
|
@ -95,11 +96,18 @@ export class Pipeline {
|
||||||
mod: Readonly<ComponentInstance>,
|
mod: Readonly<ComponentInstance>,
|
||||||
onRequest?: MiddlewareHandler<MiddlewareReturnType>
|
onRequest?: MiddlewareHandler<MiddlewareReturnType>
|
||||||
): Promise<Response | EndpointCallResult> {
|
): Promise<Response | EndpointCallResult> {
|
||||||
|
const routeUrl = createRouteUrl(renderContext.route, {
|
||||||
|
params: renderContext.params,
|
||||||
|
base: env.base,
|
||||||
|
site: env.site ?? new URL(renderContext.request.url).origin,
|
||||||
|
});
|
||||||
|
|
||||||
const apiContext = createAPIContext({
|
const apiContext = createAPIContext({
|
||||||
request: renderContext.request,
|
request: renderContext.request,
|
||||||
params: renderContext.params,
|
params: renderContext.params,
|
||||||
props: renderContext.props,
|
props: renderContext.props,
|
||||||
site: env.site,
|
site: env.site,
|
||||||
|
routeUrl,
|
||||||
adapterName: env.adapterName,
|
adapterName: env.adapterName,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { attachCookiesToResponse } from '../cookies/index.js';
|
||||||
import { callEndpoint, createAPIContext, type EndpointCallResult } from '../endpoint/index.js';
|
import { callEndpoint, createAPIContext, type EndpointCallResult } from '../endpoint/index.js';
|
||||||
import { callMiddleware } from '../middleware/callMiddleware.js';
|
import { callMiddleware } from '../middleware/callMiddleware.js';
|
||||||
import { redirectRouteGenerate, redirectRouteStatus, routeIsRedirect } from '../redirects/index.js';
|
import { redirectRouteGenerate, redirectRouteStatus, routeIsRedirect } from '../redirects/index.js';
|
||||||
|
import { createRouteUrl } from '../routing/url.js';
|
||||||
import type { RenderContext } from './context.js';
|
import type { RenderContext } from './context.js';
|
||||||
import type { Environment } from './environment.js';
|
import type { Environment } from './environment.js';
|
||||||
import { createResult } from './result.js';
|
import { createResult } from './result.js';
|
||||||
|
@ -37,11 +38,18 @@ export async function renderPage({ mod, renderContext, env, cookies }: RenderPag
|
||||||
if (!Component)
|
if (!Component)
|
||||||
throw new Error(`Expected an exported Astro component but received typeof ${typeof Component}`);
|
throw new Error(`Expected an exported Astro component but received typeof ${typeof Component}`);
|
||||||
|
|
||||||
|
const routeUrl = createRouteUrl(renderContext.route, {
|
||||||
|
params: renderContext.params,
|
||||||
|
base: env.base,
|
||||||
|
site: env.site ?? new URL(renderContext.request.url).origin,
|
||||||
|
});
|
||||||
|
|
||||||
const result = createResult({
|
const result = createResult({
|
||||||
adapterName: env.adapterName,
|
adapterName: env.adapterName,
|
||||||
links: renderContext.links,
|
links: renderContext.links,
|
||||||
styles: renderContext.styles,
|
styles: renderContext.styles,
|
||||||
logging: env.logging,
|
logging: env.logging,
|
||||||
|
routeUrl,
|
||||||
params: renderContext.params,
|
params: renderContext.params,
|
||||||
pathname: renderContext.pathname,
|
pathname: renderContext.pathname,
|
||||||
componentMetadata: renderContext.componentMetadata,
|
componentMetadata: renderContext.componentMetadata,
|
||||||
|
@ -93,11 +101,18 @@ export async function tryRenderRoute<MiddlewareReturnType = Response>(
|
||||||
mod: Readonly<ComponentInstance>,
|
mod: Readonly<ComponentInstance>,
|
||||||
onRequest?: MiddlewareHandler<MiddlewareReturnType>
|
onRequest?: MiddlewareHandler<MiddlewareReturnType>
|
||||||
): Promise<Response | EndpointCallResult> {
|
): Promise<Response | EndpointCallResult> {
|
||||||
|
const routeUrl = createRouteUrl(renderContext.route, {
|
||||||
|
params: renderContext.params,
|
||||||
|
base: env.base,
|
||||||
|
site: env.site ?? new URL(renderContext.request.url).origin,
|
||||||
|
});
|
||||||
|
|
||||||
const apiContext = createAPIContext({
|
const apiContext = createAPIContext({
|
||||||
request: renderContext.request,
|
request: renderContext.request,
|
||||||
params: renderContext.params,
|
params: renderContext.params,
|
||||||
props: renderContext.props,
|
props: renderContext.props,
|
||||||
site: env.site,
|
site: env.site,
|
||||||
|
routeUrl,
|
||||||
adapterName: env.adapterName,
|
adapterName: env.adapterName,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,10 @@ export interface Environment {
|
||||||
* Used for `Astro.site`
|
* Used for `Astro.site`
|
||||||
*/
|
*/
|
||||||
site?: string;
|
site?: string;
|
||||||
|
/**
|
||||||
|
* Used to derive `Astro.url`
|
||||||
|
*/
|
||||||
|
base?: string;
|
||||||
/**
|
/**
|
||||||
* Value of Astro config's `output` option, true if "server" or "hybrid"
|
* Value of Astro config's `output` option, true if "server" or "hybrid"
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -36,6 +36,10 @@ export interface CreateResultArgs {
|
||||||
* Used for `Astro.site`
|
* Used for `Astro.site`
|
||||||
*/
|
*/
|
||||||
site: string | undefined;
|
site: string | undefined;
|
||||||
|
/**
|
||||||
|
* Used for `Astro.url`. A stable route usually derived from `RouteData`.
|
||||||
|
*/
|
||||||
|
routeUrl: URL;
|
||||||
links?: Set<SSRElement>;
|
links?: Set<SSRElement>;
|
||||||
scripts?: Set<SSRElement>;
|
scripts?: Set<SSRElement>;
|
||||||
styles?: Set<SSRElement>;
|
styles?: Set<SSRElement>;
|
||||||
|
@ -126,7 +130,6 @@ class Slots {
|
||||||
export function createResult(args: CreateResultArgs): SSRResult {
|
export function createResult(args: CreateResultArgs): SSRResult {
|
||||||
const { params, request, resolve, locals } = args;
|
const { params, request, resolve, locals } = args;
|
||||||
|
|
||||||
const url = new URL(request.url);
|
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
headers.set('Content-Type', 'text/html');
|
headers.set('Content-Type', 'text/html');
|
||||||
const response: ResponseInit = {
|
const response: ResponseInit = {
|
||||||
|
@ -195,7 +198,7 @@ export function createResult(args: CreateResultArgs): SSRResult {
|
||||||
props,
|
props,
|
||||||
locals,
|
locals,
|
||||||
request,
|
request,
|
||||||
url,
|
url: args.routeUrl,
|
||||||
redirect(path, status) {
|
redirect(path, status) {
|
||||||
// If the response is already sent, error as we cannot proceed with the redirect.
|
// If the response is already sent, error as we cannot proceed with the redirect.
|
||||||
if ((request as any)[responseSentSymbol]) {
|
if ((request as any)[responseSentSymbol]) {
|
||||||
|
|
|
@ -2,4 +2,5 @@ export { createRouteManifest } from './manifest/create.js';
|
||||||
export { deserializeRouteData, serializeRouteData } from './manifest/serialization.js';
|
export { deserializeRouteData, serializeRouteData } from './manifest/serialization.js';
|
||||||
export { matchAllRoutes, matchRoute } from './match.js';
|
export { matchAllRoutes, matchRoute } from './match.js';
|
||||||
export { getParams } from './params.js';
|
export { getParams } from './params.js';
|
||||||
|
export { createRouteUrl } from './url.js';
|
||||||
export { validateDynamicRouteModule, validateGetStaticPathsResult } from './validation.js';
|
export { validateDynamicRouteModule, validateGetStaticPathsResult } from './validation.js';
|
||||||
|
|
24
packages/astro/src/core/routing/url.ts
Normal file
24
packages/astro/src/core/routing/url.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import type { Params, RouteData } from '../../@types/astro.js';
|
||||||
|
import { joinPaths } from '../path.js';
|
||||||
|
|
||||||
|
interface CreateUrlOptions {
|
||||||
|
params?: Params;
|
||||||
|
site?: string;
|
||||||
|
base?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRouteUrl(route: RouteData, options: CreateUrlOptions) {
|
||||||
|
const site = options.site ?? 'http://localhost:4321';
|
||||||
|
const base = options.base ?? '/';
|
||||||
|
|
||||||
|
// Tests don't implement generate, do a dirty skip here
|
||||||
|
if (route.generate == null) {
|
||||||
|
return new URL(base, site);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathnameWithoutBase = route.generate(options.params);
|
||||||
|
// If the pathname is empty (root without trailing slash), return it as is so the final
|
||||||
|
// URL also doesn't have a trailing slash
|
||||||
|
const pathname = pathnameWithoutBase === '' ? '' : joinPaths(base, pathnameWithoutBase);
|
||||||
|
return new URL(pathname, site);
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ export function createDevelopmentEnvironment(
|
||||||
resolve: createResolve(loader, settings.config.root),
|
resolve: createResolve(loader, settings.config.root),
|
||||||
routeCache: new RouteCache(logging, mode),
|
routeCache: new RouteCache(logging, mode),
|
||||||
site: manifest.site,
|
site: manifest.site,
|
||||||
|
base: manifest.base,
|
||||||
ssr: isServerLikeOutput(settings.config),
|
ssr: isServerLikeOutput(settings.config),
|
||||||
streaming: true,
|
streaming: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,7 +29,7 @@ describe('getStaticPaths - build calls', () => {
|
||||||
const html = await fixture.readFile('/food/tacos/index.html');
|
const html = await fixture.readFile('/food/tacos/index.html');
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
expect($('#url').text()).to.equal('/blog/food/tacos/');
|
expect($('#url').text()).to.equal('/blog/food/tacos');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -24,16 +24,16 @@ describe('Astro Global', () => {
|
||||||
await devServer.stop();
|
await devServer.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Astro.request.url', async () => {
|
it('Astro.url', async () => {
|
||||||
const res = await await fixture.fetch('/blog/?foo=42');
|
const res = await await fixture.fetch('/blog/?foo=42');
|
||||||
expect(res.status).to.equal(200);
|
expect(res.status).to.equal(200);
|
||||||
|
|
||||||
const html = await res.text();
|
const html = await res.text();
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
expect($('#pathname').text()).to.equal('/blog/');
|
expect($('#pathname').text()).to.equal('/blog');
|
||||||
expect($('#searchparams').text()).to.equal('{}');
|
expect($('#searchparams').text()).to.equal('{}');
|
||||||
expect($('#child-pathname').text()).to.equal('/blog/');
|
expect($('#child-pathname').text()).to.equal('/blog');
|
||||||
expect($('#nested-child-pathname').text()).to.equal('/blog/');
|
expect($('#nested-child-pathname').text()).to.equal('/blog');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Astro.glob() returned `url` metadata of each markdown file extensions DOES NOT include the extension', async () => {
|
it('Astro.glob() returned `url` metadata of each markdown file extensions DOES NOT include the extension', async () => {
|
||||||
|
|
|
@ -38,7 +38,7 @@ describe('Prerender', () => {
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
expect($('#props').text()).to.equal('10');
|
expect($('#props').text()).to.equal('10');
|
||||||
expect($('#url').text()).to.equal('/blog/food/tacos/');
|
expect($('#url').text()).to.equal('/blog/food/tacos');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ describe('Prerender', () => {
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
expect($('#props').text()).to.equal('10');
|
expect($('#props').text()).to.equal('10');
|
||||||
expect($('#url').text()).to.equal('/blog/food/tacos/');
|
expect($('#url').text()).to.equal('/blog/food/tacos');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -50,8 +50,8 @@ describe('Using Astro.request in SSR', () => {
|
||||||
const html = await response.text();
|
const html = await response.text();
|
||||||
const $ = cheerioLoad(html);
|
const $ = cheerioLoad(html);
|
||||||
expect($('#origin').text()).to.equal('http://example.com');
|
expect($('#origin').text()).to.equal('http://example.com');
|
||||||
expect($('#pathname').text()).to.equal('/subpath/request/');
|
expect($('#pathname').text()).to.equal('/subpath/request');
|
||||||
expect($('#request-pathname').text()).to.equal('/subpath/request/');
|
expect($('#request-pathname').text()).to.equal('/subpath/request');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('public file is copied over', async () => {
|
it('public file is copied over', async () => {
|
||||||
|
|
|
@ -18263,25 +18263,21 @@ packages:
|
||||||
file:packages/astro/test/fixtures/css-assets/packages/font-awesome:
|
file:packages/astro/test/fixtures/css-assets/packages/font-awesome:
|
||||||
resolution: {directory: packages/astro/test/fixtures/css-assets/packages/font-awesome, type: directory}
|
resolution: {directory: packages/astro/test/fixtures/css-assets/packages/font-awesome, type: directory}
|
||||||
name: '@test/astro-font-awesome-package'
|
name: '@test/astro-font-awesome-package'
|
||||||
version: 0.0.1
|
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:packages/astro/test/fixtures/multiple-renderers/renderers/one:
|
file:packages/astro/test/fixtures/multiple-renderers/renderers/one:
|
||||||
resolution: {directory: packages/astro/test/fixtures/multiple-renderers/renderers/one, type: directory}
|
resolution: {directory: packages/astro/test/fixtures/multiple-renderers/renderers/one, type: directory}
|
||||||
name: '@test/astro-renderer-one'
|
name: '@test/astro-renderer-one'
|
||||||
version: 1.0.0
|
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:packages/astro/test/fixtures/multiple-renderers/renderers/two:
|
file:packages/astro/test/fixtures/multiple-renderers/renderers/two:
|
||||||
resolution: {directory: packages/astro/test/fixtures/multiple-renderers/renderers/two, type: directory}
|
resolution: {directory: packages/astro/test/fixtures/multiple-renderers/renderers/two, type: directory}
|
||||||
name: '@test/astro-renderer-two'
|
name: '@test/astro-renderer-two'
|
||||||
version: 1.0.0
|
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:packages/astro/test/fixtures/solid-component/deps/solid-jsx-component:
|
file:packages/astro/test/fixtures/solid-component/deps/solid-jsx-component:
|
||||||
resolution: {directory: packages/astro/test/fixtures/solid-component/deps/solid-jsx-component, type: directory}
|
resolution: {directory: packages/astro/test/fixtures/solid-component/deps/solid-jsx-component, type: directory}
|
||||||
name: '@test/solid-jsx-component'
|
name: '@test/solid-jsx-component'
|
||||||
version: 0.0.0
|
|
||||||
dependencies:
|
dependencies:
|
||||||
solid-js: 1.7.6
|
solid-js: 1.7.6
|
||||||
dev: false
|
dev: false
|
||||||
|
|
Loading…
Reference in a new issue