1
1
import { IApi } from '@umijs/types' ;
2
2
import { join } from 'path' ;
3
3
import { existsSync } from 'fs' ;
4
+ import { constants } from 'os' ;
5
+ import http , { Server as HttpServer } from 'http' ;
6
+ import { Server as HttpsServer } from 'https' ;
7
+ import assert from 'assert' ;
4
8
import generateFiles from '@umijs/preset-built-in/lib/plugins/commands/generateFiles' ;
5
9
import { cleanTmpPathExceptCache } from '@umijs/preset-built-in/lib/plugins/commands/buildDevUtils' ;
6
10
import { watchPkg } from '@umijs/preset-built-in/lib/plugins/commands/dev/watchPkg' ;
7
11
import loadMetroConfig from '@react-native-community/cli/build/tools/loadMetroConfig' ;
8
12
import loadConfig from '@react-native-community/cli/build/tools/config' ;
13
+ import eventsSocketModule from '@react-native-community/cli/build/commands/server/eventsSocket' ;
14
+ import messageSocket from '@react-native-community/cli/build/commands/server/messageSocket' ;
15
+ import webSocketProxy from '@react-native-community/cli/build/commands/server/webSocketProxy' ;
16
+ import MiddlewareManager from '@react-native-community/cli/build/commands/server/middleware/MiddlewareManager' ;
17
+ import releaseChecker from '@react-native-community/cli/build/tools/releaseChecker' ;
18
+ import enableWatchMode from '@react-native-community/cli/build/commands/server/watchMode' ;
9
19
import getIPAddress from './getIPAddress' ;
10
20
11
21
export default ( api : IApi ) => {
12
22
const {
13
23
logger,
14
- env,
15
24
paths,
16
25
utils : { chalk, portfinder } ,
17
26
} = api ;
18
- const METRO_PATH = join ( api . paths . absNodeModulesPath || process . cwd ( ) , 'metro' ) ;
27
+ const METRO_PATH = join ( paths . absNodeModulesPath || '' , 'metro' ) ;
28
+ const METRO_CORE_PATH = join ( paths . absNodeModulesPath || '' , 'metro-core' ) ;
19
29
20
- if ( ! existsSync ( METRO_PATH ) ) {
21
- throw new TypeError ( '未找到 "metro",请执行 `yarn install` 安装所有依赖。' ) ;
22
- }
30
+ assert ( existsSync ( METRO_PATH ) , '未找到 "metro",请执行 `yarn install` 安装所有依赖。' ) ;
31
+ assert ( existsSync ( METRO_CORE_PATH ) , '未找到 "metro-core",请执行 `yarn install` 安装所有依赖。' ) ;
23
32
24
- // eslint-disable-next-line @typescript-eslint/no-var-requires
25
- const MetroServer = require ( join ( METRO_PATH , 'src' , 'Server.js' ) ) ;
26
33
let port : number ;
27
34
let hostname : string ;
28
- let server : typeof MetroServer ;
35
+ let server : HttpServer | HttpsServer ;
29
36
const unwatchs : ( ( ) => void ) [ ] = [ ] ;
30
37
31
- function destroy ( ) {
32
- for ( const unwatch of unwatchs ) {
33
- unwatch ( ) ;
34
- }
38
+ function destroy ( ) : Promise < void > {
39
+ return new Promise < void > ( ( resolve , reject ) => {
40
+ for ( const unwatch of unwatchs ) {
41
+ try {
42
+ unwatch ( ) ;
43
+ } catch ( ignored ) { }
44
+ }
45
+ if ( server ) {
46
+ server . close ( ( err ) => ( err ? reject ( err ) : resolve ( ) ) ) ;
47
+ } else {
48
+ resolve ( ) ;
49
+ }
50
+ } ) ;
35
51
}
36
52
37
53
api . registerCommand ( {
38
54
name : 'dev-rn' ,
39
55
description : 'starts react-native dev webserver' ,
40
56
fn : async ( { args } ) => {
41
- logger . info ( 'rn-dev:' , args ) ;
42
57
const defaultPort = process . env . PORT || args ?. port || api . config . devServer ?. port ;
43
58
port = await portfinder . getPortPromise ( {
44
59
port : defaultPort ? parseInt ( String ( defaultPort ) , 10 ) : 8081 ,
45
60
} ) ;
46
61
hostname = process . env . HOST || api . config . devServer ?. host || getIPAddress ( ) ;
47
- console . log ( chalk . cyan ( 'Starting the development server...' ) ) ;
48
- // eslint-disable-next-line no-unused-expressions
49
- process . send ?.( { type : 'UPDATE_PORT' , port } ) ;
62
+
63
+ logger . info ( 'rn-dev:' , args , 'hostname=' , hostname , 'port=' , port ) ;
50
64
51
65
cleanTmpPathExceptCache ( {
52
66
absTmpPath : paths . absTmpPath ! ,
53
67
} ) ;
54
68
const watch = process . env . WATCH !== 'none' ;
55
69
56
- async function runMetroServer ( ) : Promise < void > {
57
- const ctx = loadConfig ( api . paths . cwd ) ;
70
+ async function startMetroServer ( ) : Promise < void > {
71
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
72
+ const Metro = require ( METRO_PATH ) ;
73
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
74
+ const { Terminal } = require ( METRO_CORE_PATH ) ;
75
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
76
+ const TerminalReporter = require ( join ( METRO_PATH , 'src' , 'lib' , 'TerminalReporter' ) ) ;
77
+ const terminal = new Terminal ( process . stdout ) ;
78
+ const terminalReporter = new TerminalReporter ( terminal ) ;
79
+ // eslint-disable-next-line
80
+ let eventsSocket : ReturnType < typeof eventsSocketModule . attachToServer > | undefined ;
81
+
82
+ const reporter = {
83
+ update ( event : any ) {
84
+ terminalReporter . update ( event ) ;
85
+ if ( eventsSocket ) {
86
+ eventsSocket . reportEvent ( event ) ;
87
+ }
88
+ } ,
89
+ } ;
90
+ const ctx = loadConfig ( paths . cwd ) ;
91
+ logger . info ( 'ctx:' , ctx ) ;
58
92
const metroConfig = await loadMetroConfig ( ctx , {
59
93
...args ,
60
- watchFolders : [ api . paths . absTmpPath || '' ] ,
61
- projectRoot : api . paths . cwd ,
62
- // reporter,
94
+ config : join ( paths . cwd || '' , 'metro.config.js' ) ,
95
+ projectRoot : paths . absTmpPath ,
96
+ reporter,
63
97
} ) ;
64
- }
98
+ if ( Array . isArray ( args . assetPlugins ) ) {
99
+ metroConfig . transformer . assetPlugins = args . assetPlugins . map ( ( plugin ) => require . resolve ( plugin ) ) ;
100
+ }
101
+
102
+ const middlewareManager = new MiddlewareManager ( {
103
+ host : hostname ,
104
+ port : metroConfig . server . port || port ,
105
+ watchFolders : metroConfig . watchFolders ,
106
+ } ) ;
107
+
108
+ metroConfig . watchFolders . forEach ( middlewareManager . serveStatic . bind ( middlewareManager ) ) ;
109
+
110
+ const customEnhanceMiddleware = metroConfig . server . enhanceMiddleware ;
65
111
66
- function restartMetroServer ( ) : void {
67
- if ( server && typeof server . close === 'function' ) {
68
- server . close ( ( ) => {
69
- runMetroServer ( ) . then ( ) ;
112
+ metroConfig . server . enhanceMiddleware = ( middleware : any , server : unknown ) => {
113
+ if ( customEnhanceMiddleware ) {
114
+ middleware = customEnhanceMiddleware ( middleware , server ) ;
115
+ }
116
+
117
+ return middlewareManager . getConnectInstance ( ) . use ( middleware ) ;
118
+ } ;
119
+
120
+ logger . info ( 'metroConfig:' , metroConfig ) ;
121
+ server = await Metro . runServer ( metroConfig , {
122
+ host : args . host ,
123
+ secure : args . https ,
124
+ secureCert : args . cert ,
125
+ secureKey : args . key ,
126
+ hmrEnabled : true ,
127
+ } ) ;
128
+
129
+ const wsProxy = webSocketProxy . attachToServer ( server , '/debugger-proxy' ) ;
130
+ const ms = messageSocket . attachToServer ( server , '/message' ) ;
131
+ eventsSocket = eventsSocketModule . attachToServer ( server , '/events' , ms ) ;
132
+
133
+ middlewareManager . attachDevToolsSocket ( wsProxy ) ;
134
+ middlewareManager . attachDevToolsSocket ( ms ) ;
135
+
136
+ if ( args . interactive ) {
137
+ enableWatchMode ( ms ) ;
138
+ }
139
+
140
+ middlewareManager
141
+ . getConnectInstance ( )
142
+ . use ( '/reload' , ( _req : http . IncomingMessage , res : http . ServerResponse ) => {
143
+ ms . broadcast ( 'reload' ) ;
144
+ res . end ( 'OK' ) ;
70
145
} ) ;
146
+
147
+ // In Node 8, the default keep-alive for an HTTP connection is 5 seconds. In
148
+ // early versions of Node 8, this was implemented in a buggy way which caused
149
+ // some HTTP responses (like those containing large JS bundles) to be
150
+ // terminated early.
151
+ //
152
+ // As a workaround, arbitrarily increase the keep-alive from 5 to 30 seconds,
153
+ // which should be enough to send even the largest of JS bundles.
154
+ //
155
+ // For more info: https://github.com/nodejs/node/issues/13391
156
+ //
157
+ server . keepAliveTimeout = 30000 ;
158
+
159
+ await releaseChecker ( ctx . root ) ;
160
+ }
161
+
162
+ async function restartServer ( ) : Promise < void > {
163
+ console . log ( chalk . gray ( `Try to restart dev server...` ) ) ;
164
+ try {
165
+ await destroy ( ) ;
166
+ await startMetroServer ( ) ;
167
+ } catch ( err ) {
168
+ console . log ( chalk . red ( 'Dev server restarting failed' ) , err ) ;
169
+ process . exit ( constants . signals . SIGHUP ) ;
71
170
}
72
171
}
73
172
@@ -81,19 +180,19 @@ export default (api: IApi) => {
81
180
onChange ( ) {
82
181
console . log ( ) ;
83
182
api . logger . info ( `Plugins in package.json changed.` ) ;
84
- api . restartServer ( ) ;
183
+ restartServer ( ) ;
85
184
} ,
86
185
} ) ;
87
186
unwatchs . push ( unwatchPkg ) ;
88
187
89
188
// watch config change
90
189
const unwatchConfig = api . service . configInstance . watch ( {
91
190
userConfig : api . service . userConfig ,
92
- onChange : async ( { pluginChanged, userConfig , valueChanged } ) => {
191
+ onChange : async ( { pluginChanged, valueChanged } ) => {
93
192
if ( pluginChanged . length ) {
94
193
console . log ( ) ;
95
194
api . logger . info ( `Plugins of ${ pluginChanged . map ( ( p ) => p . key ) . join ( ', ' ) } changed.` ) ;
96
- api . restartServer ( ) ;
195
+ restartServer ( ) ;
97
196
}
98
197
if ( valueChanged . length ) {
99
198
let reload = false ;
@@ -117,7 +216,7 @@ export default (api: IApi) => {
117
216
if ( reload ) {
118
217
console . log ( ) ;
119
218
api . logger . info ( `Config ${ reloadConfigs . join ( ', ' ) } changed.` ) ;
120
- api . restartServer ( ) ;
219
+ restartServer ( ) ;
121
220
} else {
122
221
api . service . userConfig = api . service . configInstance . getUserConfig ( ) ;
123
222
@@ -147,6 +246,8 @@ export default (api: IApi) => {
147
246
} ) ;
148
247
unwatchs . push ( unwatchConfig ) ;
149
248
}
249
+
250
+ await startMetroServer ( ) ;
150
251
} ,
151
252
} ) ;
152
253
@@ -155,7 +256,7 @@ export default (api: IApi) => {
155
256
description : 'builds react-native javascript bundle for offline use' ,
156
257
fn : async ( { args : { platform } } ) => {
157
258
logger . info ( 'rn-build:' , platform ) ;
158
- // await generateFiles({ api, watch: false });
259
+ await generateFiles ( { api } ) ;
159
260
} ,
160
261
} ) ;
161
262
} ;
0 commit comments