Compare commits

...

1 commit

Author SHA1 Message Date
bluwy
dc1e82d622 wip stable url 2023-08-14 21:15:02 +08:00
16 changed files with 81 additions and 16 deletions

View file

@ -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,
}); });

View file

@ -526,6 +526,7 @@ async function generatePath(
}, },
routeCache, routeCache,
site: manifest.site, site: manifest.site,
base: manifest.base,
ssr, ssr,
streaming: true, streaming: true,
}); });

View file

@ -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,
}); });

View file

@ -29,6 +29,7 @@ function createContext({ request, params }: CreateContext) {
params: params ?? {}, params: params ?? {},
props: {}, props: {},
site: undefined, site: undefined,
routeUrl: new URL(request.url)
}); });
} }

View file

@ -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,
}); });

View file

@ -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,
}); });

View file

@ -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"
*/ */

View file

@ -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]) {

View file

@ -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';

View 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);
}

View file

@ -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,
}); });

View file

@ -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');
}); });
}); });

View file

@ -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 () => {

View file

@ -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');
}); });
}); });

View file

@ -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 () => {

View file

@ -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