Skip to content

Commit 7e5e69d

Browse files
authored
Turbopack: deterministic server action order (#76905)
`HashMap` strikes again, causing the server action manifest order to be non-deterministic. Less test flakeyness for `app-dir - server-action-period-hash-custom-key should have different manifest if the encryption key from process env is same`
1 parent 7d62789 commit 7e5e69d

File tree

4 files changed

+24
-55
lines changed

4 files changed

+24
-55
lines changed

crates/next-api/src/module_graph.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ impl ServerActionsGraph {
213213
return Ok(Vc::cell(Default::default()));
214214
}
215215

216-
let mut result = FxHashMap::default();
216+
let mut result = FxIndexMap::default();
217217
graph.traverse_from_entry(entry, |node| {
218218
if let Some(node_data) = data.get(&node.module) {
219219
result.insert(node.module, *node_data);

crates/next-api/src/server_actions.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use next_core::{
77
},
88
util::NextRuntime,
99
};
10-
use rustc_hash::FxHashMap;
1110
use swc_core::{
1211
atoms::Atom,
1312
common::comments::Comments,
@@ -406,7 +405,7 @@ struct OptionActionMap(Option<ResolvedVc<ActionMap>>);
406405
type LayerAndActions = (ActionLayer, ResolvedVc<ActionMap>);
407406
/// A mapping of every module module containing Server Actions, mapping to its layer and actions.
408407
#[turbo_tasks::value(transparent)]
409-
pub struct AllModuleActions(FxHashMap<ResolvedVc<Box<dyn Module>>, LayerAndActions>);
408+
pub struct AllModuleActions(FxIndexMap<ResolvedVc<Box<dyn Module>>, LayerAndActions>);
410409

411410
#[turbo_tasks::function]
412411
pub async fn map_server_actions(graph: Vc<SingleModuleGraph>) -> Result<Vc<AllModuleActions>> {
Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,10 @@
1-
import { nextTestSetup } from 'e2e-utils'
1+
import { nextTestSetup, type NextInstance } from 'e2e-utils'
22

3-
async function getServerActionManifest(next) {
4-
const content = await next.readFile(
3+
async function getServerActionManifestNodeKeys(next: NextInstance) {
4+
const manifest = await next.readJSON(
55
'.next/server/server-reference-manifest.json'
66
)
7-
return JSON.parse(content)
8-
}
9-
10-
function compareServerActionManifestKeys(a, b, equal) {
11-
a = a.node
12-
b = b.node
13-
14-
const keysA = Object.keys(a)
15-
const keysB = Object.keys(b)
16-
17-
if (equal) {
18-
expect(keysA).toEqual(keysB)
19-
} else {
20-
expect(keysA).not.toEqual(keysB)
21-
}
7+
return Object.keys(manifest.node)
228
}
239

2410
describe('app-dir - server-action-period-hash-custom-key', () => {
@@ -27,32 +13,30 @@ describe('app-dir - server-action-period-hash-custom-key', () => {
2713
skipStart: true,
2814
})
2915

30-
it('should have different manifest if the encryption key from process env is changed', async () => {
16+
it('should have a different manifest if the encryption key from process env is changed', async () => {
3117
process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY = 'my-secret-key1'
3218
await next.build()
3319
delete process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
34-
const firstManifest = await getServerActionManifest(next)
20+
const firstActionIds = await getServerActionManifestNodeKeys(next)
3521

3622
process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY = 'my-secret-key2'
3723
await next.build()
3824
delete process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
25+
const secondActionIds = await getServerActionManifestNodeKeys(next)
3926

40-
const secondManifest = await getServerActionManifest(next)
41-
42-
compareServerActionManifestKeys(firstManifest, secondManifest, false)
27+
expect(firstActionIds).not.toEqual(secondActionIds)
4328
})
4429

45-
it('should have different manifest if the encryption key from process env is same', async () => {
30+
it('should have the same manifest if the encryption key from process env is same', async () => {
4631
process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY = 'my-secret-key'
4732
await next.build()
48-
const firstManifest = await getServerActionManifest(next)
33+
const firstActionIds = await getServerActionManifestNodeKeys(next)
4934

5035
await next.remove('.next') // dismiss cache
5136
await next.build() // build with the same secret key
5237
delete process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
38+
const secondActionIds = await getServerActionManifestNodeKeys(next)
5339

54-
const secondManifest = await getServerActionManifest(next)
55-
56-
compareServerActionManifestKeys(firstManifest, secondManifest, true)
40+
expect(firstActionIds).toEqual(secondActionIds)
5741
})
5842
})
Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,10 @@
1-
import { nextTestSetup } from 'e2e-utils'
1+
import { nextTestSetup, type NextInstance } from 'e2e-utils'
22

3-
async function getServerActionManifest(next) {
4-
const content = await next.readFile(
3+
async function getServerActionManifestNodeKeys(next: NextInstance) {
4+
const manifest = await next.readJSON(
55
'.next/server/server-reference-manifest.json'
66
)
7-
return JSON.parse(content)
8-
}
9-
10-
function compareServerActionManifestKeys(a, b, equal) {
11-
a = a.node
12-
b = b.node
13-
14-
const keysA = Object.keys(a)
15-
const keysB = Object.keys(b)
16-
17-
if (equal) {
18-
expect(keysA).toEqual(keysB)
19-
} else {
20-
expect(keysA).not.toEqual(keysB)
21-
}
7+
return Object.keys(manifest.node)
228
}
239

2410
describe('app-dir - server-action-period-hash', () => {
@@ -29,23 +15,23 @@ describe('app-dir - server-action-period-hash', () => {
2915

3016
it('should have same manifest between continuous two builds', async () => {
3117
await next.build()
32-
const firstManifest = await getServerActionManifest(next)
18+
const firstActionIds = await getServerActionManifestNodeKeys(next)
3319

3420
await next.build()
35-
const secondManifest = await getServerActionManifest(next)
21+
const secondActionIds = await getServerActionManifestNodeKeys(next)
3622

37-
compareServerActionManifestKeys(firstManifest, secondManifest, true)
23+
expect(firstActionIds).toEqual(secondActionIds)
3824
})
3925

4026
it('should have different manifest between two builds with period hash', async () => {
4127
await next.build()
42-
const firstManifest = await getServerActionManifest(next)
28+
const firstActionIds = await getServerActionManifestNodeKeys(next)
4329

4430
await next.remove('.next') // dismiss cache
4531
await next.build()
4632

47-
const secondManifest = await getServerActionManifest(next)
33+
const secondActionIds = await getServerActionManifestNodeKeys(next)
4834

49-
compareServerActionManifestKeys(firstManifest, secondManifest, false)
35+
expect(firstActionIds).not.toEqual(secondActionIds)
5036
})
5137
})

0 commit comments

Comments
 (0)