Skip to content

Commit 32dd056

Browse files
authored
[turbopack] move edge entry wrapper to build template (#86699)
Move the edge-wrapper virtual module to templates
1 parent 4795a00 commit 32dd056

File tree

4 files changed

+125
-47
lines changed

4 files changed

+125
-47
lines changed

crates/next-core/src/next_edge/entry.rs

Lines changed: 19 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,39 @@
11
use anyhow::Result;
2-
use indoc::formatdoc;
32
use turbo_rcstr::{RcStr, rcstr};
43
use turbo_tasks::{ResolvedVc, Vc, fxindexmap};
5-
use turbo_tasks_fs::{File, FileContent, FileSystemPath};
6-
use turbopack_core::{
7-
asset::AssetContent, context::AssetContext, module::Module, reference_type::ReferenceType,
8-
virtual_source::VirtualSource,
9-
};
10-
use turbopack_ecmascript::utils::StringifyJs;
4+
use turbo_tasks_fs::FileSystemPath;
5+
use turbopack_core::{context::AssetContext, module::Module, reference_type::ReferenceType};
6+
7+
use crate::util::load_next_js_template_no_imports;
118

129
#[turbo_tasks::function]
13-
pub fn wrap_edge_entry(
10+
pub async fn wrap_edge_entry(
1411
asset_context: Vc<Box<dyn AssetContext>>,
1512
project_root: FileSystemPath,
1613
entry: ResolvedVc<Box<dyn Module>>,
1714
pathname: RcStr,
1815
) -> Result<Vc<Box<dyn Module>>> {
19-
// The wrapped module could be an async module, we handle that with the proxy
20-
// here. The comma expression makes sure we don't call the function with the
21-
// module as the "this" arg.
22-
// Turn exports into functions that are also a thenable. This way you can await the whole object
23-
// or exports (e.g. for Components) or call them directly as though they are async functions
24-
// (e.g. edge functions/middleware, this is what the Edge Runtime does).
25-
// Catch promise to prevent UnhandledPromiseRejectionWarning, this will be propagated through
26-
// the awaited export(s) anyway.
27-
let source = formatdoc!(
28-
r#"
29-
self._ENTRIES ||= {{}};
30-
const modProm = import('MODULE');
31-
modProm.catch(() => {{}});
32-
self._ENTRIES[{}] = new Proxy(modProm, {{
33-
get(modProm, name) {{
34-
if (name === "then") {{
35-
return (res, rej) => modProm.then(res, rej);
36-
}}
37-
let result = (...args) => modProm.then((mod) => (0, mod[name])(...args));
38-
result.then = (res, rej) => modProm.then((mod) => mod[name]).then(res, rej);
39-
return result;
40-
}},
41-
}});
42-
"#,
43-
StringifyJs(&format_args!("middleware_{pathname}"))
44-
);
45-
let file = File::from(source);
46-
47-
// TODO(alexkirsz) Figure out how to name this virtual asset.
48-
let virtual_source = VirtualSource::new(
49-
project_root.join("edge-wrapper.js")?,
50-
AssetContent::file(FileContent::Content(file).cell()),
51-
);
16+
// The actual wrapper lives in the Next.js templates directory as `edge-wrapper.js`.
17+
// We use the template expansion helper so this code is kept in sync with other
18+
// Next.js runtime templates. This particular template does not have any imports
19+
// of its own, so we use the variant that allows templates without relative
20+
// imports to be rewritten.
21+
let template_source = load_next_js_template_no_imports(
22+
"edge-wrapper.js",
23+
project_root,
24+
&[("VAR_ENTRY_NAME", &format!("middleware_{pathname}"))],
25+
&[],
26+
&[],
27+
)
28+
.await?;
5229

5330
let inner_assets = fxindexmap! {
5431
rcstr!("MODULE") => entry
5532
};
5633

5734
Ok(asset_context
5835
.process(
59-
Vc::upcast(virtual_source),
36+
template_source,
6037
ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
6138
)
6239
.module())

crates/next-core/src/util.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{fmt::Display, str::FromStr};
22

33
use anyhow::{Result, anyhow, bail};
4-
use next_taskless::expand_next_js_template;
4+
use next_taskless::{expand_next_js_template, expand_next_js_template_no_imports};
55
use serde::{Deserialize, Serialize, de::DeserializeOwned};
66
use turbo_rcstr::{RcStr, rcstr};
77
use turbo_tasks::{FxIndexMap, NonLocalValue, TaskInput, Vc, trace::TraceRawVcs};
@@ -268,6 +268,41 @@ pub async fn load_next_js_template(
268268
Ok(Vc::upcast(source))
269269
}
270270

271+
/// Loads a next.js template but does **not** require that any relative imports are present
272+
/// or rewritten. This is intended for small internal templates that do not have their own
273+
/// imports but still use template variables/injections.
274+
pub async fn load_next_js_template_no_imports(
275+
template_path: &str,
276+
project_path: FileSystemPath,
277+
replacements: &[(&str, &str)],
278+
injections: &[(&str, &str)],
279+
imports: &[(&str, Option<&str>)],
280+
) -> Result<Vc<Box<dyn Source>>> {
281+
let template_path = virtual_next_js_template_path(project_path.clone(), template_path).await?;
282+
283+
let content = file_content_rope(template_path.read()).await?;
284+
let content = content.to_str()?;
285+
286+
let package_root = get_next_package(project_path).await?;
287+
288+
let content = expand_next_js_template_no_imports(
289+
&content,
290+
&template_path.path,
291+
&package_root.path,
292+
replacements.iter().copied(),
293+
injections.iter().copied(),
294+
imports.iter().copied(),
295+
)?;
296+
297+
let file = File::from(content);
298+
let source = VirtualSource::new(
299+
template_path,
300+
AssetContent::file(FileContent::Content(file).cell()),
301+
);
302+
303+
Ok(Vc::upcast(source))
304+
}
305+
271306
#[turbo_tasks::function]
272307
pub async fn file_content_rope(content: Vc<FileContent>) -> Result<Vc<Rope>> {
273308
let content = &*content.await?;

crates/next-taskless/src/lib.rs

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,48 @@ pub fn expand_next_js_template<'a>(
2525
replacements: impl IntoIterator<Item = (&'a str, &'a str)>,
2626
injections: impl IntoIterator<Item = (&'a str, &'a str)>,
2727
imports: impl IntoIterator<Item = (&'a str, Option<&'a str>)>,
28+
) -> Result<String> {
29+
expand_next_js_template_inner(
30+
content,
31+
template_path,
32+
next_package_dir_path,
33+
replacements,
34+
injections,
35+
imports,
36+
true,
37+
)
38+
}
39+
40+
/// Same as [`expand_next_js_template`], but does not enforce that at least one relative
41+
/// import is present and rewritten. This is useful for very small templates that only
42+
/// use template variables/injections and have no imports of their own.
43+
pub fn expand_next_js_template_no_imports<'a>(
44+
content: &str,
45+
template_path: &str,
46+
next_package_dir_path: &str,
47+
replacements: impl IntoIterator<Item = (&'a str, &'a str)>,
48+
injections: impl IntoIterator<Item = (&'a str, &'a str)>,
49+
imports: impl IntoIterator<Item = (&'a str, Option<&'a str>)>,
50+
) -> Result<String> {
51+
expand_next_js_template_inner(
52+
content,
53+
template_path,
54+
next_package_dir_path,
55+
replacements,
56+
injections,
57+
imports,
58+
false,
59+
)
60+
}
61+
62+
fn expand_next_js_template_inner<'a>(
63+
content: &str,
64+
template_path: &str,
65+
next_package_dir_path: &str,
66+
replacements: impl IntoIterator<Item = (&'a str, &'a str)>,
67+
injections: impl IntoIterator<Item = (&'a str, &'a str)>,
68+
imports: impl IntoIterator<Item = (&'a str, Option<&'a str>)>,
69+
require_import_replacement: bool,
2870
) -> Result<String> {
2971
let template_parent_path = normalize_path(get_parent_path(template_path))
3072
.context("failed to normalize template path")?;
@@ -92,10 +134,11 @@ pub fn expand_next_js_template<'a>(
92134
})
93135
.context("replacing imports failed")?;
94136

95-
// Verify that at least one import was replaced. It's the case today where every template file
96-
// has at least one import to update, so this ensures that we don't accidentally remove the
97-
// import replacement code or use the wrong template file.
98-
if count == 0 {
137+
// Verify that at least one import was replaced when required. It's the case today where every
138+
// template file (except a few small internal helpers) has at least one import to update, so
139+
// this ensures that we don't accidentally remove the import replacement code or use the wrong
140+
// template file.
141+
if require_import_replacement && count == 0 {
99142
bail!("Invariant: Expected to replace at least one import")
100143
}
101144

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// The wrapped module could be an async module, we handle that with the proxy
2+
// here. The comma expression makes sure we don't call the function with the
3+
// module as the "this" arg.
4+
// Turn exports into functions that are also a thenable. This way you can await the whole object
5+
// or exports (e.g. for Components) or call them directly as though they are async functions
6+
// (e.g. edge functions/middleware, this is what the Edge Runtime does).
7+
// Catch promise to prevent UnhandledPromiseRejectionWarning, this will be propagated through
8+
// the awaited export(s) anyway.
9+
self._ENTRIES ||= {}
10+
const modProm = import('MODULE')
11+
modProm.catch(() => {})
12+
self._ENTRIES['VAR_ENTRY_NAME'] = new Proxy(modProm, {
13+
get(innerModProm, name) {
14+
if (name === 'then') {
15+
return (res, rej) => innerModProm.then(res, rej)
16+
}
17+
let result = (...args) =>
18+
innerModProm.then((mod) => (0, mod[name])(...args))
19+
result.then = (res, rej) =>
20+
innerModProm.then((mod) => mod[name]).then(res, rej)
21+
return result
22+
},
23+
})

0 commit comments

Comments
 (0)