Skip to content

Commit

Permalink
Extract future common code into base_loader_tree (#70284)
Browse files Browse the repository at this point in the history
This is a pure refactoring in preparation for introducing an `app_route_loader_tree` in a future PR.

It enables reusing common logic for injecting imported modules from the app directory into both page and route entries.
  • Loading branch information
unstubbable authored Sep 20, 2024
1 parent ce74096 commit d2274c1
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 109 deletions.
152 changes: 43 additions & 109 deletions crates/next-core/src/app_page_loader_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use std::{

use anyhow::Result;
use indexmap::IndexMap;
use indoc::formatdoc;
use turbo_tasks::{RcStr, Value, ValueToString, Vc};
use turbo_tasks::{RcStr, Value, Vc};
use turbo_tasks_fs::FileSystemPath;
use turbopack::{transition::Transition, ModuleAssetContext};
use turbopack_core::{
Expand All @@ -22,6 +21,7 @@ use crate::{
get_metadata_route_name, AppPageLoaderTree, Components, GlobalMetadata, Metadata,
MetadataItem, MetadataWithAltItem,
},
base_loader_tree::{BaseLoaderTreeBuilder, ComponentType},
next_app::{
metadata::{get_content_type, image::dynamic_image_metadata_source},
AppPage,
Expand All @@ -30,106 +30,47 @@ use crate::{
};

pub struct AppPageLoaderTreeBuilder {
inner_assets: IndexMap<RcStr, Vc<Box<dyn Module>>>,
counter: usize,
imports: Vec<RcStr>,
base: BaseLoaderTreeBuilder,
loader_tree_code: String,
module_asset_context: Vc<ModuleAssetContext>,
server_component_transition: Vc<Box<dyn Transition>>,
pages: Vec<Vc<FileSystemPath>>,
/// next.config.js' basePath option to construct og metadata.
base_path: Option<RcStr>,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ComponentType {
Page,
DefaultPage,
Error,
Layout,
Loading,
Template,
NotFound,
}

impl ComponentType {
fn name(&self) -> &'static str {
match self {
ComponentType::Page => "page",
ComponentType::DefaultPage => "defaultPage",
ComponentType::Error => "error",
ComponentType::Layout => "layout",
ComponentType::Loading => "loading",
ComponentType::Template => "template",
ComponentType::NotFound => "not-found",
}
}
}

impl AppPageLoaderTreeBuilder {
fn new(
module_asset_context: Vc<ModuleAssetContext>,
server_component_transition: Vc<Box<dyn Transition>>,
base_path: Option<RcStr>,
) -> Self {
AppPageLoaderTreeBuilder {
inner_assets: IndexMap::new(),
counter: 0,
imports: Vec::new(),
base: BaseLoaderTreeBuilder::new(module_asset_context, server_component_transition),
loader_tree_code: String::new(),
module_asset_context,
server_component_transition,
pages: Vec::new(),
base_path,
}
}

fn unique_number(&mut self) -> usize {
let i = self.counter;
self.counter += 1;
i
}

async fn write_component(
&mut self,
ty: ComponentType,
component: Option<Vc<FileSystemPath>>,
component_type: ComponentType,
path: Option<Vc<FileSystemPath>>,
) -> Result<()> {
if let Some(component) = component {
if matches!(ty, ComponentType::Page) {
self.pages.push(component);
if let Some(path) = path {
if matches!(component_type, ComponentType::Page) {
self.pages.push(path);
}

let name = ty.name();
let i = self.unique_number();
let identifier = magic_identifier::mangle(&format!("{name} #{i}"));

let module = process_module(
&self.module_asset_context,
&self.server_component_transition,
component,
);
let tuple_code = self
.base
.create_component_tuple_code(component_type, path)
.await?;

writeln!(
self.loader_tree_code,
" {name}: [() => {identifier}, {path}],",
name = StringifyJs(name),
path = StringifyJs(&module.ident().path().to_string().await?)
" {name}: {tuple_code},",
name = StringifyJs(component_type.name())
)?;

self.imports.push(
formatdoc!(
r#"
import * as {} from "COMPONENT_{}";
"#,
identifier,
i
)
.into(),
);

self.inner_assets
.insert(format!("COMPONENT_{i}").into(), module);
}
Ok(())
}
Expand Down Expand Up @@ -237,21 +178,23 @@ impl AppPageLoaderTreeBuilder {
.await?;
}
MetadataWithAltItem::Dynamic { path, .. } => {
let i = self.unique_number();
let i = self.base.unique_number();
let identifier = magic_identifier::mangle(&format!("{name} #{i}"));
let inner_module_id = format!("METADATA_{i}");

self.imports
self.base
.imports
.push(format!("import {identifier} from \"{inner_module_id}\";").into());

let source = dynamic_image_metadata_source(
Vc::upcast(self.module_asset_context),
Vc::upcast(self.base.module_asset_context),
*path,
name.into(),
app_page.clone(),
);

let module = self
.base
.module_asset_context
.process(
source,
Expand All @@ -260,7 +203,9 @@ impl AppPageLoaderTreeBuilder {
)),
)
.module();
self.inner_assets.insert(inner_module_id.into(), module);
self.base
.inner_assets
.insert(inner_module_id.into(), module);

let s = " ";
writeln!(self.loader_tree_code, "{s}{identifier},")?;
Expand All @@ -277,26 +222,27 @@ impl AppPageLoaderTreeBuilder {
path: Vc<FileSystemPath>,
alt_path: Option<Vc<FileSystemPath>>,
) -> Result<()> {
let i = self.unique_number();
let i = self.base.unique_number();

let identifier = magic_identifier::mangle(&format!("{name} #{i}"));
let inner_module_id = format!("METADATA_{i}");
let helper_import: RcStr = "import { fillMetadataSegment } from \
\"next/dist/lib/metadata/get-metadata-route\""
.into();

if !self.imports.contains(&helper_import) {
self.imports.push(helper_import);
if !self.base.imports.contains(&helper_import) {
self.base.imports.push(helper_import);
}

self.imports
self.base
.imports
.push(format!("import {identifier} from \"{inner_module_id}\";").into());
self.inner_assets.insert(
self.base.inner_assets.insert(
inner_module_id.into(),
Vc::upcast(StructuredImageModuleType::create_module(
Vc::upcast(FileSource::new(path)),
BlurPlaceholderMode::None,
self.module_asset_context,
self.base.module_asset_context,
)),
);

Expand Down Expand Up @@ -333,9 +279,13 @@ impl AppPageLoaderTreeBuilder {
if let Some(alt_path) = alt_path {
let identifier = magic_identifier::mangle(&format!("{name} alt text #{i}"));
let inner_module_id = format!("METADATA_ALT_{i}");
self.imports

self.base
.imports
.push(format!("import {identifier} from \"{inner_module_id}\";").into());

let module = self
.base
.module_asset_context
.process(
Vc::upcast(TextContentFileSource::new(Vc::upcast(FileSource::new(
Expand All @@ -344,7 +294,10 @@ impl AppPageLoaderTreeBuilder {
Value::new(ReferenceType::Internal(InnerAssets::empty())),
)
.module();
self.inner_assets.insert(inner_module_id.into(), module);

self.base
.inner_assets
.insert(inner_module_id.into(), module);

writeln!(self.loader_tree_code, "{s} alt: {identifier},")?;
}
Expand Down Expand Up @@ -431,19 +384,15 @@ impl AppPageLoaderTreeBuilder {

let components = &loader_tree.components;
if let Some(global_error) = components.global_error {
let module = process_module(
&self.module_asset_context,
&self.server_component_transition,
global_error,
);
self.inner_assets.insert(GLOBAL_ERROR.into(), module);
let module = self.base.process_module(global_error);
self.base.inner_assets.insert(GLOBAL_ERROR.into(), module);
};

self.walk_tree(loader_tree, true).await?;
Ok(AppPageLoaderTreeModule {
imports: self.imports,
imports: self.base.imports,
loader_tree_code: self.loader_tree_code.into(),
inner_assets: self.inner_assets,
inner_assets: self.base.inner_assets,
pages: self.pages,
})
}
Expand All @@ -470,18 +419,3 @@ impl AppPageLoaderTreeModule {
}

pub const GLOBAL_ERROR: &str = "GLOBAL_ERROR_MODULE";

fn process_module(
&context: &Vc<ModuleAssetContext>,
&server_component_transition: &Vc<Box<dyn Transition>>,
component: Vc<FileSystemPath>,
) -> Vc<Box<dyn Module>> {
let source = Vc::upcast(FileSource::new(component));
let reference_ty = Value::new(ReferenceType::EcmaScriptModules(
EcmaScriptModulesReferenceSubType::Undefined,
));

server_component_transition
.process(source, context, reference_ty)
.module()
}
111 changes: 111 additions & 0 deletions crates/next-core/src/base_loader_tree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use anyhow::Result;
use indexmap::IndexMap;
use indoc::formatdoc;
use turbo_tasks::{RcStr, Value, ValueToString, Vc};
use turbo_tasks_fs::FileSystemPath;
use turbopack::{transition::Transition, ModuleAssetContext};
use turbopack_core::{
file_source::FileSource,
module::Module,
reference_type::{EcmaScriptModulesReferenceSubType, ReferenceType},
};
use turbopack_ecmascript::{magic_identifier, utils::StringifyJs};

pub struct BaseLoaderTreeBuilder {
pub inner_assets: IndexMap<RcStr, Vc<Box<dyn Module>>>,
counter: usize,
pub imports: Vec<RcStr>,
pub module_asset_context: Vc<ModuleAssetContext>,
pub server_component_transition: Vc<Box<dyn Transition>>,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ComponentType {
Page,
DefaultPage,
Error,
Layout,
Loading,
Template,
NotFound,
}

impl ComponentType {
pub fn name(&self) -> &'static str {
match self {
ComponentType::Page => "page",
ComponentType::DefaultPage => "defaultPage",
ComponentType::Error => "error",
ComponentType::Layout => "layout",
ComponentType::Loading => "loading",
ComponentType::Template => "template",
ComponentType::NotFound => "not-found",
}
}
}

impl BaseLoaderTreeBuilder {
pub fn new(
module_asset_context: Vc<ModuleAssetContext>,
server_component_transition: Vc<Box<dyn Transition>>,
) -> Self {
BaseLoaderTreeBuilder {
inner_assets: IndexMap::new(),
counter: 0,
imports: Vec::new(),
module_asset_context,
server_component_transition,
}
}

pub fn unique_number(&mut self) -> usize {
let i = self.counter;
self.counter += 1;
i
}

pub fn process_module(&self, path: Vc<FileSystemPath>) -> Vc<Box<dyn Module>> {
let source = Vc::upcast(FileSource::new(path));

let reference_type = Value::new(ReferenceType::EcmaScriptModules(
EcmaScriptModulesReferenceSubType::Undefined,
));

self.server_component_transition
.process(source, self.module_asset_context, reference_type)
.module()
}

pub async fn create_component_tuple_code(
&mut self,
component_type: ComponentType,
path: Vc<FileSystemPath>,
) -> Result<String> {
let name = component_type.name();
let i = self.unique_number();
let identifier = magic_identifier::mangle(&format!("{name} #{i}"));

self.imports.push(
formatdoc!(
r#"
import * as {} from "COMPONENT_{}";
"#,
identifier,
i
)
.into(),
);

let module = self.process_module(path);

self.inner_assets
.insert(format!("COMPONENT_{i}").into(), module);

let module_path = module.ident().path().to_string().await?;

Ok(format!(
"[() => {identifier}, {path}]",
path = StringifyJs(&module_path),
))
}
}
1 change: 1 addition & 0 deletions crates/next-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
mod app_page_loader_tree;
pub mod app_segment_config;
pub mod app_structure;
mod base_loader_tree;
mod bootstrap;
mod embed_js;
mod emit;
Expand Down

0 comments on commit d2274c1

Please sign in to comment.