metro 0.72.0 → 0.72.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/package.json +21 -21
  2. package/src/Bundler.js +11 -2
  3. package/src/Bundler.js.flow +7 -1
  4. package/src/DeltaBundler/DeltaCalculator.js +83 -21
  5. package/src/DeltaBundler/DeltaCalculator.js.flow +61 -8
  6. package/src/DeltaBundler/Serializers/hmrJSBundle.js.flow +1 -3
  7. package/src/DeltaBundler/Transformer.js +27 -4
  8. package/src/DeltaBundler/Transformer.js.flow +18 -2
  9. package/src/DeltaBundler/Worker.flow.js +45 -1
  10. package/src/DeltaBundler/Worker.flow.js.flow +42 -1
  11. package/src/DeltaBundler/WorkerFarm.js +3 -2
  12. package/src/DeltaBundler/WorkerFarm.js.flow +3 -1
  13. package/src/DeltaBundler/graphOperations.js +131 -20
  14. package/src/DeltaBundler/graphOperations.js.flow +106 -17
  15. package/src/DeltaBundler/types.flow.js.flow +6 -3
  16. package/src/HmrServer.js +2 -0
  17. package/src/HmrServer.js.flow +2 -0
  18. package/src/IncrementalBundler.js +6 -0
  19. package/src/IncrementalBundler.js.flow +6 -0
  20. package/src/ModuleGraph/node-haste/HasteFS.js.flow +1 -1
  21. package/src/ModuleGraph/node-haste/node-haste.js +2 -4
  22. package/src/ModuleGraph/node-haste/node-haste.js.flow +1 -3
  23. package/src/ModuleGraph/output/indexed-ram-bundle.js.flow +5 -13
  24. package/src/ModuleGraph/output/multiple-files-ram-bundle.js.flow +4 -14
  25. package/src/ModuleGraph/output/util.js.flow +1 -1
  26. package/src/ModuleGraph/worker/collectDependencies.js.flow +1 -1
  27. package/src/Server.js +4 -0
  28. package/src/Server.js.flow +37 -10
  29. package/src/integration_tests/basic_bundle/require-context/conflict.js +25 -0
  30. package/src/integration_tests/basic_bundle/require-context/conflict.js.flow +27 -0
  31. package/src/integration_tests/basic_bundle/require-context/empty.js +29 -0
  32. package/src/integration_tests/basic_bundle/require-context/empty.js.flow +26 -0
  33. package/src/integration_tests/basic_bundle/require-context/matching.js +26 -0
  34. package/src/integration_tests/basic_bundle/require-context/matching.js.flow +27 -0
  35. package/src/integration_tests/basic_bundle/require-context/mode-eager.js +22 -0
  36. package/src/integration_tests/basic_bundle/require-context/mode-eager.js.flow +24 -0
  37. package/src/integration_tests/basic_bundle/require-context/mode-lazy-once.js +22 -0
  38. package/src/integration_tests/basic_bundle/require-context/mode-lazy-once.js.flow +24 -0
  39. package/src/integration_tests/basic_bundle/require-context/mode-lazy.js +22 -0
  40. package/src/integration_tests/basic_bundle/require-context/mode-lazy.js.flow +24 -0
  41. package/src/integration_tests/basic_bundle/require-context/mode-sync.js +20 -0
  42. package/src/integration_tests/basic_bundle/require-context/mode-sync.js.flow +22 -0
  43. package/src/integration_tests/basic_bundle/require-context/subdir/a.js +12 -0
  44. package/src/integration_tests/basic_bundle/require-context/subdir/a.js.flow +11 -0
  45. package/src/integration_tests/basic_bundle/require-context/subdir/b.js +18 -0
  46. package/src/integration_tests/basic_bundle/require-context/subdir/b.js.flow +11 -0
  47. package/src/integration_tests/basic_bundle/require-context/subdir/c.js +12 -0
  48. package/src/integration_tests/basic_bundle/require-context/subdir/c.js.flow +11 -0
  49. package/src/integration_tests/basic_bundle/require-context/subdir/nested/d.js +12 -0
  50. package/src/integration_tests/basic_bundle/require-context/subdir/nested/d.js.flow +11 -0
  51. package/src/integration_tests/basic_bundle/require-context/subdir-conflict/index.js +12 -0
  52. package/src/integration_tests/basic_bundle/require-context/subdir-conflict/index.js.flow +11 -0
  53. package/src/integration_tests/basic_bundle/require-context/utils.js +29 -0
  54. package/src/integration_tests/basic_bundle/require-context/utils.js.flow +44 -0
  55. package/src/lib/CountingSet.js +1 -0
  56. package/src/lib/CountingSet.js.flow +1 -0
  57. package/src/lib/contextModule.js +80 -0
  58. package/src/lib/contextModule.js.flow +86 -0
  59. package/src/lib/contextModuleTemplates.js +186 -0
  60. package/src/lib/contextModuleTemplates.js.flow +148 -0
  61. package/src/lib/getGraphId.js +2 -1
  62. package/src/lib/getGraphId.js.flow +3 -0
  63. package/src/lib/getPrependedScripts.js +2 -0
  64. package/src/lib/getPrependedScripts.js.flow +2 -0
  65. package/src/lib/parseOptionsFromUrl.js.flow +7 -18
  66. package/src/lib/transformHelpers.js +41 -9
  67. package/src/lib/transformHelpers.js.flow +46 -9
  68. package/src/node-haste/DependencyGraph/ModuleResolution.js +1 -0
  69. package/src/node-haste/DependencyGraph/ModuleResolution.js.flow +3 -2
  70. package/src/node-haste/DependencyGraph.js +5 -0
  71. package/src/node-haste/DependencyGraph.js.flow +14 -1
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ * @format
10
+ *
11
+ */
12
+ module.exports = "contents of subdir-conflict/index.js";
@@ -0,0 +1,11 @@
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
+ * @format
8
+ * @flow strict
9
+ */
10
+
11
+ module.exports = 'contents of subdir-conflict/index.js';
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true,
5
+ });
6
+ exports.copyContextToObject = copyContextToObject;
7
+ exports.awaitProperties = awaitProperties;
8
+
9
+ /**
10
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
11
+ *
12
+ * This source code is licensed under the MIT license found in the
13
+ * LICENSE file in the root directory of this source tree.
14
+ *
15
+ * @format
16
+ *
17
+ */
18
+ function copyContextToObject(ctx) {
19
+ return Object.fromEntries(ctx.keys().map((key) => [key, ctx(key)]));
20
+ }
21
+
22
+ function awaitProperties(obj) {
23
+ const result = {};
24
+ return Promise.all(
25
+ Object.keys(obj).map((key) => {
26
+ return obj[key].then((value) => (result[key] = value));
27
+ })
28
+ ).then(() => result);
29
+ }
@@ -0,0 +1,44 @@
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
+ * @format
8
+ * @flow
9
+ */
10
+
11
+ type ContextModule<T> = {
12
+ (key: string): T,
13
+ keys(): Array<string>,
14
+ };
15
+
16
+ export type RequireWithContext = {
17
+ (id: string): any,
18
+ resolve: (id: string, options?: {paths?: Array<string>, ...}) => string,
19
+ cache: any,
20
+ main: typeof module,
21
+ context<T>(
22
+ name: string,
23
+ recursive?: boolean,
24
+ filter?: RegExp,
25
+ mode?: 'sync' | 'eager' | 'lazy' | 'lazy-once',
26
+ ): ContextModule<T>,
27
+ };
28
+
29
+ export function copyContextToObject<T>(ctx: ContextModule<T>): {
30
+ [key: string]: T,
31
+ } {
32
+ return Object.fromEntries(ctx.keys().map(key => [key, ctx(key)]));
33
+ }
34
+
35
+ export function awaitProperties<T>(
36
+ obj: $ReadOnly<{[key: string]: Promise<T>}>,
37
+ ): Promise<{[key: string]: T}> {
38
+ const result = {};
39
+ return Promise.all(
40
+ Object.keys(obj).map(key => {
41
+ return obj[key].then(value => (result[key] = value));
42
+ }),
43
+ ).then(() => result);
44
+ }
@@ -68,6 +68,7 @@ class CountingSet {
68
68
  }
69
69
  } // Iterate over unique entries
70
70
  // $FlowIssue[unsupported-syntax]
71
+ // $FlowFixMe[missing-local-annot]
71
72
 
72
73
  [Symbol.iterator]() {
73
74
  return this.values();
@@ -81,6 +81,7 @@ export default class CountingSet<T> implements ReadOnlyCountingSet<T> {
81
81
 
82
82
  // Iterate over unique entries
83
83
  // $FlowIssue[unsupported-syntax]
84
+ // $FlowFixMe[missing-local-annot]
84
85
  [Symbol.iterator](): Iterator<T> {
85
86
  return this.values();
86
87
  }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true,
5
+ });
6
+ exports.deriveAbsolutePathFromContext = deriveAbsolutePathFromContext;
7
+ exports.fileMatchesContext = fileMatchesContext;
8
+
9
+ var _crypto = _interopRequireDefault(require("crypto"));
10
+
11
+ var _path = _interopRequireDefault(require("path"));
12
+
13
+ var _nullthrows = _interopRequireDefault(require("nullthrows"));
14
+
15
+ function _interopRequireDefault(obj) {
16
+ return obj && obj.__esModule ? obj : { default: obj };
17
+ }
18
+
19
+ /**
20
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
21
+ *
22
+ * This source code is licensed under the MIT license found in the
23
+ * LICENSE file in the root directory of this source tree.
24
+ *
25
+ *
26
+ * @format
27
+ */
28
+ function toHash(value) {
29
+ // Use `hex` to ensure filepath safety.
30
+ return _crypto.default.createHash("sha1").update(value).digest("hex");
31
+ }
32
+ /** Given a fully qualified require context, return a virtual file path that ensures uniqueness between paths with different contexts. */
33
+
34
+ function deriveAbsolutePathFromContext(from, context) {
35
+ // Drop the trailing slash, require.context should always be matched against a folder
36
+ // and we want to normalize the folder name as much as possible to prevent duplicates.
37
+ // This also makes the files show up in the correct location when debugging in Chrome.
38
+ const filePath = from.endsWith(_path.default.sep) ? from.slice(0, -1) : from;
39
+ return (
40
+ filePath +
41
+ "?ctx=" +
42
+ toHash(
43
+ [
44
+ context.mode,
45
+ context.recursive ? "recursive" : "",
46
+ new RegExp(context.filter.pattern, context.filter.flags).toString(),
47
+ ]
48
+ .filter(Boolean)
49
+ .join(" ")
50
+ )
51
+ );
52
+ }
53
+ /** Match a file against a require context. */
54
+
55
+ function fileMatchesContext(testPath, context) {
56
+ // NOTE(EvanBacon): Ensure this logic is synchronized with the similar
57
+ // functionality in `metro-file-map/src/HasteFS.js` (`matchFilesWithContext()`)
58
+ const filePath = _path.default.relative(
59
+ (0, _nullthrows.default)(context.from),
60
+ testPath
61
+ );
62
+
63
+ const filter = context.filter;
64
+
65
+ if (
66
+ // Ignore everything outside of the provided `root`.
67
+ !(filePath && !filePath.startsWith("..")) || // Prevent searching in child directories during a non-recursive search.
68
+ (!context.recursive && filePath.includes(_path.default.sep)) || // Test against the filter.
69
+ !filter.test(
70
+ // NOTE(EvanBacon): Ensure files start with `./` for matching purposes
71
+ // this ensures packages work across Metro and Webpack (ex: Storybook for React DOM / React Native).
72
+ // `a/b.js` -> `./a/b.js`
73
+ "./" + filePath.replace(/\\/g, "/")
74
+ )
75
+ ) {
76
+ return false;
77
+ }
78
+
79
+ return true;
80
+ }
@@ -0,0 +1,86 @@
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 crypto from 'crypto';
12
+ import path from 'path';
13
+ import type {
14
+ ContextMode,
15
+ RequireContextParams,
16
+ } from '../ModuleGraph/worker/collectDependencies';
17
+ import nullthrows from 'nullthrows';
18
+
19
+ export type RequireContext = $ReadOnly<{
20
+ /* Should search for files recursively. Optional, default `true` when `require.context` is used */
21
+ recursive: boolean,
22
+ /* Filename filter pattern for use in `require.context`. Optional, default `.*` (any file) when `require.context` is used */
23
+ filter: RegExp,
24
+ /** Mode for resolving dynamic dependencies. Defaults to `sync` */
25
+ mode: ContextMode,
26
+ /** Absolute path of the directory to search in */
27
+ from: string,
28
+ }>;
29
+
30
+ function toHash(value: string): string {
31
+ // Use `hex` to ensure filepath safety.
32
+ return crypto.createHash('sha1').update(value).digest('hex');
33
+ }
34
+
35
+ /** Given a fully qualified require context, return a virtual file path that ensures uniqueness between paths with different contexts. */
36
+ export function deriveAbsolutePathFromContext(
37
+ from: string,
38
+ context: RequireContextParams,
39
+ ): string {
40
+ // Drop the trailing slash, require.context should always be matched against a folder
41
+ // and we want to normalize the folder name as much as possible to prevent duplicates.
42
+ // This also makes the files show up in the correct location when debugging in Chrome.
43
+ const filePath = from.endsWith(path.sep) ? from.slice(0, -1) : from;
44
+ return (
45
+ filePath +
46
+ '?ctx=' +
47
+ toHash(
48
+ [
49
+ context.mode,
50
+ context.recursive ? 'recursive' : '',
51
+ new RegExp(context.filter.pattern, context.filter.flags).toString(),
52
+ ]
53
+ .filter(Boolean)
54
+ .join(' '),
55
+ )
56
+ );
57
+ }
58
+
59
+ /** Match a file against a require context. */
60
+ export function fileMatchesContext(
61
+ testPath: string,
62
+ context: RequireContext,
63
+ ): boolean {
64
+ // NOTE(EvanBacon): Ensure this logic is synchronized with the similar
65
+ // functionality in `metro-file-map/src/HasteFS.js` (`matchFilesWithContext()`)
66
+
67
+ const filePath = path.relative(nullthrows(context.from), testPath);
68
+ const filter = context.filter;
69
+ if (
70
+ // Ignore everything outside of the provided `root`.
71
+ !(filePath && !filePath.startsWith('..')) ||
72
+ // Prevent searching in child directories during a non-recursive search.
73
+ (!context.recursive && filePath.includes(path.sep)) ||
74
+ // Test against the filter.
75
+ !filter.test(
76
+ // NOTE(EvanBacon): Ensure files start with `./` for matching purposes
77
+ // this ensures packages work across Metro and Webpack (ex: Storybook for React DOM / React Native).
78
+ // `a/b.js` -> `./a/b.js`
79
+ './' + filePath.replace(/\\/g, '/'),
80
+ )
81
+ ) {
82
+ return false;
83
+ }
84
+
85
+ return true;
86
+ }
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true,
5
+ });
6
+ exports.getContextModuleTemplate = getContextModuleTemplate;
7
+
8
+ var path = _interopRequireWildcard(require("path"));
9
+
10
+ function _getRequireWildcardCache(nodeInterop) {
11
+ if (typeof WeakMap !== "function") return null;
12
+ var cacheBabelInterop = new WeakMap();
13
+ var cacheNodeInterop = new WeakMap();
14
+ return (_getRequireWildcardCache = function (nodeInterop) {
15
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
16
+ })(nodeInterop);
17
+ }
18
+
19
+ function _interopRequireWildcard(obj, nodeInterop) {
20
+ if (!nodeInterop && obj && obj.__esModule) {
21
+ return obj;
22
+ }
23
+ if (obj === null || (typeof obj !== "object" && typeof obj !== "function")) {
24
+ return { default: obj };
25
+ }
26
+ var cache = _getRequireWildcardCache(nodeInterop);
27
+ if (cache && cache.has(obj)) {
28
+ return cache.get(obj);
29
+ }
30
+ var newObj = {};
31
+ var hasPropertyDescriptor =
32
+ Object.defineProperty && Object.getOwnPropertyDescriptor;
33
+ for (var key in obj) {
34
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
35
+ var desc = hasPropertyDescriptor
36
+ ? Object.getOwnPropertyDescriptor(obj, key)
37
+ : null;
38
+ if (desc && (desc.get || desc.set)) {
39
+ Object.defineProperty(newObj, key, desc);
40
+ } else {
41
+ newObj[key] = obj[key];
42
+ }
43
+ }
44
+ }
45
+ newObj.default = obj;
46
+ if (cache) {
47
+ cache.set(obj, newObj);
48
+ }
49
+ return newObj;
50
+ }
51
+
52
+ /**
53
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
54
+ *
55
+ * This source code is licensed under the MIT license found in the
56
+ * LICENSE file in the root directory of this source tree.
57
+ *
58
+ *
59
+ * @format
60
+ */
61
+ function createFileMap(modulePath, files, processModule) {
62
+ let mapString = "\n";
63
+ files
64
+ .slice() // Sort for deterministic output
65
+ .sort()
66
+ .forEach((file) => {
67
+ let filePath = path.relative(modulePath, file); // NOTE(EvanBacon): I'd prefer we prevent the ability for a module to require itself (`require.context('./')`)
68
+ // but Webpack allows this, keeping it here provides better parity between bundlers.
69
+ // Ensure relative file paths start with `./` so they match the
70
+ // patterns (filters) used to include them.
71
+
72
+ if (!filePath.startsWith(".")) {
73
+ filePath = `.${path.sep}` + filePath;
74
+ }
75
+
76
+ const key = JSON.stringify(filePath); // NOTE(EvanBacon): Webpack uses `require.resolve` in order to load modules on demand,
77
+ // Metro doesn't have this functionality so it will use getters instead. Modules need to
78
+ // be loaded on demand because if we imported directly then users would get errors from importing
79
+ // a file without exports as soon as they create a new file and the context module is updated.
80
+ // NOTE: The values are set to `enumerable` so the `context.keys()` method works as expected.
81
+
82
+ mapString += ` ${key}: { enumerable: true, get() { return ${processModule(
83
+ file
84
+ )}; } },\n`;
85
+ });
86
+ return `Object.defineProperties({}, {${mapString}})`;
87
+ }
88
+
89
+ function getEmptyContextModuleTemplate(modulePath) {
90
+ return `
91
+ function metroEmptyContext(request) {
92
+ let e = new Error('No modules in context');
93
+ e.code = 'MODULE_NOT_FOUND';
94
+ throw e;
95
+ }
96
+
97
+ // Return the keys that can be resolved.
98
+ metroEmptyContext.keys = () => ([]);
99
+
100
+ // Return the module identifier for a user request.
101
+ metroEmptyContext.resolve = function metroContextResolve(request) {
102
+ throw new Error('Unimplemented Metro module context functionality');
103
+ }
104
+
105
+ module.exports = metroEmptyContext;`;
106
+ }
107
+
108
+ function getLoadableContextModuleTemplate(
109
+ modulePath,
110
+ files,
111
+ importSyntax,
112
+ getContextTemplate
113
+ ) {
114
+ return `// All of the requested modules are loaded behind enumerable getters.
115
+ const map = ${createFileMap(
116
+ modulePath,
117
+ files,
118
+ (moduleId) => `${importSyntax}(${JSON.stringify(moduleId)})`
119
+ )};
120
+
121
+ function metroContext(request) {
122
+ ${getContextTemplate}
123
+ }
124
+
125
+ // Return the keys that can be resolved.
126
+ metroContext.keys = function metroContextKeys() {
127
+ return Object.keys(map);
128
+ };
129
+
130
+ // Return the module identifier for a user request.
131
+ metroContext.resolve = function metroContextResolve(request) {
132
+ throw new Error('Unimplemented Metro module context functionality');
133
+ }
134
+
135
+ module.exports = metroContext;`;
136
+ }
137
+ /**
138
+ * Generate a context module as a virtual file string.
139
+ *
140
+ * @prop {ContextMode} mode indicates how the modules should be loaded.
141
+ * @prop {string} modulePath virtual file path for the virtual module. Example: `require.context('./src')` -> `'/path/to/project/src'`.
142
+ * @prop {string[]} files list of absolute file paths that must be exported from the context module. Example: `['/path/to/project/src/index.js']`.
143
+ *
144
+ * @returns a string representing a context module (virtual file contents).
145
+ */
146
+
147
+ function getContextModuleTemplate(mode, modulePath, files) {
148
+ if (!files.length) {
149
+ return getEmptyContextModuleTemplate(modulePath);
150
+ }
151
+
152
+ switch (mode) {
153
+ case "eager":
154
+ return getLoadableContextModuleTemplate(
155
+ modulePath,
156
+ files, // NOTE(EvanBacon): It's unclear if we should use `import` or `require` here so sticking
157
+ // with the more stable option (`require`) for now.
158
+ "require",
159
+ [
160
+ " // Here Promise.resolve().then() is used instead of new Promise() to prevent",
161
+ " // uncaught exception popping up in devtools",
162
+ " return Promise.resolve().then(() => map[request]);",
163
+ ].join("\n")
164
+ );
165
+
166
+ case "sync":
167
+ return getLoadableContextModuleTemplate(
168
+ modulePath,
169
+ files,
170
+ "require",
171
+ " return map[request];"
172
+ );
173
+
174
+ case "lazy":
175
+ case "lazy-once":
176
+ return getLoadableContextModuleTemplate(
177
+ modulePath,
178
+ files,
179
+ "import",
180
+ " return map[request];"
181
+ );
182
+
183
+ default:
184
+ throw new Error(`Metro context mode "${mode}" is unimplemented`);
185
+ }
186
+ }
@@ -0,0 +1,148 @@
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 * as path from 'path';
12
+ import type {ContextMode} from '../ModuleGraph/worker/collectDependencies';
13
+
14
+ function createFileMap(
15
+ modulePath: string,
16
+ files: string[],
17
+ processModule: (moduleId: string) => string,
18
+ ): string {
19
+ let mapString = '\n';
20
+
21
+ files
22
+ .slice()
23
+ // Sort for deterministic output
24
+ .sort()
25
+ .forEach(file => {
26
+ let filePath = path.relative(modulePath, file);
27
+
28
+ // NOTE(EvanBacon): I'd prefer we prevent the ability for a module to require itself (`require.context('./')`)
29
+ // but Webpack allows this, keeping it here provides better parity between bundlers.
30
+
31
+ // Ensure relative file paths start with `./` so they match the
32
+ // patterns (filters) used to include them.
33
+ if (!filePath.startsWith('.')) {
34
+ filePath = `.${path.sep}` + filePath;
35
+ }
36
+ const key = JSON.stringify(filePath);
37
+ // NOTE(EvanBacon): Webpack uses `require.resolve` in order to load modules on demand,
38
+ // Metro doesn't have this functionality so it will use getters instead. Modules need to
39
+ // be loaded on demand because if we imported directly then users would get errors from importing
40
+ // a file without exports as soon as they create a new file and the context module is updated.
41
+
42
+ // NOTE: The values are set to `enumerable` so the `context.keys()` method works as expected.
43
+ mapString += ` ${key}: { enumerable: true, get() { return ${processModule(
44
+ file,
45
+ )}; } },\n`;
46
+ });
47
+ return `Object.defineProperties({}, {${mapString}})`;
48
+ }
49
+
50
+ function getEmptyContextModuleTemplate(modulePath: string): string {
51
+ return `
52
+ function metroEmptyContext(request) {
53
+ let e = new Error('No modules in context');
54
+ e.code = 'MODULE_NOT_FOUND';
55
+ throw e;
56
+ }
57
+
58
+ // Return the keys that can be resolved.
59
+ metroEmptyContext.keys = () => ([]);
60
+
61
+ // Return the module identifier for a user request.
62
+ metroEmptyContext.resolve = function metroContextResolve(request) {
63
+ throw new Error('Unimplemented Metro module context functionality');
64
+ }
65
+
66
+ module.exports = metroEmptyContext;`;
67
+ }
68
+
69
+ function getLoadableContextModuleTemplate(
70
+ modulePath: string,
71
+ files: string[],
72
+ importSyntax: string,
73
+ getContextTemplate: string,
74
+ ): string {
75
+ return `// All of the requested modules are loaded behind enumerable getters.
76
+ const map = ${createFileMap(
77
+ modulePath,
78
+ files,
79
+ moduleId => `${importSyntax}(${JSON.stringify(moduleId)})`,
80
+ )};
81
+
82
+ function metroContext(request) {
83
+ ${getContextTemplate}
84
+ }
85
+
86
+ // Return the keys that can be resolved.
87
+ metroContext.keys = function metroContextKeys() {
88
+ return Object.keys(map);
89
+ };
90
+
91
+ // Return the module identifier for a user request.
92
+ metroContext.resolve = function metroContextResolve(request) {
93
+ throw new Error('Unimplemented Metro module context functionality');
94
+ }
95
+
96
+ module.exports = metroContext;`;
97
+ }
98
+
99
+ /**
100
+ * Generate a context module as a virtual file string.
101
+ *
102
+ * @prop {ContextMode} mode indicates how the modules should be loaded.
103
+ * @prop {string} modulePath virtual file path for the virtual module. Example: `require.context('./src')` -> `'/path/to/project/src'`.
104
+ * @prop {string[]} files list of absolute file paths that must be exported from the context module. Example: `['/path/to/project/src/index.js']`.
105
+ *
106
+ * @returns a string representing a context module (virtual file contents).
107
+ */
108
+ export function getContextModuleTemplate(
109
+ mode: ContextMode,
110
+ modulePath: string,
111
+ files: string[],
112
+ ): string {
113
+ if (!files.length) {
114
+ return getEmptyContextModuleTemplate(modulePath);
115
+ }
116
+ switch (mode) {
117
+ case 'eager':
118
+ return getLoadableContextModuleTemplate(
119
+ modulePath,
120
+ files,
121
+ // NOTE(EvanBacon): It's unclear if we should use `import` or `require` here so sticking
122
+ // with the more stable option (`require`) for now.
123
+ 'require',
124
+ [
125
+ ' // Here Promise.resolve().then() is used instead of new Promise() to prevent',
126
+ ' // uncaught exception popping up in devtools',
127
+ ' return Promise.resolve().then(() => map[request]);',
128
+ ].join('\n'),
129
+ );
130
+ case 'sync':
131
+ return getLoadableContextModuleTemplate(
132
+ modulePath,
133
+ files,
134
+ 'require',
135
+ ' return map[request];',
136
+ );
137
+ case 'lazy':
138
+ case 'lazy-once':
139
+ return getLoadableContextModuleTemplate(
140
+ modulePath,
141
+ files,
142
+ 'import',
143
+ ' return map[request];',
144
+ );
145
+ default:
146
+ throw new Error(`Metro context mode "${mode}" is unimplemented`);
147
+ }
148
+ }
@@ -14,7 +14,7 @@ const canonicalize = require("metro-core/src/canonicalize");
14
14
  function getGraphId(
15
15
  entryFile,
16
16
  options,
17
- { shallow, experimentalImportBundleSupport }
17
+ { shallow, experimentalImportBundleSupport, unstable_allowRequireContext }
18
18
  ) {
19
19
  return JSON.stringify(
20
20
  {
@@ -33,6 +33,7 @@ function getGraphId(
33
33
  runtimeBytecodeVersion: options.runtimeBytecodeVersion,
34
34
  type: options.type,
35
35
  experimentalImportBundleSupport,
36
+ unstable_allowRequireContext,
36
37
  shallow,
37
38
  unstable_transformProfile:
38
39
  options.unstable_transformProfile || "default",
@@ -22,9 +22,11 @@ function getGraphId(
22
22
  {
23
23
  shallow,
24
24
  experimentalImportBundleSupport,
25
+ unstable_allowRequireContext,
25
26
  }: {
26
27
  +shallow: boolean,
27
28
  +experimentalImportBundleSupport: boolean,
29
+ +unstable_allowRequireContext: boolean,
28
30
  ...
29
31
  },
30
32
  ): GraphId {
@@ -45,6 +47,7 @@ function getGraphId(
45
47
  runtimeBytecodeVersion: options.runtimeBytecodeVersion,
46
48
  type: options.type,
47
49
  experimentalImportBundleSupport,
50
+ unstable_allowRequireContext,
48
51
  shallow,
49
52
  unstable_transformProfile:
50
53
  options.unstable_transformProfile || 'default',
@@ -48,6 +48,8 @@ async function getPrependedScripts(config, options, bundler, deltaBundler) {
48
48
  config,
49
49
  transformOptions
50
50
  ),
51
+ unstable_allowRequireContext:
52
+ config.transformer.unstable_allowRequireContext,
51
53
  transformOptions,
52
54
  onProgress: null,
53
55
  experimentalImportBundleSupport:
@@ -59,6 +59,8 @@ async function getPrependedScripts(
59
59
  config,
60
60
  transformOptions,
61
61
  ),
62
+ unstable_allowRequireContext:
63
+ config.transformer.unstable_allowRequireContext,
62
64
  transformOptions,
63
65
  onProgress: null,
64
66
  experimentalImportBundleSupport: