4
4
*
5
5
* @format
6
6
*/
7
- const path = require ( 'path' ) ;
8
- const exclusionList = require ( 'metro-config/src/defaults/exclusionList' ) ;
9
-
10
- module . exports = {
11
- resolver : {
12
- blockList : exclusionList ( [
13
- // This stops "react-native run-windows" from causing the metro server to crash if its already running
14
- new RegExp (
15
- `${ path . resolve ( __dirname , 'windows' ) . replace ( / [ / \\ ] / g, '/' ) } .*` ,
16
- ) ,
17
- // This prevents "react-native run-windows" from hitting: EBUSY: resource busy or locked, open msbuild.ProjectImports.zip
18
- / .* \. P r o j e c t I m p o r t s \. z i p / ,
19
- ] ) ,
20
- } ,
21
- transformer : {
22
- getTransformOptions : async ( ) => ( {
23
- transform : {
24
- experimentalImportSupport : false ,
25
- inlineRequires : true ,
26
- } ,
27
- } ) ,
28
- } ,
29
- } ;
7
+ const path = require ( 'path' ) ;
8
+ const fs = require ( 'fs' ) ;
9
+ const exclusionList = require ( 'metro-config/src/defaults/exclusionList' ) ;
10
+
11
+ // Escape function taken from the MDN documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
12
+ function escapeRegExp ( string ) {
13
+ return string . replace ( / [ . * + \- ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ; // $& means the whole matched string
14
+ }
15
+
16
+ // NOTE: The Metro bundler does not support symlinks (see https://github.com/facebook/metro/issues/1), which NPM uses for local packages.
17
+ // To work around this, we explicity tell the metro bundler where to find local/linked packages.
18
+
19
+ // Create a mapping of package ids to linked directories.
20
+ function processModuleSymLinks ( ) {
21
+ const nodeModulesPath = path . resolve ( __dirname , 'node_modules' ) ;
22
+ let moduleMappings = { } ;
23
+ let moduleExclusions = [ ] ;
24
+
25
+ function findPackageDirs ( directory ) {
26
+ fs . readdirSync ( directory ) . forEach ( item => {
27
+ const itemPath = path . resolve ( directory , item ) ;
28
+ const itemStat = fs . lstatSync ( itemPath ) ;
29
+ if ( itemStat . isSymbolicLink ( ) ) {
30
+ let linkPath = fs . readlinkSync ( itemPath ) ;
31
+ // Sym links are relative in Unix, absolute in Windows.
32
+ if ( ! path . isAbsolute ( linkPath ) ) {
33
+ linkPath = path . resolve ( directory , linkPath ) ;
34
+ }
35
+ const linkStat = fs . statSync ( linkPath ) ;
36
+ if ( linkStat . isDirectory ( ) ) {
37
+ const packagePath = path . resolve ( linkPath , "package.json" ) ;
38
+ if ( fs . existsSync ( packagePath ) ) {
39
+ const packageId = path . relative ( nodeModulesPath , itemPath ) ;
40
+ moduleMappings [ packageId ] = linkPath ;
41
+
42
+ const packageInfoData = fs . readFileSync ( packagePath ) ;
43
+ const packageInfo = JSON . parse ( packageInfoData ) ;
44
+
45
+ const dependencies = packageInfo . dependencies ? Object . keys ( packageInfo . dependencies ) : [ ] ;
46
+ const peerDependencies = packageInfo . peerDependencies ? Object . keys ( packageInfo . peerDependencies ) : [ ] ;
47
+ const devDependencies = packageInfo . devDependencies ? Object . keys ( packageInfo . devDependencies ) : [ ] ;
48
+
49
+ // Exclude dependencies that appear in devDependencies or peerDependencies but not in dependencies. Otherwise,
50
+ // the metro bundler will package those devDependencies/peerDependencies as unintended copies.
51
+ for ( const devDependency of devDependencies . concat ( peerDependencies ) . filter ( dependency => ! dependencies . includes ( dependency ) ) ) {
52
+ moduleExclusions . push ( new RegExp ( escapeRegExp ( path . join ( linkPath , "node_modules" , devDependency ) ) + "\/.*" ) ) ;
53
+ }
54
+ }
55
+ }
56
+ } else if ( itemStat . isDirectory ( ) ) {
57
+ findPackageDirs ( itemPath ) ;
58
+ }
59
+ } ) ;
60
+ }
61
+
62
+ findPackageDirs ( nodeModulesPath ) ;
63
+
64
+ return [ moduleMappings , moduleExclusions ] ;
65
+ }
66
+
67
+ const [ moduleMappings , moduleExclusions ] = processModuleSymLinks ( ) ;
68
+ console . log ( "Mapping the following sym linked packages:" ) ;
69
+ console . log ( moduleMappings ) ;
70
+
71
+ module . exports = {
72
+ transformer : {
73
+ getTransformOptions : async ( ) => ( {
74
+ transform : {
75
+ experimentalImportSupport : false ,
76
+ inlineRequires : true ,
77
+ } ,
78
+ } ) ,
79
+ } ,
80
+
81
+ resolver : {
82
+ // Register an "extra modules proxy" for resolving modules outside of the normal resolution logic.
83
+ extraNodeModules : new Proxy (
84
+ // Provide the set of known local package mappings.
85
+ moduleMappings ,
86
+ {
87
+ // Provide a mapper function, which uses the above mappings for associated package ids,
88
+ // otherwise fall back to the standard behavior and just look in the node_modules directory.
89
+ get : ( target , name ) => name in target ? target [ name ] : path . join ( __dirname , `node_modules/${ name } ` ) ,
90
+ } ,
91
+ ) ,
92
+
93
+ blockList : exclusionList ( moduleExclusions . concat ( [
94
+ // Avoid error EBUSY: resource busy or locked, open 'D:\a\1\s\packages\playground\msbuild.ProjectImports.zip' in pipeline
95
+ / .* \. P r o j e c t I m p o r t s \. z i p / ,
96
+
97
+ // This stops "react-native run-windows" from causing the metro server to crash if its already running
98
+ new RegExp (
99
+ `${ path . resolve ( __dirname , 'windows' ) . replace ( / [ / \\ ] / g, '/' ) } .*` ,
100
+ ) ,
101
+ ] ) ) ,
102
+ } ,
103
+
104
+ projectRoot : path . resolve ( __dirname ) ,
105
+
106
+ // Also additionally watch all the mapped local directories for changes to support live updates.
107
+ watchFolders : Object . values ( moduleMappings ) ,
108
+ } ;
109
+
0 commit comments