Skip to content

Commit

Permalink
Turbopack: add support for an assetPrefix and basePath (#56058)
Browse files Browse the repository at this point in the history
Depends on vercel/turborepo#6036

This constructs a general asset prefix (depending on if basePath and/or assetPrefix is defined), and sets it on Next's and Turbopack's chunking context.

Test Plan: Use a `create-next-app` with corresponding Next.js PR and basePath and assetPrefix specified.

Tested:
- Browser (async) chunk loading
- Static asset loading on server and browser

Closes WEB-1666
  • Loading branch information
wbinnssmith authored Sep 29, 2023
1 parent 769d27a commit 741114a
Show file tree
Hide file tree
Showing 15 changed files with 207 additions and 158 deletions.
146 changes: 108 additions & 38 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ swc_core = { version = "0.83.12", features = [
testing = { version = "0.34.1" }

# Turbo crates
turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230928.3" }
turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230928.5" }
# [TODO]: need to refactor embed_directory! macro usages, as well as resolving turbo_tasks::function, macros..
turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230928.3" }
turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230928.5" }
# [TODO]: need to refactor embed_directory! macro usage in next-core
turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230928.3" }
turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230928.5" }

# General Deps

Expand Down
19 changes: 14 additions & 5 deletions packages/next-swc/crates/next-api/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,15 @@ impl Project {
}

#[turbo_tasks::function]
pub(super) fn client_relative_path(self: Vc<Self>) -> Vc<FileSystemPath> {
self.client_root().join("_next".to_string())
pub(super) async fn client_relative_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
let next_config = self.next_config().await?;
Ok(self.client_root().join(format!(
"{}/_next",
next_config
.base_path
.clone()
.unwrap_or_else(|| "".to_string()),
)))
}

#[turbo_tasks::function]
Expand Down Expand Up @@ -384,7 +391,8 @@ impl Project {
let this = self.await?;
Ok(get_client_chunking_context(
self.project_path(),
self.client_root(),
self.client_relative_path(),
self.next_config().computed_asset_prefix(),
self.client_compile_time_info().environment(),
this.mode,
))
Expand All @@ -395,7 +403,8 @@ impl Project {
get_server_chunking_context(
self.project_path(),
self.node_root(),
self.client_root(),
self.client_relative_path(),
self.next_config().computed_asset_prefix(),
self.server_compile_time_info().environment(),
)
}
Expand All @@ -405,7 +414,7 @@ impl Project {
get_edge_chunking_context(
self.project_path(),
self.node_root(),
self.client_root(),
self.client_relative_path(),
self.edge_compile_time_info().environment(),
)
}
Expand Down
25 changes: 17 additions & 8 deletions packages/next-swc/crates/next-build/src/next_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,17 +249,32 @@ pub(crate) async fn next_build(options: TransientInstance<BuildOptions>) -> Resu

// CHUNKING

// This ensures that the _next prefix is properly stripped from all client paths
// in manifests. It will be added back on the client through the chunk_base_path
// mechanism.
let next_config_ref = next_config.await?;
let client_relative_path = client_root.join(format!(
"{}/_next",
next_config_ref
.base_path
.clone()
.unwrap_or_else(|| "".to_string()),
));
let client_relative_path_ref = client_relative_path.await?;

let client_chunking_context = get_client_chunking_context(
project_root,
client_root,
client_relative_path,
next_config.computed_asset_prefix(),
client_compile_time_info.environment(),
mode,
);

let server_chunking_context = get_server_chunking_context(
project_root,
node_root,
client_root,
client_relative_path,
next_config.computed_asset_prefix(),
server_compile_time_info.environment(),
);
// TODO(alexkirsz) This should be the same chunking context. The layer should
Expand All @@ -272,12 +287,6 @@ pub(crate) async fn next_build(options: TransientInstance<BuildOptions>) -> Resu
let mut build_manifest: BuildManifest = Default::default();
let build_manifest_path = client_root.join("build-manifest.json".to_string());

// This ensures that the _next prefix is properly stripped from all client paths
// in manifests. It will be added back on the client through the chunk_base_path
// mechanism.
let client_relative_path = client_root.join("_next".to_string());
let client_relative_path_ref = client_relative_path.await?;

// PAGE CHUNKING

let mut pages_manifest: PagesManifest = Default::default();
Expand Down
2 changes: 1 addition & 1 deletion packages/next-swc/crates/next-core/src/loader_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ impl LoaderTreeBuilder {
let identifier = magic_identifier::mangle(&format!("{name} #{i}"));

match self.mode {
NextMode::Development | NextMode::DevServer => {
NextMode::Development => {
let chunks_identifier =
magic_identifier::mangle(&format!("chunks of {name} #{i}"));
writeln!(
Expand Down
6 changes: 2 additions & 4 deletions packages/next-swc/crates/next-core/src/mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ use turbo_tasks::TaskInput;
#[derive(Debug, Copy, Clone, TaskInput, Ord, PartialOrd, Hash)]
pub enum NextMode {
/// `next dev --turbo`
DevServer,
/// `next dev --experimental-turbo`
Development,
/// `next build`
Build,
Expand All @@ -16,15 +14,15 @@ impl NextMode {
/// Returns the NODE_ENV value for the current mode.
pub fn node_env(&self) -> &'static str {
match self {
NextMode::Development | NextMode::DevServer => "development",
NextMode::Development => "development",
NextMode::Build => "production",
}
}

/// Returns true if the development React runtime should be used.
pub fn is_react_development(&self) -> bool {
match self {
NextMode::Development | NextMode::DevServer => true,
NextMode::Development => true,
NextMode::Build => false,
}
}
Expand Down
54 changes: 14 additions & 40 deletions packages/next-swc/crates/next-core/src/next_client/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,38 +369,33 @@ pub async fn get_client_module_options_context(
}

#[turbo_tasks::function]
pub fn get_client_chunking_context(
pub async fn get_client_chunking_context(
project_path: Vc<FileSystemPath>,
client_root: Vc<FileSystemPath>,
asset_prefix: Vc<Option<String>>,
environment: Vc<Environment>,
mode: NextMode,
) -> Vc<Box<dyn EcmascriptChunkingContext>> {
let output_root = match mode {
NextMode::DevServer => client_root,
NextMode::Development | NextMode::Build => client_root.join("_next".to_string()),
};
let builder = DevChunkingContext::builder(
) -> Result<Vc<Box<dyn EcmascriptChunkingContext>>> {
let mut builder = DevChunkingContext::builder(
project_path,
output_root,
client_root.join("_next/static/chunks".to_string()),
client_root,
client_root.join("static/chunks".to_string()),
get_client_assets_path(client_root),
environment,
);
)
.chunk_base_path(asset_prefix)
.asset_base_path(asset_prefix);

let builder = match mode {
NextMode::DevServer => builder.hot_module_replacement(),
NextMode::Development => builder
.hot_module_replacement()
.chunk_base_path(Vc::cell(Some("_next/".to_string()))),
NextMode::Build => builder.chunk_base_path(Vc::cell(Some("_next/".to_string()))),
};
if matches!(mode, NextMode::Development) {
builder = builder.hot_module_replacement();
}

Vc::upcast(builder.build())
Ok(Vc::upcast(builder.build()))
}

#[turbo_tasks::function]
pub fn get_client_assets_path(client_root: Vc<FileSystemPath>) -> Vc<FileSystemPath> {
client_root.join("_next/static/media".to_string())
client_root.join("static/media".to_string())
}

#[turbo_tasks::function]
Expand Down Expand Up @@ -428,27 +423,6 @@ pub async fn get_client_runtime_entries(
}

match mode {
NextMode::DevServer => {
let resolve_options_context = get_client_resolve_options_context(
project_root,
ty,
mode,
next_config,
execution_context,
);
let enable_react_refresh =
assert_can_resolve_react_refresh(project_root, resolve_options_context)
.await?
.as_request();

// It's important that React Refresh come before the regular bootstrap file,
// because the bootstrap contains JSX which requires Refresh's global
// functions to be available.
if let Some(request) = enable_react_refresh {
runtime_entries
.push(RuntimeEntry::Request(request, project_root.join("_".to_string())).cell())
};
}
NextMode::Development => {
let resolve_options_context = get_client_resolve_options_context(
project_root,
Expand Down
19 changes: 19 additions & 0 deletions packages/next-swc/crates/next-core/src/next_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,25 @@ impl NextConfig {
self.await?.skip_trailing_slash_redirect.unwrap_or(false),
))
}

/// Returns the final asset prefix. If an assetPrefix is set, it's used.
/// Otherwise, the basePath is used.
#[turbo_tasks::function]
pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<Option<String>>> {
let this = self.await?;

Ok(Vc::cell(Some(format!(
"{}/_next/",
if let Some(asset_prefix) = &this.asset_prefix {
asset_prefix
} else if let Some(base_path) = &this.base_path {
base_path
} else {
""
}
.trim_end_matches('/')
))))
}
}

fn next_configs() -> Vc<Vec<String>> {
Expand Down
15 changes: 2 additions & 13 deletions packages/next-swc/crates/next-core/src/next_import_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,14 +245,6 @@ pub async fn get_next_server_import_map(
ServerContextType::AppSSR { .. }
| ServerContextType::AppRSC { .. }
| ServerContextType::AppRoute { .. } => {
match mode {
NextMode::Build => {}
NextMode::DevServer => {
// The sandbox can't be bundled and needs to be external
import_map.insert_exact_alias("next/dist/server/web/sandbox", external);
}
NextMode::Development => {}
}
import_map.insert_exact_alias(
"next/head",
request_to_import_mapping(project_path, "next/dist/client/components/noop-head"),
Expand Down Expand Up @@ -424,10 +416,7 @@ async fn insert_next_server_special_aliases(
}
(_, ServerContextType::PagesData { .. }) => {}
// the logic closely follows the one in createRSCAliases in webpack-config.ts
(
NextMode::DevServer | NextMode::Build | NextMode::Development,
ServerContextType::AppSSR { app_dir },
) => {
(NextMode::Build | NextMode::Development, ServerContextType::AppSSR { app_dir }) => {
import_map.insert_exact_alias(
"@opentelemetry/api",
// TODO(WEB-625) this actually need to prefer the local version of
Expand Down Expand Up @@ -547,7 +536,7 @@ async fn insert_next_server_special_aliases(
);
}
(
NextMode::Build | NextMode::Development | NextMode::DevServer,
NextMode::Build | NextMode::Development,
ServerContextType::AppRSC { app_dir, .. } | ServerContextType::AppRoute { app_dir },
) => {
import_map.insert_exact_alias(
Expand Down
31 changes: 15 additions & 16 deletions packages/next-swc/crates/next-core/src/next_server/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,21 +575,17 @@ pub fn get_server_runtime_entries(
)))
.cell()];

match mode {
NextMode::Development => {}
NextMode::DevServer => {}
NextMode::Build => {
if let ServerContextType::AppRSC { .. } = ty.into_value() {
runtime_entries.push(
RuntimeEntry::Request(
Request::parse(Value::new(Pattern::Constant(
"./build/server/app-bootstrap.ts".to_string(),
))),
next_js_fs().root().join("_".to_string()),
)
.cell(),
);
}
if matches!(mode, NextMode::Build) {
if let ServerContextType::AppRSC { .. } = ty.into_value() {
runtime_entries.push(
RuntimeEntry::Request(
Request::parse(Value::new(Pattern::Constant(
"./build/server/app-bootstrap.ts".to_string(),
))),
next_js_fs().root().join("_".to_string()),
)
.cell(),
);
}
}

Expand All @@ -603,6 +599,7 @@ pub fn get_server_chunking_context(
// TODO(alexkirsz) Is this even necessary? Are assets not always on the client chunking context
// anyway?
client_root: Vc<FileSystemPath>,
asset_prefix: Vc<Option<String>>,
environment: Vc<Environment>,
) -> Vc<BuildChunkingContext> {
// TODO(alexkirsz) This should return a trait that can be implemented by the
Expand All @@ -611,10 +608,12 @@ pub fn get_server_chunking_context(
BuildChunkingContext::builder(
project_path,
node_root,
client_root,
node_root.join("server/chunks".to_string()),
client_root.join("_next/static/media".to_string()),
client_root.join("static/media".to_string()),
environment,
)
.asset_prefix(asset_prefix)
.minify_type(MinifyType::NoMinify)
.build()
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@ impl CustomTransformer for NextJsDynamic {
let p = std::mem::replace(program, Program::Module(Module::dummy()));
*program = p.fold_with(&mut next_dynamic(
match self.mode {
NextMode::Development | NextMode::DevServer => true,
NextMode::Development => true,
NextMode::Build => false,
},
self.is_server,
self.is_server_components,
NextDynamicMode::Turbopack {
dynamic_transition_name: match self.mode {
NextMode::Development | NextMode::DevServer => "next-client-chunks".to_string(),
NextMode::Development => "next-client-chunks".to_string(),
NextMode::Build => "next-dynamic".to_string(),
},
},
Expand Down
9 changes: 3 additions & 6 deletions packages/next-swc/crates/next-core/src/page_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,9 @@ fn page_loader_chunk_reference_description() -> Vc<String> {
impl OutputAsset for PageLoaderAsset {
#[turbo_tasks::function]
async fn ident(&self) -> Result<Vc<AssetIdent>> {
Ok(AssetIdent::from_path(self.server_root.join(format!(
"_next/static/chunks/pages{}",
let root = self.rebase_prefix_path.await?.unwrap_or(self.server_root);
Ok(AssetIdent::from_path(root.join(format!(
"static/chunks/pages{}",
get_asset_path_from_pathname(&self.pathname.await?, ".js")
))))
}
Expand Down Expand Up @@ -213,10 +214,6 @@ impl Asset for PageLoaderAsset {
async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
let this = &*self.await?;

// The asset emits `__turbopack_load_page_chunks__`, which is implemented using
// `__turbopack_load__`. `__turbopack_load__` will prefix the path with the
// relative client path (`_next/`), so we need to remove that when we emit the
// chunk paths.
let chunks_data = self.chunks_data(this.rebase_prefix_path).await?;
let chunks_data = chunks_data.iter().try_join().await?;
let chunks_data: Vec<_> = chunks_data
Expand Down
2 changes: 1 addition & 1 deletion packages/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@
"@types/ws": "8.2.0",
"@vercel/ncc": "0.34.0",
"@vercel/nft": "0.22.6",
"@vercel/turbopack-ecmascript-runtime": "https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230928.3",
"@vercel/turbopack-ecmascript-runtime": "https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230928.5",
"acorn": "8.5.0",
"ajv": "8.11.0",
"amphtml-validator": "1.0.35",
Expand Down
Loading

0 comments on commit 741114a

Please sign in to comment.