diff --git a/Cargo.lock b/Cargo.lock index 561cae8bfe128..0133cf263a80f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3440,7 +3440,6 @@ dependencies = [ "either", "fxhash", "hex", - "next-transform-dynamic", "next-transform-font", "once_cell", "pathdiff", diff --git a/packages/next-swc/crates/core/Cargo.toml b/packages/next-swc/crates/core/Cargo.toml index ad12e9536cd3d..de1f102f2115f 100644 --- a/packages/next-swc/crates/core/Cargo.toml +++ b/packages/next-swc/crates/core/Cargo.toml @@ -15,6 +15,7 @@ either = "1" fxhash = "0.2.1" hex = "0.4.3" once_cell = { workspace = true } +next-transform-font = {workspace = true} pathdiff = "0.2.0" regex = "1.5" rustc-hash = "1" @@ -23,9 +24,6 @@ serde_json = "1" sha1 = "0.10.1" tracing = { version = "0.1.37" } -next-transform-dynamic = { workspace = true } -next-transform-font = { workspace = true } - turbopack-binding = { workspace = true, features = [ "__swc_core", "__swc_core_next_core", diff --git a/packages/next-swc/crates/core/src/lib.rs b/packages/next-swc/crates/core/src/lib.rs index f810a4ceb0eb9..82bf191047d80 100644 --- a/packages/next-swc/crates/core/src/lib.rs +++ b/packages/next-swc/crates/core/src/lib.rs @@ -37,7 +37,6 @@ use std::{cell::RefCell, path::PathBuf, rc::Rc, sync::Arc}; use auto_cjs::contains_cjs; use either::Either; use fxhash::FxHashSet; -use next_transform_dynamic::{next_dynamic, NextDynamicMode}; use next_transform_font::next_font_loaders; use serde::Deserialize; use turbopack_binding::swc::{ @@ -59,6 +58,7 @@ mod auto_cjs; pub mod cjs_optimizer; pub mod disallow_re_export_all_in_page; pub mod named_import_transform; +pub mod next_dynamic; pub mod next_ssg; pub mod optimize_barrel; pub mod optimize_server_react; @@ -226,7 +226,7 @@ where !opts.disable_next_ssg ), amp_attributes::amp_attributes(), - next_dynamic( + next_dynamic::next_dynamic( opts.is_development, opts.is_server, match &opts.server_components { @@ -238,7 +238,6 @@ where }, _ => false, }, - NextDynamicMode::Webpack, file.name.clone(), opts.pages_dir.clone() ), diff --git a/packages/next-swc/crates/core/src/next_dynamic.rs b/packages/next-swc/crates/core/src/next_dynamic.rs new file mode 100644 index 0000000000000..7539c6945319f --- /dev/null +++ b/packages/next-swc/crates/core/src/next_dynamic.rs @@ -0,0 +1,325 @@ +use std::path::{Path, PathBuf}; + +use pathdiff::diff_paths; +use turbopack_binding::swc::core::{ + common::{errors::HANDLER, FileName, DUMMY_SP}, + ecma::{ + ast::{ + ArrayLit, ArrowExpr, BinExpr, BinaryOp, BlockStmtOrExpr, Bool, CallExpr, Callee, Expr, + ExprOrSpread, Id, Ident, ImportDecl, ImportSpecifier, KeyValueProp, Lit, MemberExpr, + MemberProp, Null, ObjectLit, Prop, PropName, PropOrSpread, Str, Tpl, + }, + atoms::js_word, + utils::ExprFactory, + visit::{Fold, FoldWith}, + }, +}; + +pub fn next_dynamic( + is_development: bool, + is_server: bool, + is_server_components: bool, + filename: FileName, + pages_dir: Option, +) -> impl Fold { + NextDynamicPatcher { + is_development, + is_server, + is_server_components, + pages_dir, + filename, + dynamic_bindings: vec![], + is_next_dynamic_first_arg: false, + dynamically_imported_specifier: None, + } +} + +#[derive(Debug)] +struct NextDynamicPatcher { + is_development: bool, + is_server: bool, + is_server_components: bool, + pages_dir: Option, + filename: FileName, + dynamic_bindings: Vec, + is_next_dynamic_first_arg: bool, + dynamically_imported_specifier: Option, +} + +impl Fold for NextDynamicPatcher { + fn fold_import_decl(&mut self, decl: ImportDecl) -> ImportDecl { + let ImportDecl { + ref src, + ref specifiers, + .. + } = decl; + if &src.value == "next/dynamic" { + for specifier in specifiers { + if let ImportSpecifier::Default(default_specifier) = specifier { + self.dynamic_bindings.push(default_specifier.local.to_id()); + } + } + } + + decl + } + + fn fold_call_expr(&mut self, expr: CallExpr) -> CallExpr { + if self.is_next_dynamic_first_arg { + if let Callee::Import(..) = &expr.callee { + match &*expr.args[0].expr { + Expr::Lit(Lit::Str(Str { value, .. })) => { + self.dynamically_imported_specifier = Some(value.to_string()); + } + Expr::Tpl(Tpl { exprs, quasis, .. }) if exprs.is_empty() => { + self.dynamically_imported_specifier = Some(quasis[0].raw.to_string()); + } + _ => {} + } + } + return expr.fold_children_with(self); + } + let mut expr = expr.fold_children_with(self); + if let Callee::Expr(i) = &expr.callee { + if let Expr::Ident(identifier) = &**i { + if self.dynamic_bindings.contains(&identifier.to_id()) { + if expr.args.is_empty() { + HANDLER.with(|handler| { + handler + .struct_span_err( + identifier.span, + "next/dynamic requires at least one argument", + ) + .emit() + }); + return expr; + } else if expr.args.len() > 2 { + HANDLER.with(|handler| { + handler + .struct_span_err( + identifier.span, + "next/dynamic only accepts 2 arguments", + ) + .emit() + }); + return expr; + } + if expr.args.len() == 2 { + match &*expr.args[1].expr { + Expr::Object(_) => {} + _ => { + HANDLER.with(|handler| { + handler + .struct_span_err( + identifier.span, + "next/dynamic options must be an object literal.\nRead more: https://nextjs.org/docs/messages/invalid-dynamic-options-type", + ) + .emit(); + }); + return expr; + } + } + } + + self.is_next_dynamic_first_arg = true; + expr.args[0].expr = expr.args[0].expr.clone().fold_with(self); + self.is_next_dynamic_first_arg = false; + + if self.dynamically_imported_specifier.is_none() { + return expr; + } + + // dev client or server: + // loadableGenerated: { + // modules: + // ["/project/src/file-being-transformed.js -> " + '../components/hello'] } + + // prod client + // loadableGenerated: { + // webpack: () => [require.resolveWeak('../components/hello')], + let generated = Box::new(Expr::Object(ObjectLit { + span: DUMMY_SP, + props: if self.is_development || self.is_server { + vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { + key: PropName::Ident(Ident::new("modules".into(), DUMMY_SP)), + value: Box::new(Expr::Array(ArrayLit { + elems: vec![Some(ExprOrSpread { + expr: Box::new(Expr::Bin(BinExpr { + span: DUMMY_SP, + op: BinaryOp::Add, + left: Box::new(Expr::Lit(Lit::Str(Str { + value: format!( + "{} -> ", + rel_filename( + self.pages_dir.as_deref(), + &self.filename + ) + ) + .into(), + span: DUMMY_SP, + raw: None, + }))), + right: Box::new(Expr::Lit(Lit::Str(Str { + value: self + .dynamically_imported_specifier + .as_ref() + .unwrap() + .clone() + .into(), + span: DUMMY_SP, + raw: None, + }))), + })), + spread: None, + })], + span: DUMMY_SP, + })), + })))] + } else { + vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { + key: PropName::Ident(Ident::new("webpack".into(), DUMMY_SP)), + value: Box::new(Expr::Arrow(ArrowExpr { + params: vec![], + body: Box::new(BlockStmtOrExpr::Expr(Box::new(Expr::Array( + ArrayLit { + elems: vec![Some(ExprOrSpread { + expr: Box::new(Expr::Call(CallExpr { + callee: Callee::Expr(Box::new(Expr::Member( + MemberExpr { + obj: Box::new(Expr::Ident(Ident { + sym: js_word!("require"), + span: DUMMY_SP, + optional: false, + })), + prop: MemberProp::Ident(Ident { + sym: "resolveWeak".into(), + span: DUMMY_SP, + optional: false, + }), + span: DUMMY_SP, + }, + ))), + args: vec![ExprOrSpread { + expr: Box::new(Expr::Lit(Lit::Str(Str { + value: self + .dynamically_imported_specifier + .as_ref() + .unwrap() + .clone() + .into(), + span: DUMMY_SP, + raw: None, + }))), + spread: None, + }], + span: DUMMY_SP, + type_args: None, + })), + spread: None, + })], + span: DUMMY_SP, + }, + )))), + is_async: false, + is_generator: false, + span: DUMMY_SP, + return_type: None, + type_params: None, + })), + })))] + }, + })); + + let mut props = + vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { + key: PropName::Ident(Ident::new("loadableGenerated".into(), DUMMY_SP)), + value: generated, + })))]; + + let mut has_ssr_false = false; + + if expr.args.len() == 2 { + if let Expr::Object(ObjectLit { + props: options_props, + .. + }) = &*expr.args[1].expr + { + for prop in options_props.iter() { + if let Some(KeyValueProp { key, value }) = match prop { + PropOrSpread::Prop(prop) => match &**prop { + Prop::KeyValue(key_value_prop) => Some(key_value_prop), + _ => None, + }, + _ => None, + } { + if let Some(Ident { + sym, + span: _, + optional: _, + }) = match key { + PropName::Ident(ident) => Some(ident), + _ => None, + } { + if sym == "ssr" { + if let Some(Lit::Bool(Bool { + value: false, + span: _, + })) = value.as_lit() + { + has_ssr_false = true + } + } + } + } + } + props.extend(options_props.iter().cloned()); + } + } + + if has_ssr_false && self.is_server && !self.is_server_components { + expr.args[0] = Lit::Null(Null { span: DUMMY_SP }).as_arg(); + } + + let second_arg = ExprOrSpread { + spread: None, + expr: Box::new(Expr::Object(ObjectLit { + span: DUMMY_SP, + props, + })), + }; + + if expr.args.len() == 2 { + expr.args[1] = second_arg; + } else { + expr.args.push(second_arg) + } + self.dynamically_imported_specifier = None; + } + } + } + expr + } +} + +fn rel_filename(base: Option<&Path>, file: &FileName) -> String { + let base = match base { + Some(v) => v, + None => return file.to_string(), + }; + + let file = match file { + FileName::Real(v) => v, + _ => { + return file.to_string(); + } + }; + + let rel_path = diff_paths(file, base); + + let rel_path = match rel_path { + Some(v) => v, + None => return file.display().to_string(), + }; + + rel_path.display().to_string() +} diff --git a/packages/next-swc/crates/core/tests/errors.rs b/packages/next-swc/crates/core/tests/errors.rs index 1bc4b09d92fba..5ea07faf0ccf9 100644 --- a/packages/next-swc/crates/core/tests/errors.rs +++ b/packages/next-swc/crates/core/tests/errors.rs @@ -2,13 +2,13 @@ use std::path::PathBuf; use next_swc::{ disallow_re_export_all_in_page::disallow_re_export_all_in_page, + next_dynamic::next_dynamic, next_ssg::next_ssg, react_server_components::server_components, server_actions::{ server_actions, {self}, }, }; -use next_transform_dynamic::{next_dynamic, NextDynamicMode}; use next_transform_font::{next_font_loaders, Config as FontLoaderConfig}; use turbopack_binding::swc::{ core::{ @@ -56,7 +56,6 @@ fn next_dynamic_errors(input: PathBuf) { true, false, false, - NextDynamicMode::Webpack, FileName::Real(PathBuf::from("/some-project/src/some-file.js")), Some("/some-project/src".into()), ) diff --git a/packages/next-swc/crates/core/tests/fixture.rs b/packages/next-swc/crates/core/tests/fixture.rs index 61177ccf0f75c..b4b3ccdce78ff 100644 --- a/packages/next-swc/crates/core/tests/fixture.rs +++ b/packages/next-swc/crates/core/tests/fixture.rs @@ -4,6 +4,7 @@ use next_swc::{ amp_attributes::amp_attributes, cjs_optimizer::cjs_optimizer, named_import_transform::named_import_transform, + next_dynamic::next_dynamic, next_ssg::next_ssg, optimize_barrel::optimize_barrel, optimize_server_react::optimize_server_react, @@ -14,7 +15,6 @@ use next_swc::{ }, shake_exports::{shake_exports, Config as ShakeExportsConfig}, }; -use next_transform_dynamic::{next_dynamic, NextDynamicMode}; use next_transform_font::{next_font_loaders, Config as FontLoaderConfig}; use serde::de::DeserializeOwned; use turbopack_binding::swc::{ @@ -64,7 +64,6 @@ fn next_dynamic_fixture(input: PathBuf) { true, false, false, - NextDynamicMode::Webpack, FileName::Real(PathBuf::from("/some-project/src/some-file.js")), Some("/some-project/src".into()), ) @@ -80,7 +79,6 @@ fn next_dynamic_fixture(input: PathBuf) { false, false, false, - NextDynamicMode::Webpack, FileName::Real(PathBuf::from("/some-project/src/some-file.js")), Some("/some-project/src".into()), ) @@ -96,7 +94,6 @@ fn next_dynamic_fixture(input: PathBuf) { false, true, false, - NextDynamicMode::Webpack, FileName::Real(PathBuf::from("/some-project/src/some-file.js")), Some("/some-project/src".into()), ) @@ -119,7 +116,6 @@ fn app_dir_next_dynamic_fixture(input: PathBuf) { true, false, true, - NextDynamicMode::Webpack, FileName::Real(PathBuf::from("/some-project/src/some-file.js")), Some("/some-project/src".into()), ) @@ -135,7 +131,6 @@ fn app_dir_next_dynamic_fixture(input: PathBuf) { false, false, true, - NextDynamicMode::Webpack, FileName::Real(PathBuf::from("/some-project/src/some-file.js")), Some("/some-project/src".into()), ) @@ -151,7 +146,6 @@ fn app_dir_next_dynamic_fixture(input: PathBuf) { false, true, true, - NextDynamicMode::Webpack, FileName::Real(PathBuf::from("/some-project/src/some-file.js")), Some("/some-project/src".into()), ) diff --git a/packages/next-swc/crates/next-api/src/app.rs b/packages/next-swc/crates/next-api/src/app.rs index 7cd5bfd1b2163..b7d70260e6a4d 100644 --- a/packages/next-swc/crates/next-api/src/app.rs +++ b/packages/next-swc/crates/next-api/src/app.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use anyhow::{bail, Context, Result}; use next_core::{ app_structure::{ @@ -23,8 +21,7 @@ use next_core::{ next_edge::route_regex::get_named_middleware_regex, next_manifests::{ AppBuildManifest, AppPathsManifest, BuildManifest, ClientReferenceManifest, - EdgeFunctionDefinition, LoadableManifest, MiddlewareMatcher, MiddlewaresManifestV2, - PagesManifest, Regions, + EdgeFunctionDefinition, MiddlewareMatcher, MiddlewaresManifestV2, PagesManifest, Regions, }, next_server::{ get_server_module_options_context, get_server_resolve_options_context, @@ -42,7 +39,7 @@ use turbopack_binding::{ turbopack::{ core::{ asset::{Asset, AssetContent}, - chunk::{availability_info::AvailabilityInfo, ChunkingContext, EvaluatableAssets}, + chunk::{ChunkingContext, EvaluatableAssets}, file_source::FileSource, module::Module, output::{OutputAsset, OutputAssets}, @@ -56,10 +53,6 @@ use turbopack_binding::{ }; use crate::{ - dynamic_imports::{ - collect_chunk_group, collect_evaluated_chunk_group, collect_next_dynamic_imports, - DynamicImportedChunks, - }, project::Project, route::{Endpoint, Route, Routes, WrittenEndpoint}, server_actions::create_server_actions_manifest, @@ -686,67 +679,6 @@ impl AppEndpoint { ))) } - async fn create_react_loadable_manifest( - dynamic_import_entries: Vc, - ty: &'static str, - node_root: Vc, - pathname: &str, - ) -> Result> { - let dynamic_import_entries = &*dynamic_import_entries.await?; - - let mut output = vec![]; - let mut loadable_manifest: HashMap = Default::default(); - - for (origin, dynamic_imports) in dynamic_import_entries.into_iter() { - let origin_path = &*origin.ident().path().await?; - - for (import, chunk_output) in dynamic_imports { - let chunk_output = chunk_output.await?; - output.extend(chunk_output.iter().copied()); - - let id = format!("{} -> {}", origin_path, import); - - let server_path = node_root.join("server".to_string()); - let server_path_value = server_path.await?; - let files = chunk_output - .iter() - .map(move |&file| { - let server_path_value = server_path_value.clone(); - async move { - Ok(server_path_value - .get_path_to(&*file.ident().path().await?) - .map(|path| path.to_string())) - } - }) - .try_flat_join() - .await?; - - let manifest_item = LoadableManifest { - id: id.clone(), - files, - }; - - loadable_manifest.insert(id, manifest_item); - } - } - - let loadable_path_prefix = get_asset_prefix_from_pathname(pathname); - let loadable_manifest = Vc::upcast(VirtualOutputAsset::new( - node_root.join(format!( - "server/app{loadable_path_prefix}/{ty}/react-loadable-manifest.json", - )), - AssetContent::file( - FileContent::Content(File::from(serde_json::to_string_pretty( - &loadable_manifest, - )?)) - .cell(), - ), - )); - - output.push(loadable_manifest); - Ok(Vc::cell(output)) - } - let endpoint_output = match app_entry.config.await?.runtime.unwrap_or_default() { NextRuntime::Edge => { // create edge chunks @@ -782,7 +714,7 @@ impl AppEndpoint { let files = chunking_context.evaluated_chunk_group( app_entry.rsc_entry.ident(), - Vc::cell(evaluatable_assets.clone()), + Vc::cell(evaluatable_assets), ); server_assets.extend(files.await?.iter().copied()); @@ -869,24 +801,6 @@ impl AppEndpoint { )?; server_assets.push(app_paths_manifest_output); - // create react-loadable-manifest for next/dynamic - let dynamic_import_modules = - collect_next_dynamic_imports(app_entry.rsc_entry).await?; - let dynamic_import_entries = collect_evaluated_chunk_group( - chunking_context, - dynamic_import_modules, - Vc::cell(evaluatable_assets), - ) - .await?; - let loadable_manifest_output = create_react_loadable_manifest( - dynamic_import_entries, - ty, - node_root, - &app_entry.pathname, - ) - .await?; - server_assets.extend(loadable_manifest_output.await?.iter().copied()); - AppEndpointOutput::Edge { files, server_assets: Vc::cell(server_assets), @@ -943,25 +857,6 @@ impl AppEndpoint { )?; server_assets.push(app_paths_manifest_output); - // create react-loadable-manifest for next/dynamic - let availability_info = Value::new(AvailabilityInfo::Root); - let dynamic_import_modules = - collect_next_dynamic_imports(app_entry.rsc_entry).await?; - let dynamic_import_entries = collect_chunk_group( - this.app_project.project().rsc_chunking_context(), - dynamic_import_modules, - availability_info, - ) - .await?; - let loadable_manifest_output = create_react_loadable_manifest( - dynamic_import_entries, - ty, - node_root, - &app_entry.pathname, - ) - .await?; - server_assets.extend(loadable_manifest_output.await?.iter().copied()); - AppEndpointOutput::NodeJs { rsc_chunk, server_assets: Vc::cell(server_assets), diff --git a/packages/next-swc/crates/next-api/src/dynamic_imports.rs b/packages/next-swc/crates/next-api/src/dynamic_imports.rs deleted file mode 100644 index 43d3268209d0f..0000000000000 --- a/packages/next-swc/crates/next-api/src/dynamic_imports.rs +++ /dev/null @@ -1,319 +0,0 @@ -use std::collections::HashMap; - -use anyhow::{bail, Result}; -use indexmap::IndexMap; -use turbo_tasks::{ - graph::{GraphTraversal, NonDeterministic}, - Value, Vc, -}; -use turbopack_binding::{ - swc::core::ecma::{ - ast::{CallExpr, Callee, Expr, Ident, Lit}, - visit::{Visit, VisitWith}, - }, - turbopack::{ - build::BuildChunkingContext, - core::{ - chunk::{ - availability_info::AvailabilityInfo, ChunkableModule, ChunkingContext, - EvaluatableAssets, - }, - issue::{IssueSeverity, OptionIssueSource}, - module::Module, - output::OutputAssets, - reference::primary_referenced_modules, - reference_type::EcmaScriptModulesReferenceSubType, - resolve::{origin::PlainResolveOrigin, parse::Request, pattern::Pattern}, - }, - ecmascript::{ - chunk::{EcmascriptChunkPlaceable, EcmascriptChunkingContext}, - parse::ParseResult, - resolve::esm_resolve, - EcmascriptModuleAsset, - }, - }, -}; - -async fn collect_chunk_group_inner( - dynamic_import_entries: IndexMap>, DynamicImportedModules>, - build_chunk: F, -) -> Result> -where - F: Fn(Vc>) -> Vc, -{ - let mut chunks_hash: HashMap> = HashMap::new(); - let mut dynamic_import_chunks = IndexMap::new(); - - // Iterate over the collected import mappings, and create a chunk for each - // dynamic import. - for (origin_module, dynamic_imports) in dynamic_import_entries { - for (imported_raw_str, imported_module) in dynamic_imports { - let chunk = if let Some(chunk) = chunks_hash.get(&imported_raw_str) { - *chunk - } else { - let Some(chunk_item) = - Vc::try_resolve_sidecast::>(imported_module).await? - else { - bail!("module must be evaluatable"); - }; - - // [Note]: this seems to create duplicated chunks for the same module to the original import() call - // and the explicit chunk we ask in here. So there'll be at least 2 - // chunks for the same module, relying on - // naive hash to have additonal - // chunks in case if there are same modules being imported in differnt - // origins. - let chunk_group = build_chunk(chunk_item); - chunks_hash.insert(imported_raw_str.to_string(), chunk_group); - chunk_group - }; - - dynamic_import_chunks - .entry(origin_module) - .or_insert_with(Vec::new) - .push((imported_raw_str.clone(), chunk)); - } - } - - Ok(Vc::cell(dynamic_import_chunks)) -} - -pub(crate) async fn collect_chunk_group( - chunking_context: Vc, - dynamic_import_entries: IndexMap>, DynamicImportedModules>, - availability_info: Value, -) -> Result> { - collect_chunk_group_inner(dynamic_import_entries, |chunk_item| { - chunking_context.chunk_group(chunk_item, availability_info) - }) - .await -} - -pub(crate) async fn collect_evaluated_chunk_group( - chunking_context: Vc>, - dynamic_import_entries: IndexMap>, DynamicImportedModules>, - evaluatable_assets: Vc, -) -> Result> { - collect_chunk_group_inner(dynamic_import_entries, |chunk_item| { - chunking_context.evaluated_chunk_group(chunk_item.ident(), evaluatable_assets) - }) - .await -} - -/// Returns a mapping of the dynamic imports for each module, if the import is -/// wrapped in `next/dynamic`'s `dynamic()`. Refer https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading#with-named-exports for the usecases. -/// -/// If an import is specified as dynamic, next.js does few things: -/// - Runs a next_dynamic transform to the source file (https://github.com/vercel/next.js/blob/ae1b89984d26b2af3658001fa19a19e1e77c312d/packages/next-swc/crates/next-transform-dynamic/src/lib.rs#L22) -/// - This transform will inject `loadableGenerated` property, which contains the list of the import ids in the form of `${origin} -> ${imported}`. -/// (https://github.com/vercel/next.js/blob/ae1b89984d26b2af3658001fa19a19e1e77c312d/packages/next-swc/crates/next-transform-dynamic/tests/fixture/wrapped-import/output-webpack-dev.js#L5) -/// - Emits `react-loadable-manifest.json` which contains the mapping of the -/// import ids to the chunk ids. -/// - Webpack: (https://github.com/vercel/next.js/blob/ae1b89984d26b2af3658001fa19a19e1e77c312d/packages/next/src/build/webpack/plugins/react-loadable-plugin.ts) -/// - Turbopack: ( https://github.com/vercel/next.js/pull/56389/files#diff-3cac9d9bfe73e0619e6407f21f6fe652da0719d0ec9074ff813ad3e416d0eb1a -/// / https://github.com/vercel/next.js/pull/56389/files#diff-791951bbe1fa09bcbad9be9173412d0848168f7d658758f11b6e8888a021552c -/// / https://github.com/vercel/next.js/pull/56389/files#diff-c33f6895801329243dd3f627c69da259bcab95c2c9d12993152842591931ff01R557 -/// ) -/// - When running an application, -/// - Server reads generated `react-loadable-manifest.json`, sets dynamicImportIds with the mapping of the import ids, and dynamicImports to the actual corresponding chunks. -/// (https://github.com/vercel/next.js/blob/ad42b610c25b72561ad367b82b1c7383fd2a5dd2/packages/next/src/server/load-components.ts#L119 / -/// https://github.com/vercel/next.js/blob/ad42b610c25b72561ad367b82b1c7383fd2a5dd2/packages/next/src/server/render.tsx#L1417C7-L1420) -/// - Server embeds those into __NEXT_DATA__ and sent to the client. (https://github.com/vercel/next.js/blob/ad42b610c25b72561ad367b82b1c7383fd2a5dd2/packages/next/src/server/render.tsx#L1453) -/// - When client boots up, pass it to the client preload (https://github.com/vercel/next.js/blob/ad42b610c25b72561ad367b82b1c7383fd2a5dd2/packages/next/src/client/index.tsx#L943) -/// - Loadable runtime injects preload fn to wait until all the dynamic components are being loaded, this ensures hydration mismatch won't occur -/// (https://github.com/vercel/next.js/blob/ad42b610c25b72561ad367b82b1c7383fd2a5dd2/packages/next/src/shared/lib/loadable.shared-runtime.tsx#L281) -pub(crate) async fn collect_next_dynamic_imports( - entry: Vc>, -) -> Result>, DynamicImportedModules>> { - // Traverse referenced modules graph, collect all of the dynamic imports: - // - Read the Program AST of the Module, this is the origin (A) - // - If there's `dynamic(import(B))`, then B is the module that is being - // imported - // Returned import mappings are in the form of - // (Module, Vec<(B, Module)>) (where B is the raw import source string, - // and Module is the actual resolved Module) - let imported_modules_mapping = NonDeterministic::new() - .skip_duplicates() - .visit([Vc::upcast(entry)], get_referenced_modules) - .await - .completed()? - .into_inner() - .into_iter() - .map(build_dynamic_imports_map_for_module); - - // Consolidate import mappings into a single indexmap - let mut import_mappings: IndexMap>, DynamicImportedModules> = - IndexMap::new(); - - for module_mapping in imported_modules_mapping { - if let Some(module_mapping) = &*module_mapping.await? { - let (origin_module, dynamic_imports) = &*module_mapping.await?; - import_mappings - .entry(*origin_module) - .or_insert_with(Vec::new) - .append(&mut dynamic_imports.clone()) - } - } - - Ok(import_mappings) -} - -async fn get_referenced_modules( - parent: Vc>, -) -> Result>> + Send> { - primary_referenced_modules(parent) - .await - .map(|modules| modules.clone_value().into_iter()) -} - -#[turbo_tasks::function] -async fn build_dynamic_imports_map_for_module( - module: Vc>, -) -> Result> { - let Some(ecmascript_asset) = - Vc::try_resolve_downcast_type::(module).await? - else { - return Ok(OptionDynamicImportsMap::none()); - }; - - // https://github.com/vercel/next.js/pull/56389#discussion_r1349336374 - // don't emit specific error as we expect there's a parse error already reported - let ParseResult::Ok { program, .. } = &*ecmascript_asset.parse().await? else { - return Ok(OptionDynamicImportsMap::none()); - }; - - // Reading the Program AST, collect raw imported module str if it's wrapped in - // dynamic() - let mut visitor = DynamicImportVisitor::new(); - program.visit_with(&mut visitor); - - if visitor.import_sources.is_empty() { - return Ok(OptionDynamicImportsMap::none()); - } - - let mut import_sources = vec![]; - for import in visitor.import_sources.drain(..) { - // Using the given `Module` which is the origin of the dynamic import, trying to - // resolve the module that is being imported. - let dynamic_imported_resolved_module = *esm_resolve( - Vc::upcast(PlainResolveOrigin::new( - ecmascript_asset.await?.asset_context, - module.ident().path(), - )), - Request::parse(Value::new(Pattern::Constant(import.to_string()))), - Value::new(EcmaScriptModulesReferenceSubType::Undefined), - OptionIssueSource::none(), - IssueSeverity::Error.cell(), - ) - .first_module() - .await?; - - if let Some(dynamic_imported_resolved_module) = dynamic_imported_resolved_module { - import_sources.push((import, dynamic_imported_resolved_module)); - } - } - - Ok(Vc::cell(Some(Vc::cell((module, import_sources))))) -} - -/// A visitor to check if there's import to `next/dynamic`, then collecting the -/// import wrapped with dynamic() via CollectImportSourceVisitor. -struct DynamicImportVisitor { - dynamic_ident: Option, - pub import_sources: Vec, -} - -impl DynamicImportVisitor { - fn new() -> Self { - Self { - import_sources: vec![], - dynamic_ident: None, - } - } -} - -impl Visit for DynamicImportVisitor { - fn visit_import_decl(&mut self, decl: &turbopack_binding::swc::core::ecma::ast::ImportDecl) { - // find import decl from next/dynamic, i.e import dynamic from 'next/dynamic' - if decl.src.value == *"next/dynamic" { - if let Some(specifier) = decl.specifiers.first().and_then(|s| s.as_default()) { - self.dynamic_ident = Some(specifier.local.clone()); - } - } - } - - fn visit_call_expr(&mut self, call_expr: &CallExpr) { - // Collect imports if the import call is wrapped in the call dynamic() - if let Callee::Expr(ident) = &call_expr.callee { - if let Expr::Ident(ident) = &**ident { - if let Some(dynamic_ident) = &self.dynamic_ident { - if ident.sym == *dynamic_ident.sym { - let mut collect_import_source_visitor = CollectImportSourceVisitor::new(); - call_expr.visit_children_with(&mut collect_import_source_visitor); - - if let Some(import_source) = collect_import_source_visitor.import_source { - self.import_sources.push(import_source); - } - } - } - } - } - - call_expr.visit_children_with(self); - } -} - -/// A visitor to collect import source string from import('path/to/module') -struct CollectImportSourceVisitor { - import_source: Option, -} - -impl CollectImportSourceVisitor { - fn new() -> Self { - Self { - import_source: None, - } - } -} - -impl Visit for CollectImportSourceVisitor { - fn visit_call_expr(&mut self, call_expr: &CallExpr) { - // find import source from import('path/to/module') - // [NOTE]: Turbopack does not support webpack-specific comment directives, i.e - // import(/* webpackChunkName: 'hello1' */ '../../components/hello3') - // Renamed chunk in the comment will be ignored. - if let Callee::Import(_import) = call_expr.callee { - if let Some(arg) = call_expr.args.first() { - if let Expr::Lit(Lit::Str(str_)) = &*arg.expr { - self.import_source = Some(str_.value.to_string()); - } - } - } - - // Don't need to visit children, we expect import() won't have any - // nested calls as dynamic() should be statically analyzable import. - } -} - -pub type DynamicImportedModules = Vec<(String, Vc>)>; -pub type DynamicImportedOutputAssets = Vec<(String, Vc)>; - -/// A struct contains mapping for the dynamic imports to construct chunk per -/// each individual module (Origin Module, Vec<(ImportSourceString, Module)>) -#[turbo_tasks::value(transparent)] -pub struct DynamicImportsMap(pub (Vc>, DynamicImportedModules)); - -/// An Option wrapper around [DynamicImportsMap]. -#[turbo_tasks::value(transparent)] -pub struct OptionDynamicImportsMap(Option>); - -#[turbo_tasks::value_impl] -impl OptionDynamicImportsMap { - #[turbo_tasks::function] - pub fn none() -> Vc { - Vc::cell(None) - } -} - -#[turbo_tasks::value(transparent)] -pub struct DynamicImportedChunks(pub IndexMap>, DynamicImportedOutputAssets>); diff --git a/packages/next-swc/crates/next-api/src/lib.rs b/packages/next-swc/crates/next-api/src/lib.rs index 0e7d7c7b04e6b..2a83ab2d75eba 100644 --- a/packages/next-swc/crates/next-api/src/lib.rs +++ b/packages/next-swc/crates/next-api/src/lib.rs @@ -3,7 +3,6 @@ #![feature(async_fn_in_trait)] mod app; -mod dynamic_imports; mod entrypoints; mod middleware; mod pages; diff --git a/packages/next-swc/crates/next-api/src/pages.rs b/packages/next-swc/crates/next-api/src/pages.rs index 0401a2281d37a..b3fd51f922442 100644 --- a/packages/next-swc/crates/next-api/src/pages.rs +++ b/packages/next-swc/crates/next-api/src/pages.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use anyhow::{bail, Context, Result}; use indexmap::IndexMap; use next_core::{ @@ -13,8 +11,8 @@ use next_core::{ next_dynamic::NextDynamicTransition, next_edge::route_regex::get_named_middleware_regex, next_manifests::{ - BuildManifest, EdgeFunctionDefinition, LoadableManifest, MiddlewareMatcher, - MiddlewaresManifestV2, PagesManifest, + BuildManifest, EdgeFunctionDefinition, MiddlewareMatcher, MiddlewaresManifestV2, + PagesManifest, }, next_pages::create_page_ssr_entry_module, next_server::{ @@ -39,7 +37,7 @@ use turbopack_binding::{ build::BuildChunkingContext, core::{ asset::AssetContent, - chunk::{availability_info::AvailabilityInfo, ChunkingContext, EvaluatableAssets}, + chunk::{ChunkingContext, EvaluatableAssets}, context::AssetContext, file_source::FileSource, issue::{IssueSeverity, OptionIssueSource}, @@ -65,10 +63,6 @@ use turbopack_binding::{ }; use crate::{ - dynamic_imports::{ - collect_chunk_group, collect_evaluated_chunk_group, collect_next_dynamic_imports, - DynamicImportedChunks, - }, project::Project, route::{Endpoint, Route, Routes, WrittenEndpoint}, server_paths::all_server_paths, @@ -615,21 +609,9 @@ impl PageEndpoint { evaluatable_assets.push(evaluatable); let edge_files = edge_chunking_context - .evaluated_chunk_group(ssr_module.ident(), Vc::cell(evaluatable_assets.clone())); - - let dynamic_import_modules = collect_next_dynamic_imports(ssr_module).await?; - let dynamic_import_entries = collect_evaluated_chunk_group( - edge_chunking_context, - dynamic_import_modules, - Vc::cell(evaluatable_assets.clone()), - ) - .await?; + .evaluated_chunk_group(ssr_module.ident(), Vc::cell(evaluatable_assets)); - Ok(SsrChunk::Edge { - files: edge_files, - dynamic_import_entries, - } - .cell()) + Ok(SsrChunk::Edge { files: edge_files }.cell()) } else { let ssr_module = create_page_ssr_entry_module( this.pathname, @@ -651,15 +633,8 @@ impl PageEndpoint { runtime_entries, ); - let availability_info = Value::new(AvailabilityInfo::Root); - let dynamic_import_modules = collect_next_dynamic_imports(ssr_module).await?; - let dynamic_import_entries = - collect_chunk_group(chunking_context, dynamic_import_modules, availability_info) - .await?; - Ok(SsrChunk::NodeJs { entry: ssr_entry_chunk, - dynamic_import_entries, } .cell()) } @@ -753,78 +728,6 @@ impl PageEndpoint { ))) } - #[turbo_tasks::function] - async fn react_loadable_manifest( - self: Vc, - dynamic_import_entries: Vc, - ) -> Result> { - let this = self.await?; - let node_root = this.pages_project.project().node_root(); - let pages_dir = this.pages_project.pages_dir().await?; - - let dynamic_import_entries = &*dynamic_import_entries.await?; - - let mut output = vec![]; - let mut loadable_manifest: HashMap = Default::default(); - for (origin, dynamic_imports) in dynamic_import_entries.into_iter() { - let origin_path = &*origin.ident().path().await?; - - for (import, chunk_output) in dynamic_imports { - let chunk_output = chunk_output.await?; - output.extend(chunk_output.iter().copied()); - - // https://github.com/vercel/next.js/blob/b7c85b87787283d8fb86f705f67bdfabb6b654bb/packages/next-swc/crates/next-transform-dynamic/src/lib.rs#L230 - // For the pages dir, next_dynamic transform puts relative paths to the pages - // dir for the origin import. - let id = format!( - "{} -> {}", - pages_dir - .get_path_to(origin_path) - .map_or_else(|| origin_path.to_string(), |path| path.to_string()), - import - ); - - let server_path = node_root.join("server".to_string()); - let server_path_value = server_path.await?; - let files = chunk_output - .iter() - .map(move |file| { - let server_path_value = server_path_value.clone(); - async move { - Ok(server_path_value - .get_path_to(&*file.ident().path().await?) - .map(|path| path.to_string())) - } - }) - .try_flat_join() - .await?; - - let manifest_item = LoadableManifest { - id: id.clone(), - files, - }; - - loadable_manifest.insert(id, manifest_item); - } - } - - let loadable_path_prefix = get_asset_prefix_from_pathname(&this.pathname.await?); - let loadable_manifest = Vc::upcast(VirtualOutputAsset::new( - node_root.join(format!( - "server/pages{loadable_path_prefix}/react-loadable-manifest.json" - )), - AssetContent::file( - FileContent::Content(File::from(serde_json::to_string_pretty( - &loadable_manifest, - )?)) - .cell(), - ), - )); - - output.push(loadable_manifest); - Ok(Vc::cell(output)) - } - #[turbo_tasks::function] async fn build_manifest( self: Vc, @@ -893,27 +796,18 @@ impl PageEndpoint { }; let page_output = match *ssr_chunk.await? { - SsrChunk::NodeJs { - entry, - dynamic_import_entries, - } => { + SsrChunk::NodeJs { entry } => { let pages_manifest = self.pages_manifest(entry); server_assets.push(pages_manifest); server_assets.push(entry); - let loadable_manifest_output = self.react_loadable_manifest(dynamic_import_entries); - server_assets.extend(loadable_manifest_output.await?.iter().copied()); - PageEndpointOutput::NodeJs { entry_chunk: entry, server_assets: Vc::cell(server_assets), client_assets: Vc::cell(client_assets), } } - SsrChunk::Edge { - files, - dynamic_import_entries, - } => { + SsrChunk::Edge { files } => { let node_root = this.pages_project.project().node_root(); let files_value = files.await?; if let Some(&file) = files_value.first() { @@ -973,9 +867,6 @@ impl PageEndpoint { )); server_assets.push(middleware_manifest_v2); - let loadable_manifest_output = self.react_loadable_manifest(dynamic_import_entries); - server_assets.extend(loadable_manifest_output.await?.iter().copied()); - PageEndpointOutput::Edge { files, server_assets: Vc::cell(server_assets), @@ -1110,12 +1001,6 @@ impl PageEndpointOutput { #[turbo_tasks::value] pub enum SsrChunk { - NodeJs { - entry: Vc>, - dynamic_import_entries: Vc, - }, - Edge { - files: Vc, - dynamic_import_entries: Vc, - }, + NodeJs { entry: Vc> }, + Edge { files: Vc }, } diff --git a/packages/next-swc/crates/next-core/src/next_manifests/mod.rs b/packages/next-swc/crates/next-core/src/next_manifests/mod.rs index 86042ed18fe2f..5d6a452be39e6 100644 --- a/packages/next-swc/crates/next-core/src/next_manifests/mod.rs +++ b/packages/next-swc/crates/next-core/src/next_manifests/mod.rs @@ -145,17 +145,6 @@ pub struct AppPathsManifest { pub node_server_app_paths: PagesManifest, } -// A struct represent a single entry in react-loadable-manifest.json. -// The manifest is in a format of: -// { [`${origin} -> ${imported}`]: { id: `${origin} -> ${imported}`, files: -// string[] } } -#[derive(Serialize, Default, Debug)] -#[serde(rename_all = "camelCase")] -pub struct LoadableManifest { - pub id: String, - pub files: Vec, -} - #[derive(Serialize, Default, Debug)] #[serde(rename_all = "camelCase")] pub struct ServerReferenceManifest { diff --git a/packages/next-swc/crates/next-core/src/next_shared/transforms/next_dynamic.rs b/packages/next-swc/crates/next-core/src/next_shared/transforms/next_dynamic.rs index c0267fb1d0191..588ad932c600d 100644 --- a/packages/next-swc/crates/next-core/src/next_shared/transforms/next_dynamic.rs +++ b/packages/next-swc/crates/next-core/src/next_shared/transforms/next_dynamic.rs @@ -65,7 +65,12 @@ impl CustomTransformer for NextJsDynamic { }, self.is_server, self.is_server_components, - NextDynamicMode::Webpack, + NextDynamicMode::Turbopack { + dynamic_transition_name: match self.mode { + NextMode::Development => "next-client-chunks".to_string(), + NextMode::Build => "next-dynamic".to_string(), + }, + }, FileName::Real(ctx.file_path_str.into()), self.pages_dir.clone(), )); diff --git a/packages/next-swc/crates/next-transform-dynamic/src/lib.rs b/packages/next-swc/crates/next-transform-dynamic/src/lib.rs index 9136fd78a336d..fc630e6c44113 100644 --- a/packages/next-swc/crates/next-transform-dynamic/src/lib.rs +++ b/packages/next-swc/crates/next-transform-dynamic/src/lib.rs @@ -19,11 +19,6 @@ use swc_core::{ quote, }; -/// Creates a SWC visitor to transform `next/dynamic` calls to have the -/// corresponding `loadableGenerated` property. -/// -/// [NOTE] We do not use `NextDynamicMode::Turbopack` yet. It isn't compatible -/// with current loadable manifest, which causes hydration errors. pub fn next_dynamic( is_development: bool, is_server: bool, @@ -94,7 +89,6 @@ enum NextDynamicPatcherState { Webpack, /// In Turbo mode, contains a list of modules that need to be imported with /// the given transition under a particular ident. - #[allow(unused)] Turbopack { dynamic_transition_name: String, imports: Vec, diff --git a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts index 55d55a6f7ce43..17562efdcac23 100644 --- a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts +++ b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts @@ -78,7 +78,6 @@ import { PAGES_MANIFEST, PHASE_DEVELOPMENT_SERVER, SERVER_REFERENCE_MANIFEST, - REACT_LOADABLE_MANIFEST, } from '../../../shared/lib/constants' import { getMiddlewareRouteMatcher } from '../../../shared/lib/router/utils/middleware-route-matcher' @@ -118,7 +117,6 @@ import { normalizeMetadataRoute } from '../../../lib/metadata/get-metadata-route import { clearModuleContext } from '../render-server' import type { ActionManifest } from '../../../build/webpack/plugins/flight-client-entry-plugin' import { denormalizePagePath } from '../../../shared/lib/page-path/denormalize-page-path' -import type { LoadableManifest } from '../../load-components' const wsServer = new ws.Server({ noServer: true }) @@ -505,7 +503,6 @@ async function startWatcher(opts: SetupOpts) { ws, Map> >() - const loadbleManifests = new Map() const clients = new Set() async function loadMiddlewareManifest( @@ -563,16 +560,6 @@ async function startWatcher(opts: SetupOpts) { ) } - async function loadLoadableManifest( - pageName: string, - type: 'app' | 'pages' = 'pages' - ): Promise { - loadbleManifests.set( - pageName, - await loadPartialManifest(REACT_LOADABLE_MANIFEST, pageName, type) - ) - } - const buildingReported = new Set() async function changeSubscription( @@ -710,14 +697,6 @@ async function startWatcher(opts: SetupOpts) { return manifest } - function mergeLoadableManifests(manifests: Iterable) { - const manifest: LoadableManifest = {} - for (const m of manifests) { - Object.assign(manifest, m) - } - return manifest - } - async function writeFileAtomic( filePath: string, content: string @@ -887,14 +866,13 @@ async function startWatcher(opts: SetupOpts) { ) } - async function writeLoadableManifest(): Promise { - const loadableManifest = mergeLoadableManifests(loadbleManifests.values()) - const loadableManifestPath = path.join(distDir, REACT_LOADABLE_MANIFEST) - deleteCache(loadableManifestPath) - await writeFileAtomic( - loadableManifestPath, - JSON.stringify(loadableManifest, null, 2) + async function writeOtherManifests(): Promise { + const loadableManifestPath = path.join( + distDir, + 'react-loadable-manifest.json' ) + deleteCache(loadableManifestPath) + await writeFileAtomic(loadableManifestPath, JSON.stringify({}, null, 2)) } async function subscribeToHmrEvents(id: string, client: ws) { @@ -1074,8 +1052,8 @@ async function startWatcher(opts: SetupOpts) { await writeAppPathsManifest() await writeMiddlewareManifest() await writeActionManifest() + await writeOtherManifests() await writeFontManifest() - await writeLoadableManifest() const turbopackHotReloader: NextJsHotReloaderInterface = { turbopackProject: project, @@ -1242,7 +1220,7 @@ async function startWatcher(opts: SetupOpts) { await writeFallbackBuildManifest() await writePagesManifest() await writeMiddlewareManifest() - await writeLoadableManifest() + await writeOtherManifests() return } @@ -1351,13 +1329,12 @@ async function startWatcher(opts: SetupOpts) { } else { middlewareManifests.delete(page) } - await loadLoadableManifest(page, 'pages') await writeBuildManifest(opts.fsChecker.rewrites) await writeFallbackBuildManifest() await writePagesManifest() await writeMiddlewareManifest() - await writeLoadableManifest() + await writeOtherManifests() processIssues(page, page, writtenEndpoint) @@ -1381,11 +1358,10 @@ async function startWatcher(opts: SetupOpts) { } else { middlewareManifests.delete(page) } - await loadLoadableManifest(page, 'pages') await writePagesManifest() await writeMiddlewareManifest() - await writeLoadableManifest() + await writeOtherManifests() processIssues(page, page, writtenEndpoint) @@ -1419,7 +1395,7 @@ async function startWatcher(opts: SetupOpts) { await writeAppPathsManifest() await writeMiddlewareManifest() await writeActionManifest() - await writeLoadableManifest() + await writeOtherManifests() processIssues(page, page, writtenEndpoint, true) @@ -1444,7 +1420,7 @@ async function startWatcher(opts: SetupOpts) { await writeAppPathsManifest() await writeMiddlewareManifest() await writeMiddlewareManifest() - await writeLoadableManifest() + await writeOtherManifests() processIssues(page, page, writtenEndpoint, true) diff --git a/packages/next/src/server/load-components.ts b/packages/next/src/server/load-components.ts index cce641eb998f2..63e563b13a2d3 100644 --- a/packages/next/src/server/load-components.ts +++ b/packages/next/src/server/load-components.ts @@ -33,17 +33,6 @@ export type ManifestItem = { export type ReactLoadableManifest = { [moduleId: string]: ManifestItem } -/** - * A manifest entry type for the react-loadable-manifest.json. - * - * The whole manifest.json is a type of `Record` - * where pathName is a string-based key points to the path of the page contains - * each dynamic imports. - */ -export interface LoadableManifest { - [k: string]: { id: string | number; files: string[] } -} - export type LoadComponentsReturnType = { Component: NextComponentType pageConfig: PageConfig diff --git a/test/development/basic/next-dynamic.test.ts b/test/development/basic/next-dynamic.test.ts index efbd2c99c66d0..5294624adeb1d 100644 --- a/test/development/basic/next-dynamic.test.ts +++ b/test/development/basic/next-dynamic.test.ts @@ -237,35 +237,32 @@ describe.each([ }) } }) - // Turbopack doesn't have this feature. - ;(process.env.TURBOPACK ? describe.skip : describe)( - 'custom chunkfilename', - () => { - it('should render the correct filename', async () => { - const $ = await get$(basePath + '/dynamic/chunkfilename') - expect($('body').text()).toMatch(/test chunkfilename/) - expect($('html').html()).toMatch(/hello-world\.js/) - }) - it('should render the component on client side', async () => { - let browser - try { - browser = await webdriver( - next.url, - basePath + '/dynamic/chunkfilename' - ) - await check( - () => browser.elementByCss('body').text(), - /test chunkfilename/ - ) - } finally { - if (browser) { - await browser.close() - } + describe('custom chunkfilename', () => { + it('should render the correct filename', async () => { + const $ = await get$(basePath + '/dynamic/chunkfilename') + expect($('body').text()).toMatch(/test chunkfilename/) + expect($('html').html()).toMatch(/hello-world\.js/) + }) + + it('should render the component on client side', async () => { + let browser + try { + browser = await webdriver( + next.url, + basePath + '/dynamic/chunkfilename' + ) + await check( + () => browser.elementByCss('body').text(), + /test chunkfilename/ + ) + } finally { + if (browser) { + await browser.close() } - }) - } - ) + } + }) + }) describe('custom loading', () => { it('should render custom loading on the server side when `ssr:false` and `loading` is provided', async () => { @@ -292,49 +289,45 @@ describe.each([ }) }) - // TODO: Make this test work with Turbopack. Currently the test relies on `chunkFileName` which is not supported by Turbopack. - ;(process.env.TURBOPACK ? describe.skip : describe)( - 'Multiple modules', - () => { - it('should only include the rendered module script tag', async () => { - const $ = await get$(basePath + '/dynamic/multiple-modules') - const html = $('html').html() + describe('Multiple modules', () => { + it('should only include the rendered module script tag', async () => { + const $ = await get$(basePath + '/dynamic/multiple-modules') + const html = $('html').html() + expect(html).toMatch(/hello1\.js/) + expect(html).not.toMatch(/hello2\.js/) + }) + + it('should only load the rendered module in the browser', async () => { + let browser + try { + browser = await webdriver( + next.url, + basePath + '/dynamic/multiple-modules' + ) + const html = await browser.eval( + 'document.documentElement.innerHTML' + ) expect(html).toMatch(/hello1\.js/) expect(html).not.toMatch(/hello2\.js/) - }) - - it('should only load the rendered module in the browser', async () => { - let browser - try { - browser = await webdriver( - next.url, - basePath + '/dynamic/multiple-modules' - ) - const html = await browser.eval( - 'document.documentElement.innerHTML' - ) - expect(html).toMatch(/hello1\.js/) - expect(html).not.toMatch(/hello2\.js/) - } finally { - if (browser) { - await browser.close() - } + } finally { + if (browser) { + await browser.close() } - }) + } + }) - it('should only render one bundle if component is used multiple times', async () => { - const $ = await get$(basePath + '/dynamic/multiple-modules') - const html = $('html').html() - try { - expect(html.match(/chunks[\\/]hello1\.js/g).length).toBe(1) - expect(html).not.toMatch(/hello2\.js/) - } catch (err) { - console.error(html) - throw err - } - }) - } - ) + it('should only render one bundle if component is used multiple times', async () => { + const $ = await get$(basePath + '/dynamic/multiple-modules') + const html = $('html').html() + try { + expect(html.match(/chunks[\\/]hello1\.js/g).length).toBe(1) + expect(html).not.toMatch(/hello2\.js/) + } catch (err) { + console.error(html) + throw err + } + }) + }) }) } ) diff --git a/test/integration/next-dynamic-lazy-compilation/test/index.test.js b/test/integration/next-dynamic-lazy-compilation/test/index.test.js index c441a54635a4c..b075256aba2c6 100644 --- a/test/integration/next-dynamic-lazy-compilation/test/index.test.js +++ b/test/integration/next-dynamic-lazy-compilation/test/index.test.js @@ -46,38 +46,31 @@ function runTests() { }) } -// This test is not needed for Turbopack as it relies on an experimental webpack feature. -;(process.env.TURBOPACK ? describe.skip : describe)( - 'next/dynamic lazy compilation', - () => { - describe('dev mode', () => { - beforeAll(async () => { - appPort = await findPort() - app = await launchApp(appDir, appPort) - }) - afterAll(() => killApp(app)) - - runTests(true) +describe('next/dynamic', () => { + describe('dev mode', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort) }) - ;(process.env.TURBOPACK ? describe.skip : describe)( - 'production mode', - () => { - beforeAll(async () => { - await runNextCommand(['build', appDir]) + afterAll(() => killApp(app)) + + runTests(true) + }) + ;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => { + beforeAll(async () => { + await runNextCommand(['build', appDir]) - app = nextServer({ - dir: appDir, - dev: false, - quiet: true, - }) + app = nextServer({ + dir: appDir, + dev: false, + quiet: true, + }) - server = await startApp(app) - appPort = server.address().port - }) - afterAll(() => stopApp(server)) + server = await startApp(app) + appPort = server.address().port + }) + afterAll(() => stopApp(server)) - runTests() - } - ) - } -) + runTests() + }) +})