Skip to content

Commit dcff078

Browse files
ijjkhuozhi
andauthored
Re-land build(edge): extract buildId into environment (#65426)
### What * Extract `buildId` and server action encryption key into environment variables for edge to make code more deterministic * Fixed the legacy bad env names from #64108 * Always sort `routes` in prerender manifest for consistent output * Change `environments` to `env` in middleware manifest, confirmed with @javivelasco this is a fine change without need to bumping the version ### Why Dynamic variants like `buildId`, SA `encryptionKey` and preview props are different per build, which results to the non determinstic edge bundles. Once we extracted them into env vars then the bundles become deterministic which give us more space for optimization Closes NEXT-3117 Reverts #65425 Co-authored-by: Jiachi Liu <inbox@huozhi.im>
1 parent 715e157 commit dcff078

File tree

36 files changed

+424
-83
lines changed

36 files changed

+424
-83
lines changed

packages/next-swc/crates/napi/src/next_api/project.rs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use napi::{
99
use next_api::{
1010
entrypoints::Entrypoints,
1111
project::{
12-
DefineEnv, Instrumentation, Middleware, PartialProjectOptions, Project, ProjectContainer,
13-
ProjectOptions,
12+
DefineEnv, DraftModeOptions, Instrumentation, Middleware, PartialProjectOptions, Project,
13+
ProjectContainer, ProjectOptions,
1414
},
1515
route::{Endpoint, Route},
1616
};
@@ -63,6 +63,23 @@ pub struct NapiEnvVar {
6363
pub value: String,
6464
}
6565

66+
#[napi(object)]
67+
pub struct NapiDraftModeOptions {
68+
pub preview_mode_id: String,
69+
pub preview_mode_encryption_key: String,
70+
pub preview_mode_signing_key: String,
71+
}
72+
73+
impl From<NapiDraftModeOptions> for DraftModeOptions {
74+
fn from(val: NapiDraftModeOptions) -> Self {
75+
DraftModeOptions {
76+
preview_mode_id: val.preview_mode_id,
77+
preview_mode_encryption_key: val.preview_mode_encryption_key,
78+
preview_mode_signing_key: val.preview_mode_signing_key,
79+
}
80+
}
81+
}
82+
6683
#[napi(object)]
6784
pub struct NapiProjectOptions {
6885
/// A root path from which all files must be nested under. Trying to access
@@ -94,6 +111,15 @@ pub struct NapiProjectOptions {
94111

95112
/// The mode in which Next.js is running.
96113
pub dev: bool,
114+
115+
/// The server actions encryption key.
116+
pub encryption_key: String,
117+
118+
/// The build id.
119+
pub build_id: String,
120+
121+
/// Options for draft mode.
122+
pub preview_props: NapiDraftModeOptions,
97123
}
98124

99125
/// [NapiProjectOptions] with all fields optional.
@@ -128,6 +154,15 @@ pub struct NapiPartialProjectOptions {
128154

129155
/// The mode in which Next.js is running.
130156
pub dev: Option<bool>,
157+
158+
/// The server actions encryption key.
159+
pub encryption_key: Option<String>,
160+
161+
/// The build id.
162+
pub build_id: Option<String>,
163+
164+
/// Options for draft mode.
165+
pub preview_props: Option<NapiDraftModeOptions>,
131166
}
132167

133168
#[napi(object)]
@@ -159,6 +194,9 @@ impl From<NapiProjectOptions> for ProjectOptions {
159194
.collect(),
160195
define_env: val.define_env.into(),
161196
dev: val.dev,
197+
encryption_key: val.encryption_key,
198+
build_id: val.build_id,
199+
preview_props: val.preview_props.into(),
162200
}
163201
}
164202
}
@@ -176,6 +214,9 @@ impl From<NapiPartialProjectOptions> for PartialProjectOptions {
176214
.map(|env| env.into_iter().map(|var| (var.name, var.value)).collect()),
177215
define_env: val.define_env.map(|env| env.into()),
178216
dev: val.dev,
217+
encryption_key: val.encryption_key,
218+
build_id: val.build_id,
219+
preview_props: val.preview_props.map(|props| props.into()),
179220
}
180221
}
181222
}

packages/next-swc/crates/next-api/src/app.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,7 @@ impl AppEndpoint {
11001100
.clone()
11011101
.map(Regions::Multiple),
11021102
matchers: vec![matchers],
1103+
env: this.app_project.project().edge_env().await?.clone_value(),
11031104
..Default::default()
11041105
};
11051106
let middleware_manifest_v2 = MiddlewaresManifestV2 {

packages/next-swc/crates/next-api/src/middleware.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ impl MiddlewareEndpoint {
161161
page: "/".to_string(),
162162
regions: None,
163163
matchers,
164+
env: this.project.edge_env().await?.clone_value(),
164165
..Default::default()
165166
};
166167
let middleware_manifest_v2 = MiddlewaresManifestV2 {

packages/next-swc/crates/next-api/src/pages.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,7 @@ impl PageEndpoint {
10951095
page: original_name.to_string(),
10961096
regions: None,
10971097
matchers: vec![matchers],
1098+
env: this.pages_project.project().edge_env().await?.clone_value(),
10981099
..Default::default()
10991100
};
11001101
let middleware_manifest_v2 = MiddlewaresManifestV2 {

packages/next-swc/crates/next-api/src/project.rs

Lines changed: 106 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::path::MAIN_SEPARATOR;
22

33
use anyhow::Result;
4-
use indexmap::{map::Entry, IndexMap};
4+
use indexmap::{indexmap, map::Entry, IndexMap};
55
use next_core::{
66
all_assets_from_entries,
77
app_structure::find_app_dir,
@@ -68,6 +68,14 @@ use crate::{
6868
versioned_content_map::{OutputAssetsOperation, VersionedContentMap},
6969
};
7070

71+
#[derive(Debug, Serialize, Deserialize, Clone, TaskInput, PartialEq, Eq, TraceRawVcs)]
72+
#[serde(rename_all = "camelCase")]
73+
pub struct DraftModeOptions {
74+
pub preview_mode_id: String,
75+
pub preview_mode_encryption_key: String,
76+
pub preview_mode_signing_key: String,
77+
}
78+
7179
#[derive(Debug, Serialize, Deserialize, Clone, TaskInput, PartialEq, Eq, TraceRawVcs)]
7280
#[serde(rename_all = "camelCase")]
7381
pub struct ProjectOptions {
@@ -96,6 +104,15 @@ pub struct ProjectOptions {
96104

97105
/// The mode in which Next.js is running.
98106
pub dev: bool,
107+
108+
/// The server actions encryption key.
109+
pub encryption_key: String,
110+
111+
/// The build id.
112+
pub build_id: String,
113+
114+
/// Options for draft mode.
115+
pub preview_props: DraftModeOptions,
99116
}
100117

101118
#[derive(Debug, Serialize, Deserialize, Clone, TaskInput, PartialEq, Eq, TraceRawVcs)]
@@ -126,6 +143,15 @@ pub struct PartialProjectOptions {
126143

127144
/// The mode in which Next.js is running.
128145
pub dev: Option<bool>,
146+
147+
/// The server actions encryption key.
148+
pub encryption_key: Option<String>,
149+
150+
/// The build id.
151+
pub build_id: Option<String>,
152+
153+
/// Options for draft mode.
154+
pub preview_props: Option<DraftModeOptions>,
129155
}
130156

131157
#[derive(Debug, Serialize, Deserialize, Clone, TaskInput, PartialEq, Eq, TraceRawVcs)]
@@ -166,29 +192,55 @@ impl ProjectContainer {
166192

167193
#[turbo_tasks::function]
168194
pub fn update(&self, options: PartialProjectOptions) -> Vc<()> {
195+
let PartialProjectOptions {
196+
root_path,
197+
project_path,
198+
next_config,
199+
js_config,
200+
env,
201+
define_env,
202+
watch,
203+
dev,
204+
encryption_key,
205+
build_id,
206+
preview_props,
207+
} = options;
208+
169209
let mut new_options = self.options_state.get().clone();
170210

171-
if let Some(root_path) = options.root_path {
211+
if let Some(root_path) = root_path {
172212
new_options.root_path = root_path;
173213
}
174-
if let Some(project_path) = options.project_path {
214+
if let Some(project_path) = project_path {
175215
new_options.project_path = project_path;
176216
}
177-
if let Some(next_config) = options.next_config {
217+
if let Some(next_config) = next_config {
178218
new_options.next_config = next_config;
179219
}
180-
if let Some(js_config) = options.js_config {
220+
if let Some(js_config) = js_config {
181221
new_options.js_config = js_config;
182222
}
183-
if let Some(env) = options.env {
223+
if let Some(env) = env {
184224
new_options.env = env;
185225
}
186-
if let Some(define_env) = options.define_env {
226+
if let Some(define_env) = define_env {
187227
new_options.define_env = define_env;
188228
}
189-
if let Some(watch) = options.watch {
229+
if let Some(watch) = watch {
190230
new_options.watch = watch;
191231
}
232+
if let Some(dev) = dev {
233+
new_options.dev = dev;
234+
}
235+
if let Some(encryption_key) = encryption_key {
236+
new_options.encryption_key = encryption_key;
237+
}
238+
if let Some(build_id) = build_id {
239+
new_options.build_id = build_id;
240+
}
241+
if let Some(preview_props) = preview_props {
242+
new_options.preview_props = preview_props;
243+
}
192244

193245
// TODO: Handle mode switch, should prevent mode being switched.
194246

@@ -201,32 +253,36 @@ impl ProjectContainer {
201253
pub async fn project(self: Vc<Self>) -> Result<Vc<Project>> {
202254
let this = self.await?;
203255

204-
let (env, define_env, next_config, js_config, root_path, project_path, watch, dev) = {
256+
let env_map: Vc<EnvMap>;
257+
let next_config;
258+
let define_env;
259+
let js_config;
260+
let root_path;
261+
let project_path;
262+
let watch;
263+
let dev;
264+
let encryption_key;
265+
let build_id;
266+
let preview_props;
267+
{
205268
let options = this.options_state.get();
206-
let env: Vc<EnvMap> = Vc::cell(options.env.iter().cloned().collect());
207-
let define_env: Vc<ProjectDefineEnv> = ProjectDefineEnv {
269+
env_map = Vc::cell(options.env.iter().cloned().collect());
270+
define_env = ProjectDefineEnv {
208271
client: Vc::cell(options.define_env.client.iter().cloned().collect()),
209272
edge: Vc::cell(options.define_env.edge.iter().cloned().collect()),
210273
nodejs: Vc::cell(options.define_env.nodejs.iter().cloned().collect()),
211274
}
212275
.cell();
213-
let next_config = NextConfig::from_string(Vc::cell(options.next_config.clone()));
214-
let js_config = JsConfig::from_string(Vc::cell(options.js_config.clone()));
215-
let root_path = options.root_path.clone();
216-
let project_path = options.project_path.clone();
217-
let watch = options.watch;
218-
let dev = options.dev;
219-
(
220-
env,
221-
define_env,
222-
next_config,
223-
js_config,
224-
root_path,
225-
project_path,
226-
watch,
227-
dev,
228-
)
229-
};
276+
next_config = NextConfig::from_string(Vc::cell(options.next_config.clone()));
277+
js_config = JsConfig::from_string(Vc::cell(options.js_config.clone()));
278+
root_path = options.root_path.clone();
279+
project_path = options.project_path.clone();
280+
watch = options.watch;
281+
dev = options.dev;
282+
encryption_key = options.encryption_key.clone();
283+
build_id = options.build_id.clone();
284+
preview_props = options.preview_props.clone();
285+
}
230286

231287
let dist_dir = next_config
232288
.await?
@@ -241,7 +297,7 @@ impl ProjectContainer {
241297
next_config,
242298
js_config,
243299
dist_dir,
244-
env: Vc::upcast(env),
300+
env: Vc::upcast(env_map),
245301
define_env,
246302
browserslist_query: "last 1 Chrome versions, last 1 Firefox versions, last 1 Safari \
247303
versions, last 1 Edge versions"
@@ -252,6 +308,9 @@ impl ProjectContainer {
252308
NextMode::Build.cell()
253309
},
254310
versioned_content_map: this.versioned_content_map,
311+
build_id,
312+
encryption_key,
313+
preview_props,
255314
}
256315
.cell())
257316
}
@@ -323,6 +382,12 @@ pub struct Project {
323382
mode: Vc<NextMode>,
324383

325384
versioned_content_map: Vc<VersionedContentMap>,
385+
386+
build_id: String,
387+
388+
encryption_key: String,
389+
390+
preview_props: DraftModeOptions,
326391
}
327392

328393
#[turbo_tasks::value]
@@ -545,6 +610,18 @@ impl Project {
545610
))
546611
}
547612

613+
#[turbo_tasks::function]
614+
pub(super) fn edge_env(&self) -> Vc<EnvMap> {
615+
let edge_env = indexmap! {
616+
"__NEXT_BUILD_ID".to_string() => self.build_id.clone(),
617+
"NEXT_SERVER_ACTIONS_ENCRYPTION_KEY".to_string() => self.encryption_key.clone(),
618+
"__NEXT_PREVIEW_MODE_ID".to_string() => self.preview_props.preview_mode_id.clone(),
619+
"__NEXT_PREVIEW_MODE_ENCRYPTION_KEY".to_string() => self.preview_props.preview_mode_encryption_key.clone(),
620+
"__NEXT_PREVIEW_MODE_SIGNING_KEY".to_string() => self.preview_props.preview_mode_signing_key.clone(),
621+
};
622+
Vc::cell(edge_env)
623+
}
624+
548625
#[turbo_tasks::function]
549626
pub(super) async fn client_chunking_context(
550627
self: Vc<Self>,

packages/next-swc/crates/next-core/src/next_app/app_page_entry.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@ async fn wrap_edge_page(
152152
let next_config = &*next_config.await?;
153153

154154
// TODO(WEB-1824): add build support
155-
let build_id = "development";
156155
let dev = true;
157156

158157
// TODO(timneutkens): remove this
@@ -174,7 +173,6 @@ async fn wrap_edge_page(
174173
indexmap! {
175174
"VAR_USERLAND" => INNER.to_string(),
176175
"VAR_PAGE" => page.to_string(),
177-
"VAR_BUILD_ID" => build_id.to_string(),
178176
},
179177
indexmap! {
180178
"sriEnabled" => serde_json::Value::Bool(sri_enabled).to_string(),

packages/next-swc/crates/next-core/src/next_manifests/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pub(crate) mod client_reference_manifest;
44

55
use std::collections::HashMap;
66

7-
use indexmap::IndexSet;
7+
use indexmap::{IndexMap, IndexSet};
88
use serde::{Deserialize, Serialize};
99
use turbo_tasks::{trace::TraceRawVcs, TaskInput};
1010

@@ -88,6 +88,7 @@ pub struct EdgeFunctionDefinition {
8888
pub assets: Vec<AssetBinding>,
8989
#[serde(skip_serializing_if = "Option::is_none")]
9090
pub regions: Option<Regions>,
91+
pub env: IndexMap<String, String>,
9192
}
9293

9394
#[derive(Serialize, Default, Debug)]

packages/next-swc/crates/next-core/src/next_pages/page_entry.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,6 @@ async fn wrap_edge_page(
212212
let next_config = &*next_config.await?;
213213

214214
// TODO(WEB-1824): add build support
215-
let build_id = "development";
216215
let dev = true;
217216

218217
let sri_enabled = !dev
@@ -229,7 +228,6 @@ async fn wrap_edge_page(
229228
indexmap! {
230229
"VAR_USERLAND" => INNER.to_string(),
231230
"VAR_PAGE" => pathname.clone(),
232-
"VAR_BUILD_ID" => build_id.to_string(),
233231
"VAR_MODULE_DOCUMENT" => INNER_DOCUMENT.to_string(),
234232
"VAR_MODULE_APP" => INNER_APP.to_string(),
235233
"VAR_MODULE_GLOBAL_ERROR" => INNER_ERROR.to_string(),

packages/next/src/build/entries.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,6 @@ export function getEdgeServerEntry(opts: {
406406
absoluteDocumentPath: opts.pages['/_document'],
407407
absoluteErrorPath: opts.pages['/_error'],
408408
absolutePagePath: opts.absolutePagePath,
409-
buildId: opts.buildId,
410409
dev: opts.isDev,
411410
isServerComponent: opts.isServerComponent,
412411
page: opts.page,

0 commit comments

Comments
 (0)