Skip to content

Commit

Permalink
feat(turbopack): support loading WebAssembly in the edge runtime (#59013
Browse files Browse the repository at this point in the history
)
  • Loading branch information
ForsakenHarmony authored Dec 5, 2023
1 parent b2084ea commit 2cd3bf4
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 97 deletions.
50 changes: 16 additions & 34 deletions packages/next-swc/crates/next-api/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::collections::HashMap;
use anyhow::{bail, Context, Result};
use indexmap::IndexSet;
use next_core::{
all_assets_from_entries,
app_structure::{
get_entrypoints, Entrypoint as AppEntrypoint, Entrypoints as AppEntrypoints, LoaderTree,
MetadataItem,
Expand Down Expand Up @@ -63,6 +64,7 @@ use crate::{
collect_chunk_group, collect_evaluated_chunk_group, collect_next_dynamic_imports,
DynamicImportedChunks,
},
middleware::{get_js_paths_from_root, get_wasm_paths_from_root, wasm_paths_to_bindings},
project::Project,
route::{Endpoint, Route, Routes, WrittenEndpoint},
server_actions::create_server_actions_manifest,
Expand Down Expand Up @@ -844,7 +846,9 @@ impl AppEndpoint {
app_entry.rsc_entry.ident(),
Vc::cell(evaluatable_assets.clone()),
);
server_assets.extend(files.await?.iter().copied());
let files_value = files.await?;

server_assets.extend(files_value.iter().copied());

// the next-edge-ssr-loader templates expect the manifests to be stored in
// global variables defined in these files
Expand All @@ -856,44 +860,21 @@ impl AppEndpoint {
"server/middleware-react-loadable-manifest.js".to_string(),
"server/next-font-manifest.js".to_string(),
];
let mut wasm_paths_from_root = vec![];

let node_root_value = node_root.await?;

let middleware_paths_from_root = middleware_assets
.iter()
.map({
let node_root_value = node_root_value.clone();
move |&file| {
let node_root_value = node_root_value.clone();
async move {
Ok(node_root_value
.get_path_to(&*file.ident().path().await?)
.filter(|path| path.ends_with(".js"))
.map(|path| path.to_string()))
}
}
})
.try_flat_join()
.await?;
file_paths_from_root
.extend(get_js_paths_from_root(&node_root_value, &middleware_assets).await?);
file_paths_from_root
.extend(get_js_paths_from_root(&node_root_value, &files_value).await?);

file_paths_from_root.extend(middleware_paths_from_root);
let all_output_assets = all_assets_from_entries(files).await?;

let rsc_paths_from_root = files
.await?
.iter()
.map(move |&file| {
let node_root_value = node_root_value.clone();
async move {
Ok(node_root_value
.get_path_to(&*file.ident().path().await?)
.filter(|path| path.ends_with(".js"))
.map(|path| path.to_string()))
}
})
.try_flat_join()
.await?;

file_paths_from_root.extend(rsc_paths_from_root);
wasm_paths_from_root
.extend(get_wasm_paths_from_root(&node_root_value, &middleware_assets).await?);
wasm_paths_from_root
.extend(get_wasm_paths_from_root(&node_root_value, &all_output_assets).await?);

let entry_file = "app-edge-has-no-entrypoint".to_string();

Expand All @@ -907,6 +888,7 @@ impl AppEndpoint {
};
let edge_function_definition = EdgeFunctionDefinition {
files: file_paths_from_root,
wasm: wasm_paths_to_bindings(wasm_paths_from_root),
name: app_entry.pathname.to_string(),
page: app_entry.original_name.clone(),
regions: app_entry
Expand Down
33 changes: 16 additions & 17 deletions packages/next-swc/crates/next-api/src/instrumentation.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use anyhow::{bail, Context, Result};
use anyhow::{bail, Result};
use next_core::{
all_assets_from_entries,
mode::NextMode,
next_edge::entry::wrap_edge_entry,
next_manifests::{InstrumentationDefinition, MiddlewaresManifestV2},
next_server::{get_server_chunking_context, get_server_runtime_entries, ServerContextType},
};
use tracing::Instrument;
use turbo_tasks::{Completion, TryJoinIterExt, Value, Vc};
use turbo_tasks::{Completion, Value, Vc};
use turbopack_binding::{
turbo::tasks_fs::{File, FileContent},
turbopack::{
Expand All @@ -23,6 +24,7 @@ use turbopack_binding::{
};

use crate::{
middleware::{get_js_paths_from_root, get_wasm_paths_from_root, wasm_paths_to_bindings},
project::Project,
route::{Endpoint, WrittenEndpoint},
server_paths::all_server_paths,
Expand Down Expand Up @@ -123,26 +125,23 @@ impl InstrumentationEndpoint {
let this = self.await?;

if this.is_edge {
let mut output_assets = self.edge_files().await?.clone_value();
let edge_files = self.edge_files();
let mut output_assets = edge_files.await?.clone_value();

let node_root = this.project.node_root();
let node_root_value = node_root.await?;

let files_paths_from_root = {
let node_root = &node_root.await?;
output_assets
.iter()
.map(|&file| async move {
Ok(node_root
.get_path_to(&*file.ident().path().await?)
.context("middleware file path must be inside the node root")?
.to_string())
})
.try_join()
.await?
};
let file_paths_from_root =
get_js_paths_from_root(&node_root_value, &output_assets).await?;

let all_output_assets = all_assets_from_entries(edge_files).await?;

let wasm_paths_from_root =
get_wasm_paths_from_root(&node_root_value, &all_output_assets).await?;

let instrumentation_definition = InstrumentationDefinition {
files: files_paths_from_root,
files: file_paths_from_root,
wasm: wasm_paths_to_bindings(wasm_paths_from_root),
name: "instrumentation".to_string(),
..Default::default()
};
Expand Down
112 changes: 94 additions & 18 deletions packages/next-swc/crates/next-api/src/middleware.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
use anyhow::{bail, Context, Result};
use next_core::{
all_assets_from_entries,
middleware::get_middleware_module,
mode::NextMode,
next_edge::entry::wrap_edge_entry,
next_manifests::{EdgeFunctionDefinition, MiddlewareMatcher, MiddlewaresManifestV2},
next_manifests::{
AssetBinding, EdgeFunctionDefinition, MiddlewareMatcher, MiddlewaresManifestV2,
},
next_server::{get_server_runtime_entries, ServerContextType},
util::parse_config_from_source,
};
use tracing::Instrument;
use turbo_tasks::{Completion, TryJoinIterExt, Value, Vc};
use turbo_tasks::{Completion, TryFlatJoinIterExt, Value, Vc};
use turbopack_binding::{
turbo::tasks_fs::{File, FileContent},
turbo::tasks_fs::{File, FileContent, FileSystemPath},
turbopack::{
core::{
asset::AssetContent,
Expand Down Expand Up @@ -101,23 +104,18 @@ impl MiddlewareEndpoint {

let config = parse_config_from_source(this.userland_module);

let mut output_assets = self.edge_files().await?.clone_value();
let edge_files = self.edge_files();
let mut output_assets = edge_files.await?.clone_value();

let node_root = this.project.node_root();
let node_root_value = node_root.await?;

let files_paths_from_root = {
let node_root = &node_root.await?;
output_assets
.iter()
.map(|&file| async move {
Ok(node_root
.get_path_to(&*file.ident().path().await?)
.context("middleware file path must be inside the node root")?
.to_string())
})
.try_join()
.await?
};
let file_paths_from_root = get_js_paths_from_root(&node_root_value, &output_assets).await?;

let all_output_assets = all_assets_from_entries(edge_files).await?;

let wasm_paths_from_root =
get_wasm_paths_from_root(&node_root_value, &all_output_assets).await?;

let matchers = if let Some(matchers) = config.await?.matcher.as_ref() {
matchers
Expand All @@ -136,7 +134,8 @@ impl MiddlewareEndpoint {
};

let edge_function_definition = EdgeFunctionDefinition {
files: files_paths_from_root,
files: file_paths_from_root,
wasm: wasm_paths_to_bindings(wasm_paths_from_root),
name: "middleware".to_string(),
page: "/".to_string(),
regions: None,
Expand Down Expand Up @@ -197,3 +196,80 @@ impl Endpoint for MiddlewareEndpoint {
Completion::immutable()
}
}

pub(crate) async fn get_paths_from_root(
root: &FileSystemPath,
output_assets: &[Vc<Box<dyn OutputAsset>>],
filter: impl FnOnce(&str) -> bool + Copy,
) -> Result<Vec<String>> {
output_assets
.iter()
.map({
move |&file| async move {
let path = &*file.ident().path().await?;
let relative = root
.get_path_to(path)
.context("file path must be inside the root")?;

Ok(if filter(relative) {
Some(relative.to_string())
} else {
None
})
}
})
.try_flat_join()
.await
}

pub(crate) async fn get_js_paths_from_root(
root: &FileSystemPath,
output_assets: &[Vc<Box<dyn OutputAsset>>],
) -> Result<Vec<String>> {
get_paths_from_root(root, output_assets, |path| path.ends_with(".js")).await
}

pub(crate) async fn get_wasm_paths_from_root(
root: &FileSystemPath,
output_assets: &[Vc<Box<dyn OutputAsset>>],
) -> Result<Vec<String>> {
get_paths_from_root(root, output_assets, |path| path.ends_with(".wasm")).await
}

fn get_file_stem(path: &str) -> &str {
let file_name = if let Some((_, file_name)) = path.rsplit_once('/') {
file_name
} else {
path
};

if let Some((stem, _)) = file_name.split_once('.') {
if stem.is_empty() {
file_name
} else {
stem
}
} else {
file_name
}
}

pub(crate) fn wasm_paths_to_bindings(paths: Vec<String>) -> Vec<AssetBinding> {
paths
.into_iter()
.map(|path| {
let stem = get_file_stem(&path);

// very simple escaping just replacing unsupported characters with `_`
let escaped = stem.replace(
|c: char| !c.is_ascii_alphanumeric() && c != '$' && c != '_',
"_",
);

AssetBinding {
name: format!("wasm_{}", escaped),
file_path: path,
}
})
.collect()
}
25 changes: 11 additions & 14 deletions packages/next-swc/crates/next-api/src/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::HashMap;
use anyhow::{bail, Context, Result};
use indexmap::IndexMap;
use next_core::{
create_page_loader_entry_module, get_asset_path_from_pathname,
all_assets_from_entries, create_page_loader_entry_module, get_asset_path_from_pathname,
get_edge_resolve_options_context,
mode::NextMode,
next_client::{
Expand Down Expand Up @@ -70,6 +70,7 @@ use crate::{
collect_chunk_group, collect_evaluated_chunk_group, collect_next_dynamic_imports,
DynamicImportedChunks,
},
middleware::{get_js_paths_from_root, get_wasm_paths_from_root, wasm_paths_to_bindings},
project::Project,
route::{Endpoint, Route, Routes, WrittenEndpoint},
server_paths::all_server_paths,
Expand Down Expand Up @@ -992,22 +993,17 @@ impl PageEndpoint {
"server/middleware-react-loadable-manifest.js".to_string(),
"server/next-font-manifest.js".to_string(),
];
let mut wasm_paths_from_root = vec![];

let node_root_value = node_root.await?;
let middleware_paths_from_root: Vec<String> = files_value
.iter()
.map(move |&file| {
let node_root_value = node_root_value.clone();
async move {
Ok(node_root_value
.get_path_to(&*file.ident().path().await?)
.map(|path| path.to_string()))
}
})
.try_flat_join()
.await?;

file_paths_from_root.extend(middleware_paths_from_root);
file_paths_from_root
.extend(get_js_paths_from_root(&node_root_value, &files_value).await?);

let all_output_assets = all_assets_from_entries(files).await?;

wasm_paths_from_root
.extend(get_wasm_paths_from_root(&node_root_value, &all_output_assets).await?);

let pathname = this.pathname.await?;
let named_regex = get_named_middleware_regex(&pathname);
Expand All @@ -1019,6 +1015,7 @@ impl PageEndpoint {
let original_name = this.original_name.await?;
let edge_function_definition = EdgeFunctionDefinition {
files: file_paths_from_root,
wasm: wasm_paths_to_bindings(wasm_paths_from_root),
name: pathname.to_string(),
page: original_name.to_string(),
regions: None,
Expand Down
5 changes: 3 additions & 2 deletions packages/next-swc/crates/next-api/src/server_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,10 @@ async fn build_server_actions_loader(
let module_name = import_map
.entry(*module)
.or_insert_with(|| format!("ACTIONS_MODULE{index}"));
write!(
writeln!(
contents,
" '{hash_id}': (...args) => (0, require('{module_name}')['{name}'])(...args),",
" '{hash_id}': (...args) => Promise.resolve(require('{module_name}')).then(mod => \
(0, mod['{name}'])(...args)),",
)?;
}
write!(contents, "}});")?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ pub async fn get_edge_resolve_options_context(

let resolve_options_context = ResolveOptionsContext {
enable_node_modules: Some(project_path.root().resolve().await?),
enable_edge_node_externals: true,
custom_conditions,
import_map: Some(next_edge_import_map),
module: true,
Expand Down
Loading

1 comment on commit 2cd3bf4

@ijjk
Copy link
Member

@ijjk ijjk commented on 2cd3bf4 Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stats from current release

Default Build (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary v14.0.3 vercel/next.js canary Change
buildDuration 9.9s 11.3s ⚠️ +1.3s
buildDurationCached 5.8s 5.9s ⚠️ +103ms
nodeModulesSize 199 MB 199 MB ⚠️ +642 kB
nextStartRea..uration (ms) 413ms 424ms N/A
Client Bundles (main, webpack) Overall increase ⚠️
vercel/next.js canary v14.0.3 vercel/next.js canary Change
199-HASH.js gzip 28.7 kB 30.6 kB ⚠️ +1.89 kB
3f784ff6-HASH.js gzip 53.3 kB 53.3 kB
494.HASH.js gzip 180 B 181 B N/A
framework-HASH.js gzip 45.2 kB 45.2 kB N/A
main-app-HASH.js gzip 241 B 238 B N/A
main-HASH.js gzip 31.7 kB 31.7 kB N/A
webpack-HASH.js gzip 1.7 kB 1.7 kB
Overall change 83.7 kB 85.6 kB ⚠️ +1.89 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary v14.0.3 vercel/next.js canary Change
polyfills-HASH.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary v14.0.3 vercel/next.js canary Change
_app-HASH.js gzip 194 B 195 B N/A
_error-HASH.js gzip 182 B 181 B N/A
amp-HASH.js gzip 504 B 503 B N/A
css-HASH.js gzip 322 B 323 B N/A
dynamic-HASH.js gzip 2.5 kB 2.5 kB N/A
edge-ssr-HASH.js gzip 253 B 255 B N/A
head-HASH.js gzip 348 B 347 B N/A
hooks-HASH.js gzip 369 B 368 B N/A
image-HASH.js gzip 4.3 kB 4.27 kB N/A
index-HASH.js gzip 256 B 256 B
link-HASH.js gzip 2.63 kB 2.6 kB N/A
routerDirect..HASH.js gzip 311 B 311 B
script-HASH.js gzip 384 B 383 B N/A
withRouter-HASH.js gzip 307 B 308 B N/A
1afbb74e6ecf..834.css gzip 106 B 106 B
Overall change 673 B 673 B
Client Build Manifests
vercel/next.js canary v14.0.3 vercel/next.js canary Change
_buildManifest.js gzip 484 B 483 B N/A
Overall change 0 B 0 B
Rendered Page Sizes
vercel/next.js canary v14.0.3 vercel/next.js canary Change
index.html gzip 528 B 527 B N/A
link.html gzip 539 B 541 B N/A
withRouter.html gzip 524 B 522 B N/A
Overall change 0 B 0 B
Edge SSR bundle Size Overall increase ⚠️
vercel/next.js canary v14.0.3 vercel/next.js canary Change
edge-ssr.js gzip 92.6 kB 93.7 kB ⚠️ +1.14 kB
page.js gzip 145 kB 146 kB ⚠️ +1.04 kB
Overall change 238 kB 240 kB ⚠️ +2.18 kB
Middleware size Overall increase ⚠️
vercel/next.js canary v14.0.3 vercel/next.js canary Change
middleware-b..fest.js gzip 623 B 624 B N/A
middleware-r..fest.js gzip 150 B 151 B N/A
middleware.js gzip 24.8 kB 37.5 kB ⚠️ +12.7 kB
edge-runtime..pack.js gzip 1.92 kB 1.92 kB
Overall change 26.7 kB 39.4 kB ⚠️ +12.7 kB
Next Runtimes Overall increase ⚠️
vercel/next.js canary v14.0.3 vercel/next.js canary Change
app-page-exp...dev.js gzip 167 kB 168 kB ⚠️ +521 B
app-page-exp..prod.js gzip 93.3 kB 93.8 kB ⚠️ +419 B
app-page-tur..prod.js gzip 94.1 kB 94.5 kB ⚠️ +427 B
app-page-tur..prod.js gzip 88.7 kB 89.1 kB ⚠️ +345 B
app-page.run...dev.js gzip 137 kB 138 kB ⚠️ +649 B
app-page.run..prod.js gzip 88 kB 88.4 kB ⚠️ +343 B
app-route-ex...dev.js gzip 23.8 kB 24.2 kB ⚠️ +409 B
app-route-ex..prod.js gzip 16.4 kB 16.8 kB ⚠️ +402 B
app-route-tu..prod.js gzip 16.4 kB 16.9 kB ⚠️ +403 B
app-route-tu..prod.js gzip 16 kB 16.4 kB ⚠️ +409 B
app-route.ru...dev.js gzip 23.2 kB 23.6 kB ⚠️ +415 B
app-route.ru..prod.js gzip 16 kB 16.4 kB ⚠️ +409 B
pages-api-tu..prod.js gzip 9.37 kB 9.37 kB
pages-api.ru...dev.js gzip 9.64 kB 9.64 kB
pages-api.ru..prod.js gzip 9.37 kB 9.37 kB
pages-turbo...prod.js gzip 21.8 kB 21.9 kB N/A
pages.runtim...dev.js gzip 22.5 kB 22.6 kB N/A
pages.runtim..prod.js gzip 21.8 kB 21.9 kB N/A
server.runti..prod.js gzip 49 kB 49.4 kB ⚠️ +330 B
Overall change 858 kB 863 kB ⚠️ +5.48 kB
Diff details
Diff for page.js

Diff too large to display

Diff for middleware.js

Diff too large to display

Diff for edge-ssr.js

Diff too large to display

Diff for 199-HASH.js

Diff too large to display

Diff for main-HASH.js

Diff too large to display

Diff for app-page-exp..ntime.dev.js
failed to diff
Diff for app-page-exp..time.prod.js

Diff too large to display

Diff for app-page-tur..time.prod.js

Diff too large to display

Diff for app-page-tur..time.prod.js

Diff too large to display

Diff for app-page.runtime.dev.js
failed to diff
Diff for app-page.runtime.prod.js

Diff too large to display

Diff for app-route-ex..ntime.dev.js

Diff too large to display

Diff for app-route-ex..time.prod.js

Diff too large to display

Diff for app-route-tu..time.prod.js

Diff too large to display

Diff for app-route-tu..time.prod.js

Diff too large to display

Diff for app-route.runtime.dev.js

Diff too large to display

Diff for app-route.ru..time.prod.js

Diff too large to display

Diff for pages-turbo...time.prod.js

Diff too large to display

Diff for pages.runtime.dev.js

Diff too large to display

Diff for pages.runtime.prod.js

Diff too large to display

Diff for server.runtime.prod.js

Diff too large to display

Please sign in to comment.