Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions crates/next-core/src/next_server/transforms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ use crate::{
next_config::NextConfig,
next_server::context::ServerContextType,
next_shared::transforms::{
get_next_dynamic_transform_rule, get_next_font_transform_rule, get_next_image_rule,
get_next_lint_transform_rule, get_next_modularize_imports_rule,
get_next_pages_transforms_rule, get_next_track_dynamic_imports_transform_rule,
get_server_actions_transform_rule, next_cjs_optimizer::get_next_cjs_optimizer_rule,
get_next_debug_instant_stack_rule, get_next_dynamic_transform_rule,
get_next_font_transform_rule, get_next_image_rule, get_next_lint_transform_rule,
get_next_modularize_imports_rule, get_next_pages_transforms_rule,
get_next_track_dynamic_imports_transform_rule, get_server_actions_transform_rule,
next_cjs_optimizer::get_next_cjs_optimizer_rule,
next_disallow_re_export_all_in_page::get_next_disallow_export_all_in_page_rule,
next_edge_node_api_assert::next_edge_node_api_assert,
next_middleware_dynamic_assert::get_middleware_dynamic_assert_rule,
Expand Down Expand Up @@ -147,6 +148,10 @@ pub async fn get_next_server_transforms_rules(
ServerContextType::Middleware { .. } | ServerContextType::Instrumentation { .. } => false,
};

if is_app_dir {
rules.push(get_next_debug_instant_stack_rule(mdx_rs));
}

if is_app_dir &&
// `cacheComponents` is not supported in the edge runtime.
// (also, the code generated by the dynamic imports transform relies on `CacheSignal`, which uses nodejs-specific APIs)
Expand Down
2 changes: 2 additions & 0 deletions crates/next-core/src/next_shared/transforms/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub(crate) mod debug_fn_name;
pub(crate) mod emotion;
pub(crate) mod modularize_imports;
pub(crate) mod next_cjs_optimizer;
pub(crate) mod next_debug_instant_stack;
pub(crate) mod next_disallow_re_export_all_in_page;
pub(crate) mod next_dynamic;
pub(crate) mod next_edge_node_api_assert;
Expand All @@ -23,6 +24,7 @@ pub(crate) mod swc_ecma_transform_plugins;

use anyhow::Result;
pub use modularize_imports::{ModularizeImportPackageConfig, get_next_modularize_imports_rule};
pub use next_debug_instant_stack::get_next_debug_instant_stack_rule;
pub use next_dynamic::get_next_dynamic_transform_rule;
pub use next_font::get_next_font_transform_rule;
pub use next_lint::get_next_lint_transform_rule;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use anyhow::Result;
use async_trait::async_trait;
use next_custom_transforms::transforms::debug_instant_stack::debug_instant_stack;
use swc_core::ecma::ast::Program;
use turbo_tasks::ResolvedVc;
use turbopack::module_options::{ModuleRule, ModuleRuleEffect};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};

use super::module_rule_match_js_no_url;

pub fn get_next_debug_instant_stack_rule(enable_mdx_rs: bool) -> ModuleRule {
let transform =
EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(NextDebugInstantStack {}) as _));

ModuleRule::new(
module_rule_match_js_no_url(enable_mdx_rs),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
preprocess: ResolvedVc::cell(vec![]),
main: ResolvedVc::cell(vec![]),
postprocess: ResolvedVc::cell(vec![transform]),
}],
)
}

#[derive(Debug)]
struct NextDebugInstantStack {}

#[async_trait]
impl CustomTransformer for NextDebugInstantStack {
#[tracing::instrument(level = tracing::Level::TRACE, name = "debug_instant_stack", skip_all)]
async fn transform(&self, program: &mut Program, _ctx: &TransformContext<'_>) -> Result<()> {
program.mutate(debug_instant_stack());
Ok(())
}
}
1 change: 1 addition & 0 deletions crates/next-custom-transforms/src/chain_transforms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ where
crate::transforms::debug_fn_name::debug_fn_name(),
opts.debug_function_name,
),
crate::transforms::debug_instant_stack::debug_instant_stack(),
visit_mut_pass(crate::transforms::pure::pure_magic(comments.clone())),
Optional::new(
linter(lint_codemod_comments(comments)),
Expand Down
111 changes: 111 additions & 0 deletions crates/next-custom-transforms/src/transforms/debug_instant_stack.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use swc_core::{
common::{Span, Spanned, DUMMY_SP},
ecma::{
ast::*,
visit::{fold_pass, Fold},
},
};

pub fn debug_instant_stack() -> impl Pass {
fold_pass(DebugInstantStack {
instant_export_span: None,
})
}

struct DebugInstantStack {
instant_export_span: Option<Span>,
}

impl Fold for DebugInstantStack {
fn fold_module_items(&mut self, items: Vec<ModuleItem>) -> Vec<ModuleItem> {
// Scan for `export const unstable_instant = ...`
for item in &items {
if let ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) = item {
if let Decl::Var(var_decl) = &export_decl.decl {
for decl in &var_decl.decls {
if let Pat::Ident(ident) = &decl.name {
if ident.id.sym == "unstable_instant" {
if let Some(init) = &decl.init {
self.instant_export_span = Some(init.span());
}
}
}
}
}
}
}

if let Some(source_span) = self.instant_export_span {
let mut new_items = items;
new_items.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
span: DUMMY_SP,
decl: Decl::Var(Box::new(VarDecl {
decls: vec![VarDeclarator {
name: Pat::Ident(BindingIdent {
id: Ident {
sym: "__debugInstantStack".into(),
..Default::default()
},
type_ann: None,
}),
init: Some(Box::new(Expr::Cond(CondExpr {
span: DUMMY_SP,
// process.env.NODE_ENV !== "production"
test: Box::new(Expr::Bin(BinExpr {
span: DUMMY_SP,
op: BinaryOp::NotEqEq,
left: Box::new(Expr::Member(MemberExpr {
span: DUMMY_SP,
obj: Box::new(Expr::Member(MemberExpr {
span: DUMMY_SP,
obj: Box::new(Expr::Ident(Ident {
sym: "process".into(),
..Default::default()
})),
prop: MemberProp::Ident(IdentName {
sym: "env".into(),
span: DUMMY_SP,
}),
})),
prop: MemberProp::Ident(IdentName {
sym: "NODE_ENV".into(),
span: DUMMY_SP,
}),
})),
right: Box::new(Expr::Lit(Lit::Str(Str {
span: DUMMY_SP,
value: "production".into(),
raw: None,
}))),
})),
// new Error()
cons: Box::new(Expr::New(NewExpr {
callee: Box::new(Expr::Ident(Ident {
sym: "Error".into(),
span: source_span,
..Default::default()
})),
args: Some(vec![]),
span: source_span,
..Default::default()
})),
// undefined
alt: Box::new(Expr::Ident(Ident {
sym: "undefined".into(),
..Default::default()
})),
}))),
span: DUMMY_SP,
definite: false,
}],
span: DUMMY_SP,
kind: VarDeclKind::Const,
..Default::default()
})),
})));
new_items
} else {
items
}
}
}
1 change: 1 addition & 0 deletions crates/next-custom-transforms/src/transforms/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod cjs_finder;
pub mod cjs_optimizer;
pub mod debug_fn_name;
pub mod debug_instant_stack;
pub mod disallow_re_export_all_in_page;
pub mod dynamic;
pub mod fonts;
Expand Down
17 changes: 17 additions & 0 deletions crates/next-custom-transforms/tests/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use bytes_str::BytesStr;
use next_custom_transforms::transforms::{
cjs_optimizer::cjs_optimizer,
debug_fn_name::debug_fn_name,
debug_instant_stack::debug_instant_stack,
dynamic::{next_dynamic, NextDynamicMode},
fonts::{next_font_loaders, Config as FontLoaderConfig},
named_import_transform::named_import_transform,
Expand Down Expand Up @@ -869,6 +870,22 @@ fn test_debug_name(input: PathBuf) {
);
}

#[fixture("tests/fixture/debug-instant-stack/**/input.js")]
fn test_debug_instant_stack(input: PathBuf) {
let output = input.parent().unwrap().join("output.js");

test_fixture(
syntax(),
&|_| debug_instant_stack(),
&input,
&output,
FixtureTestConfig {
sourcemap: true,
..Default::default()
},
);
}

#[fixture("tests/fixture/edge-assert/**/input.js")]
fn test_edge_assert(input: PathBuf) {
let output = input.parent().unwrap().join("output.js");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const unstable_instant = { prefetch: 'static' }

export default function Page() {
return <div>Hello</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const unstable_instant = {
prefetch: 'static'
};
export default function Page() {
return <div>Hello</div>;
}
export const __debugInstantStack = process.env.NODE_ENV !== "production" ? new Error() : undefined;

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const revalidate = 60

export default function Page() {
return <div>Hello</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const revalidate = 60;
export default function Page() {
return <div>Hello</div>;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 17 additions & 4 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4230,6 +4230,16 @@ async function validateInstantConfigs(
? instantConfig.prefetch === 'runtime'
: false
})

// Collect the first debug stack from segments with instant configs so we can
// pass it into validation state for setting `cause` on errors.
const debugInstantStack =
segmentsWithInstantConfigs
.map(
(segmentPath) => treeNodes.get(segmentPath)?.module?.debugInstantStack
)
.find((stack): stack is Error => stack != null) ?? null

const clientReferenceManifest = getClientReferenceManifest()

const {
Expand Down Expand Up @@ -4269,7 +4279,8 @@ async function validateInstantConfigs(
hmrRefreshHash,
validationRouteTree,
navigationParent,
false // use static stage for static segments
false, // use static stage for static segments
debugInstantStack
)
if (initialResults.errors.length === 0) {
debug?.(` ✅ Validation successful`)
Expand All @@ -4288,7 +4299,8 @@ async function validateInstantConfigs(
hmrRefreshHash,
validationRouteTree,
navigationParent,
true // use runtime stage for static segments instead
true, // use runtime stage for static segments instead
debugInstantStack
)
if (runtimeResults.errors.length > 0) {
// The errors remained in the runtime stage, so they were caused by a dynamic access.
Expand Down Expand Up @@ -4320,7 +4332,8 @@ async function validateInstantConfigNavigation(
hmrRefreshHash: string | undefined,
routeTree: InstantValidation.RouteTree,
navigationParent: InstantValidation.SegmentPath,
useRuntimeStageForPartialSegments: boolean
useRuntimeStageForPartialSegments: boolean,
debugInstantStack: Error | null
): Promise<{ dynamicHoleKind: DynamicHoleKind; errors: Array<unknown> }> {
const { implicitTags, nonce, workStore } = ctx
const isDebugChannelEnabled = !!ctx.renderOpts.setReactDebugChannel
Expand All @@ -4336,7 +4349,7 @@ async function validateInstantConfigNavigation(
const preinitScripts = () => {}
const { ServerInsertedHTMLProvider } = createServerInsertedHTML()

const dynamicValidation = createInstantValidationState()
const dynamicValidation = createInstantValidationState(debugInstantStack)
const boundaryState = createValidationBoundaryTracking()

const finalClientPrerenderStore: PrerenderStore = {
Expand Down
Loading
Loading