Skip to content

Commit eeb211f

Browse files
robhoganfacebook-github-bot
authored andcommitted
Include additional build parameters in file map cache key
Summary: We inherited cache key components from `jest-haste-map` in https://github.com/facebook/metro/blob/13f06bd14da0d307eea1599f5d2f86c0a30f711e/packages/metro-file-map/src/index.js#L334-L355, and I did some reorganising in c7fc436 to attempt to separate "build parameters" - config that would change the built output - from other options. There are a few old options that were in build parameters but never part of the cache key - they should've been. This diff adds them. - `enableSymlinks` (determines whether crawlers include symlinks) - `retainAllFiles` (determines whether `node_modules` files are ignored) - `skipPackageJson` (determines whether `package.json` files are "processed" for Haste packages) Of these, only `enableSymlinks` is configurable via Metro config, through `resolver.unstable_enableSymlinks`. The other two are internal-only. Additionally: - Refactor `rootRelativeCacheKeys` so that Flow will alert us to any missed keys. - Add a test verifying that we get a different hash for any variation in config. Changelog: * **Fix:** Include `resolver.unstable_enableSymlinks` in file map cache key. Reviewed By: motiz88 Differential Revision: D43624928 fbshipit-source-id: bca285bda21796fea4a8664a6f71f92591412c4c
1 parent 2303c10 commit eeb211f

File tree

2 files changed

+160
-18
lines changed

2 files changed

+160
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
import type {BuildParameters} from '../../flow-types';
12+
13+
import rootRelativeCacheKeys from '../rootRelativeCacheKeys';
14+
15+
const buildParameters: BuildParameters = {
16+
computeDependencies: false,
17+
computeSha1: false,
18+
dependencyExtractor: null,
19+
enableSymlinks: false,
20+
extensions: ['a'],
21+
forceNodeFilesystemAPI: false,
22+
hasteImplModulePath: null,
23+
ignorePattern: /a/,
24+
mocksPattern: /a/,
25+
platforms: ['a'],
26+
retainAllFiles: false,
27+
rootDir: '/root',
28+
roots: ['a', 'b'],
29+
skipPackageJson: false,
30+
cacheBreaker: 'a',
31+
};
32+
33+
jest.mock(
34+
'/haste/1',
35+
() => ({
36+
getCacheKey: () => 'haste/1',
37+
}),
38+
{virtual: true},
39+
);
40+
jest.mock(
41+
'/haste/2',
42+
() => ({
43+
getCacheKey: () => 'haste/2',
44+
}),
45+
{virtual: true},
46+
);
47+
jest.mock(
48+
'/extractor/1',
49+
() => ({
50+
getCacheKey: () => 'extractor/1',
51+
}),
52+
{virtual: true},
53+
);
54+
jest.mock(
55+
'/extractor/2',
56+
() => ({
57+
getCacheKey: () => 'extractor/2',
58+
}),
59+
{virtual: true},
60+
);
61+
62+
it('returns a distinct cache key for any change', () => {
63+
const {
64+
hasteImplModulePath: _,
65+
dependencyExtractor: __,
66+
rootDir: ___,
67+
...simpleParameters
68+
} = buildParameters;
69+
70+
const varyDefault = <T: $Keys<typeof simpleParameters>>(
71+
key: T,
72+
newVal: BuildParameters[T],
73+
): BuildParameters => {
74+
// $FlowFixMe[invalid-computed-prop] Can't use a union for a computed prop
75+
return {...buildParameters, [key]: newVal};
76+
};
77+
78+
const configs = Object.keys(simpleParameters).map(key => {
79+
switch (key) {
80+
// Boolean
81+
case 'computeDependencies':
82+
case 'computeSha1':
83+
case 'enableSymlinks':
84+
case 'forceNodeFilesystemAPI':
85+
case 'retainAllFiles':
86+
case 'skipPackageJson':
87+
return varyDefault(key, !buildParameters[key]);
88+
// Strings
89+
case 'cacheBreaker':
90+
return varyDefault(key, 'foo');
91+
// String arrays
92+
case 'extensions':
93+
case 'platforms':
94+
case 'roots':
95+
return varyDefault(key, ['foo']);
96+
// Regexp
97+
case 'mocksPattern':
98+
case 'ignorePattern':
99+
return varyDefault(key, /foo/);
100+
default:
101+
(key: empty);
102+
throw new Error('Unrecognised key in build parameters: ' + key);
103+
}
104+
});
105+
configs.push(buildParameters);
106+
configs.push({...buildParameters, dependencyExtractor: '/extractor/1'});
107+
configs.push({...buildParameters, dependencyExtractor: '/extractor/2'});
108+
configs.push({...buildParameters, hasteImplModulePath: '/haste/1'});
109+
configs.push({...buildParameters, hasteImplModulePath: '/haste/2'});
110+
111+
// Generate hashes for each config
112+
const configHashes = configs.map(
113+
config => rootRelativeCacheKeys(config).relativeConfigHash,
114+
);
115+
116+
// We expect them all to have distinct hashes
117+
const seen = new Map<string, number>();
118+
for (const [i, configHash] of configHashes.entries()) {
119+
const seenIndex = seen.get(configHash);
120+
if (seenIndex != null) {
121+
// Two configs have the same hash - let Jest print the differences
122+
expect(configs[seenIndex]).toEqual(configs[i]);
123+
}
124+
seen.set(configHash, i);
125+
}
126+
});

packages/metro-file-map/src/lib/rootRelativeCacheKeys.js

+34-18
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,45 @@ export default function rootRelativeCacheKeys(
3636
rootDirHash: string,
3737
relativeConfigHash: string,
3838
} {
39-
const rootDirHash = createHash('md5')
40-
.update(buildParameters.rootDir)
41-
.digest('hex');
39+
const {rootDir, ...otherParameters} = buildParameters;
40+
const rootDirHash = createHash('md5').update(rootDir).digest('hex');
41+
42+
const cacheComponents = Object.keys(otherParameters)
43+
.sort()
44+
.map(key => {
45+
switch (key) {
46+
case 'roots':
47+
return buildParameters[key].map(root =>
48+
fastPath.relative(rootDir, root),
49+
);
50+
case 'cacheBreaker':
51+
case 'extensions':
52+
case 'computeDependencies':
53+
case 'computeSha1':
54+
case 'enableSymlinks':
55+
case 'forceNodeFilesystemAPI':
56+
case 'platforms':
57+
case 'retainAllFiles':
58+
case 'skipPackageJson':
59+
return buildParameters[key] ?? null;
60+
case 'mocksPattern':
61+
return buildParameters[key]?.toString() ?? null;
62+
case 'ignorePattern':
63+
return buildParameters[key].toString();
64+
case 'hasteImplModulePath':
65+
case 'dependencyExtractor':
66+
return moduleCacheKey(buildParameters[key]);
67+
default:
68+
(key: empty);
69+
throw new Error('Unrecognised key in build parameters: ' + key);
70+
}
71+
});
4272

4373
// JSON.stringify is stable here because we only deal in (nested) arrays of
4474
// primitives. Use a different approach if this is expanded to include
4575
// objects/Sets/Maps, etc.
46-
const serializedConfig = JSON.stringify([
47-
buildParameters.roots.map(root =>
48-
fastPath.relative(buildParameters.rootDir, root),
49-
),
50-
buildParameters.extensions,
51-
buildParameters.platforms,
52-
buildParameters.computeSha1,
53-
buildParameters.mocksPattern?.toString() ?? null,
54-
buildParameters.ignorePattern.toString(),
55-
moduleCacheKey(buildParameters.hasteImplModulePath),
56-
moduleCacheKey(buildParameters.dependencyExtractor),
57-
buildParameters.computeDependencies,
58-
buildParameters.cacheBreaker,
59-
]);
6076
const relativeConfigHash = createHash('md5')
61-
.update(serializedConfig)
77+
.update(JSON.stringify(cacheComponents))
6278
.digest('hex');
6379

6480
return {

0 commit comments

Comments
 (0)