Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix turbopack _devPagesManifest.json on next-site #47427

Merged
merged 9 commits into from Mar 30, 2023
35 changes: 19 additions & 16 deletions packages/next-swc/crates/next-core/js/src/entry/router.ts
@@ -1,9 +1,9 @@
import type { Ipc } from "@vercel/turbopack-next/ipc/index";
import type { Ipc, StructuredError } from "@vercel/turbopack-next/ipc/index";
import type { IncomingMessage, ServerResponse } from "node:http";
import { Buffer } from "node:buffer";
import { createServer, makeRequest } from "@vercel/turbopack-next/ipc/server";
import { toPairs } from "@vercel/turbopack-next/internal/headers";
import { makeResolver } from "next/dist/server/lib/route-resolver";
import { makeResolver, RouteResult } from "next/dist/server/lib/route-resolver";
import loadConfig from "next/dist/server/config";
import { PHASE_DEVELOPMENT_SERVER } from "next/dist/shared/lib/constants";

Expand All @@ -19,16 +19,6 @@ type RouterRequest = {
rawQuery: string;
};

type RouteResult =
| {
type: "rewrite";
url: string;
headers: Record<string, string>;
}
| {
type: "none";
};

type IpcOutgoingMessage = {
type: "value";
data: string | Buffer;
Expand All @@ -41,6 +31,10 @@ type MessageData =
type: "rewrite";
data: RewriteResponse;
}
| {
type: "error";
error: StructuredError;
}
| { type: "none" };

type RewriteResponse = {
Expand Down Expand Up @@ -150,15 +144,24 @@ async function handleClientResponse(
return {
type: "none",
};
case "error":
return {
type: "error",
error: data.error,
};
case "rewrite":
default:
return {
type: "rewrite",
data: {
url: data.url,
headers: Object.entries(data.headers),
headers: Object.entries(data.headers)
.filter(([, val]) => val != null)
.map(([name, value]) => [name, value!.toString()]),
},
};
default:
// @ts-expect-error data.type is never
throw new Error(`unknown route result type: ${data.type}`);
}
}

Expand All @@ -167,7 +170,7 @@ async function handleClientResponse(
headers: toPairs(clientResponse.rawHeaders),
};

ipc.send({
await ipc.send({
type: "value",
data: JSON.stringify({
type: "middleware-headers",
Expand All @@ -176,7 +179,7 @@ async function handleClientResponse(
});

for await (const chunk of clientResponse) {
ipc.send({
await ipc.send({
type: "value",
data: JSON.stringify({
type: "middleware-body",
Expand Down
67 changes: 55 additions & 12 deletions packages/next-swc/crates/next-core/src/router.rs
Expand Up @@ -26,7 +26,7 @@ use turbo_binding::turbopack::ecmascript::{
use turbo_binding::turbopack::node::{
evaluate::evaluate,
execution_context::{ExecutionContext, ExecutionContextVc},
source_map::StructuredError,
source_map::{trace_stack, StructuredError},
};
use turbo_binding::turbopack::turbopack::{
evaluate_context::node_evaluate_asset_context, transition::TransitionsByNameVc,
Expand Down Expand Up @@ -111,7 +111,7 @@ enum RouterIncomingMessage {
MiddlewareHeaders { data: MiddlewareHeadersResponse },
MiddlewareBody { data: Vec<u8> },
None,
Error(StructuredError),
Error { error: StructuredError },
}

#[turbo_tasks::value(eq = "manual", cell = "new", serialization = "none")]
Expand All @@ -123,13 +123,13 @@ pub struct MiddlewareResponse {
pub body: Stream<Result<Bytes, SharedError>>,
}

#[derive(Debug)]
#[turbo_tasks::value(eq = "manual", cell = "new", serialization = "none")]
#[derive(Debug)]
pub enum RouterResult {
Rewrite(RewriteResponse),
Middleware(MiddlewareResponse),
None,
Error,
Error(#[turbo_tasks(trace_ignore)] SharedError),
}

#[turbo_tasks::function]
Expand Down Expand Up @@ -330,6 +330,18 @@ pub async fn route(
.await
}

macro_rules! shared_anyhow {
($msg:literal $(,)?) => {
turbo_tasks::util::SharedError::new(anyhow::anyhow!($msg))
};
($err:expr $(,)?) => {
turbo_tasks::util::SharedError::new(anyhow::anyhow!($err))
};
($fmt:expr, $($arg:tt)*) => {
turbo_tasks::util::SharedError::new(anyhow::anyhow!($fmt, $($arg)*))
};
}

#[turbo_tasks::function]
async fn route_internal(
execution_context: ExecutionContextVc,
Expand Down Expand Up @@ -385,10 +397,25 @@ async fn route_internal(

let mut read = result.read();

let Some(Ok(first)) = read.next().await else {
return Ok(RouterResult::Error.cell());
let first = match read.next().await {
Some(Ok(first)) => first,
Some(Err(e)) => {
return Ok(RouterResult::Error(SharedError::new(
anyhow!(e)
.context("router evaluation failed: received error from javascript stream"),
))
.cell())
}
None => {
return Ok(RouterResult::Error(shared_anyhow!(
"router evaluation failed: no message received from javascript stream"
))
.cell())
}
};
let first: RouterIncomingMessage = parse_json_with_source_context(first.to_str()?)?;
let first = first.to_str()?;
let first: RouterIncomingMessage = parse_json_with_source_context(first)
.with_context(|| format!("parsing incoming message ({})", first))?;

let (res, read) = match first {
RouterIncomingMessage::Rewrite { data } => (RouterResult::Rewrite(data), Some(read)),
Expand All @@ -404,10 +431,7 @@ async fn route_internal(
.and_then(parse_json_with_source_context)?;
match chunk {
RouterIncomingMessage::MiddlewareBody { data } => Ok(Bytes::from(data)),
m => Err(SharedError::new(anyhow!(
"unexpected message type: {:#?}",
m
))),
m => Err(shared_anyhow!("unexpected message type: {:#?}", m)),
}
});
let middleware = MiddlewareResponse {
Expand All @@ -420,7 +444,26 @@ async fn route_internal(
}

RouterIncomingMessage::None => (RouterResult::None, Some(read)),
_ => (RouterResult::Error, Some(read)),

RouterIncomingMessage::Error { error } => (
RouterResult::Error(shared_anyhow!(
trace_stack(
error,
router_asset,
chunking_context.output_root(),
project_path
)
.await?
)),
Some(read),
),

RouterIncomingMessage::MiddlewareBody { .. } => (
RouterResult::Error(shared_anyhow!(
"unexpected incoming middleware body without middleware headers"
)),
Some(read),
),
};

// Middleware will naturally drain the full stream, but the rest only take a
Expand Down
14 changes: 8 additions & 6 deletions packages/next-swc/crates/next-core/src/router_source.rs
@@ -1,4 +1,4 @@
use anyhow::{anyhow, bail, Context, Result};
use anyhow::{anyhow, Context, Result};
use futures::stream::StreamExt;
use indexmap::IndexSet;
use turbo_binding::turbopack::core::{
Expand Down Expand Up @@ -127,13 +127,15 @@ impl ContentSource for NextRouterContentSource {

let res = res
.await
.with_context(|| anyhow!("failed to fetch /{path}{}", formated_query(raw_query)))?;
.with_context(|| format!("failed to fetch /{path}{}", formated_query(raw_query)))?;

Ok(match &*res {
RouterResult::Error => bail!(
"error during Next.js routing for /{path}{}",
formated_query(raw_query)
),
RouterResult::Error(e) => {
return Err(anyhow!(e.clone()).context(format!(
"error during Next.js routing for /{path}{}: {e}",
formated_query(raw_query)
)))
}
RouterResult::None => this
.inner
.get(path, Value::new(ContentSourceData::default())),
Expand Down
4 changes: 3 additions & 1 deletion packages/next/src/server/base-http/index.ts
@@ -1,4 +1,4 @@
import type { IncomingHttpHeaders } from 'http'
import type { IncomingHttpHeaders, OutgoingHttpHeaders } from 'http'
import type { I18NConfig } from '../config-shared'

import { PERMANENT_REDIRECT_STATUS } from '../../shared/lib/constants'
Expand Down Expand Up @@ -55,6 +55,8 @@ export abstract class BaseNextResponse<Destination = any> {
*/
abstract getHeader(name: string): string | undefined

abstract getHeaders(): OutgoingHttpHeaders

abstract body(value: string): this

abstract send(): void
Expand Down
5 changes: 5 additions & 0 deletions packages/next/src/server/base-http/node.ts
Expand Up @@ -7,6 +7,7 @@ import { parseBody } from '../api-utils/node'
import { NEXT_REQUEST_META, RequestMeta } from '../request-meta'

import { BaseNextRequest, BaseNextResponse } from './index'
import { OutgoingHttpHeaders } from 'node:http'

type Req = IncomingMessage & {
[NEXT_REQUEST_META]?: RequestMeta
Expand Down Expand Up @@ -103,6 +104,10 @@ export class NodeNextResponse extends BaseNextResponse<Writable> {
return Array.isArray(values) ? values.join(',') : undefined
}

getHeaders(): OutgoingHttpHeaders {
return this._res.getHeaders()
}

appendHeader(name: string, value: string): this {
const currentValues = this.getHeaderValues(name) ?? []

Expand Down
6 changes: 5 additions & 1 deletion packages/next/src/server/base-http/web.ts
@@ -1,4 +1,4 @@
import type { IncomingHttpHeaders } from 'http'
import type { IncomingHttpHeaders, OutgoingHttpHeaders } from 'http'

import { BaseNextRequest, BaseNextResponse } from './index'

Expand Down Expand Up @@ -74,6 +74,10 @@ export class WebNextResponse extends BaseNextResponse<WritableStream> {
return this.headers.get(name) ?? undefined
}

getHeaders(): OutgoingHttpHeaders {
return Object.fromEntries(this.headers.entries())
}

hasHeader(name: string): boolean {
return this.headers.has(name)
}
Expand Down