44 *
55 * @format
66 */
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