@@ -7,164 +7,168 @@ const chalk = require('chalk')
77const webpack = require ( 'webpack' )
88const WebpackDevServer = require ( 'webpack-dev-server' )
99const config = require ( '../config/webpack.config.dev' )
10+ const { choosePort, prepareUrls, prepareProxy} = require ( 'react-dev-utils/WebpackDevServerUtils' )
1011const formatWebpackMessages = require ( 'react-dev-utils/formatWebpackMessages' )
1112const clearConsole = require ( 'react-dev-utils/clearConsole' )
1213const openBrowser = require ( 'react-dev-utils/openBrowser' )
13- const historyApiFallback = require ( 'connect-history-api-fallback ' )
14- const httpProxyMiddleware = require ( 'http-proxy-middleware ' )
14+ const createDevServerConfig = require ( '../config/webpackDevServer.config ' )
15+ const paths = require ( '../config/paths ' )
1516
1617if ( fs . existsSync ( 'elm-package.json' ) === false ) {
1718 console . log ( 'Please, run the build script from project root directory' )
1819 process . exit ( 0 )
1920}
2021
21- // http://webpack.github.io/docs/node.js-api.html#the-long-way
22- const compiler = webpack ( config )
23- const port = 3000
22+ const isInteractive = process . stdout . isTTY
2423
25- compiler . plugin ( 'invalid' , function ( ) {
26- clearConsole ( )
27- console . log ( 'Compiling...' )
28- } )
24+ function printInstructions ( appName , urls ) {
25+ console . log ( )
26+ console . log ( `You can now view ${ chalk . bold ( appName ) } in the browser.` )
27+ console . log ( )
2928
30- compiler . plugin ( 'done' , function ( stats ) {
31- clearConsole ( )
32-
33- const hasErrors = stats . hasErrors ( )
34- const hasWarnings = stats . hasWarnings ( )
35-
36- if ( ! hasErrors && ! hasWarnings ) {
37- console . log ( chalk . green ( 'Compiled successfully!' ) )
38- console . log ( '\nThe app is running at:' )
39- console . log ( '\n ' + chalk . cyan ( 'http://localhost:' + port + '/' ) )
40- console . log ( '\nTo create production build, run:' )
41- console . log ( '\n elm-app build' )
42- return
43- }
44-
45- if ( hasErrors ) {
46- console . log ( chalk . red ( 'Compilation failed.\n' ) )
47-
48- const json = formatWebpackMessages ( stats . toJson ( { } , true ) )
49-
50- json . errors . forEach ( function ( message ) {
51- console . log ( message )
52- console . log ( )
53- } )
54- }
55- } )
56-
57- // We need to provide a custom onError function for httpProxyMiddleware.
58- // It allows us to log custom error messages on the console.
59- function onProxyError ( proxy ) {
60- return function ( err , req , res ) {
61- const host = req . headers && req . headers . host
29+ if ( urls . lanUrlForTerminal ) {
6230 console . log (
63- chalk . red ( 'Proxy error:' ) + ' Could not proxy request ' + chalk . cyan ( req . url ) +
64- ' from ' + chalk . cyan ( host ) + ' to ' + chalk . cyan ( proxy ) + '.'
31+ ` ${ chalk . bold ( 'Local:' ) } ${ urls . localUrlForTerminal } `
6532 )
6633 console . log (
67- 'See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (' +
68- chalk . cyan ( err . code ) + ').'
69- )
70- console . log ( )
71-
72- // And immediately send the proper error response to the client.
73- // Otherwise, the request will eventually timeout with ERR_EMPTY_RESPONSE on the client side.
74- if ( res . writeHead && ! res . headersSent ) {
75- res . writeHead ( 500 )
76- }
77- res . end ( 'Proxy error: Could not proxy request ' + req . url + ' from ' +
78- host + ' to ' + proxy + ' (' + err . code + ').'
34+ ` ${ chalk . bold ( 'On Your Network:' ) } ${ urls . lanUrlForTerminal } `
7935 )
36+ } else {
37+ console . log ( ` ${ urls . localUrlForTerminal } ` )
8038 }
39+
40+ console . log ( )
41+ console . log ( 'Note that the development build is not optimized.' )
42+ console . log (
43+ `To create a production build, use ` +
44+ `${ chalk . cyan ( 'elm-app build' ) } .`
45+ )
46+ console . log ( )
8147}
8248
83- function addMiddleware ( devServer ) {
84- // `proxy` lets you to specify a fallback server during development.
85- // Every unrecognized request will be forwarded to it.
86- const proxy = JSON . parse ( fs . readFileSync ( 'elm-package.json' , 'utf-8' ) ) . proxy
87- devServer . use ( historyApiFallback ( {
88- // Paths with dots should still use the history fallback.
89- // See https://github.com/facebookincubator/create-react-app/issues/387.
90- disableDotRule : true ,
91- // For single page apps, we generally want to fallback to /index.html.
92- // However we also want to respect `proxy` for API calls.
93- // So if `proxy` is specified, we need to decide which fallback to use.
94- // We use a heuristic: if request `accept`s text/html, we pick /index.html.
95- // Modern browsers include text/html into `accept` header when navigating.
96- // However API calls like `fetch()` won’t generally accept text/html.
97- // If this heuristic doesn’t work well for you, don’t use `proxy`.
98- htmlAcceptHeaders : proxy
99- ? [ 'text/html' ]
100- : [ 'text/html' , '*/*' ]
101- } ) )
102- if ( proxy ) {
103- if ( typeof proxy !== 'string' ) {
104- console . log ( chalk . red ( 'When specified, "proxy" in package.json must be a string.' ) )
105- console . log ( chalk . red ( 'Instead, the type of "proxy" was "' + typeof proxy + '".' ) )
106- console . log ( chalk . red ( 'Either remove "proxy" from package.json, or make it a string.' ) )
107- process . exit ( 1 )
108- }
49+ function createCompiler ( webpack , config , appName , urls ) {
50+ // "Compiler" is a low-level interface to Webpack.
51+ // It lets us listen to some events and provide our own custom messages.
52+ let compiler
53+ try {
54+ compiler = webpack ( config )
55+ } catch ( err ) {
56+ console . log ( chalk . red ( 'Failed to compile.' ) )
57+ console . log ( )
58+ console . log ( err . message || err )
59+ console . log ( )
60+ process . exit ( 1 )
61+ }
10962
110- // Otherwise, if proxy is specified, we will let it handle any request.
111- // There are a few exceptions which we won't send to the proxy:
112- // - /index.html (served as HTML5 history API fallback)
113- // - /*.hot-update.json (WebpackDevServer uses this too for hot reloading)
114- // - /sockjs-node/* (WebpackDevServer uses this for hot reloading)
115- // Tip: use https://jex.im/regulex/ to visualize the regex
116- const mayProxy = / ^ (? ! \/ ( i n d e x \. h t m l $ | .* \. h o t - u p d a t e \. j s o n $ | s o c k j s - n o d e \/ ) ) .* $ /
117-
118- // Pass the scope regex both to Express and to the middleware for proxying
119- // of both HTTP and WebSockets to work without false positives.
120- const hpm = httpProxyMiddleware (
121- function ( pathname ) {
122- return mayProxy . test ( pathname )
123- } ,
124- {
125- target : proxy ,
126- logLevel : 'silent' ,
127- onProxyReq : function ( proxyReq ) {
128- // Browers may send Origin headers even with same-origin
129- // requests. To prevent CORS issues, we have to change
130- // the Origin to match the target URL.
131- if ( proxyReq . getHeader ( 'origin' ) ) {
132- proxyReq . setHeader ( 'origin' , proxy )
133- }
134- } ,
135- onError : onProxyError ( proxy ) ,
136- secure : false ,
137- changeOrigin : true ,
138- ws : true
139- } )
140- devServer . use ( mayProxy , hpm )
63+ // "invalid" event fires when you have changed a file, and Webpack is
64+ // recompiling a bundle. WebpackDevServer takes care to pause serving the
65+ // bundle, so if you refresh, it'll wait instead of serving the old one.
66+ // "invalid" is short for "bundle invalidated", it doesn't imply any errors.
67+ compiler . plugin ( 'invalid' , ( ) => {
68+ if ( isInteractive ) {
69+ clearConsole ( )
70+ }
71+ console . log ( 'Compiling...' )
72+ } )
14173
142- // Listen for the websocket 'upgrade' event and upgrade the connection.
143- // If this is not done, httpProxyMiddleware will not try to upgrade until
144- // an initial plain HTTP request is made.
145- devServer . listeningApp . on ( 'upgrade' , hpm . upgrade )
146- }
74+ let isFirstCompile = true
14775
148- // Finally, by now we have certainly resolved the URL.
149- // It may be /index.html, so let the dev server try serving it again.
150- devServer . use ( devServer . middleware )
151- }
76+ // "done" event fires when Webpack has finished recompiling the bundle.
77+ // Whether or not you have warnings or errors, you will get this event.
78+ compiler . plugin ( 'done' , stats => {
79+ if ( isInteractive ) {
80+ clearConsole ( )
81+ }
15282
153- const devServer = new WebpackDevServer ( compiler , {
154- hot : true ,
155- inline : true ,
156- publicPath : '/' ,
157- quiet : true ,
158- historyApiFallback : true
159- } )
83+ // We have switched off the default Webpack output in WebpackDevServer
84+ // options so we are going to "massage" the warnings and errors and present
85+ // them in a readable focused way.
86+ const messages = formatWebpackMessages ( stats . toJson ( { } , true ) )
87+ const isSuccessful = ! messages . errors . length && ! messages . warnings . length
88+ if ( isSuccessful ) {
89+ console . log ( chalk . green ( 'Compiled successfully!' ) )
90+ }
91+ if ( isSuccessful && ( isInteractive || isFirstCompile ) ) {
92+ printInstructions ( appName , urls )
93+ }
94+ isFirstCompile = false
16095
161- addMiddleware ( devServer )
96+ // If errors exist, only show errors.
97+ if ( messages . errors . length ) {
98+ console . log ( chalk . red ( 'Failed to compile.\n' ) )
99+ console . log ( messages . errors . join ( '\n\n' ) )
100+ return
101+ }
162102
163- // Launch WebpackDevServer.
164- devServer . listen ( port , function ( err ) {
165- if ( err ) {
166- return console . log ( err )
167- }
168- } )
103+ // Show warnings if no errors were found.
104+ if ( messages . warnings . length ) {
105+ console . log ( chalk . yellow ( 'Compiled with warnings.\n' ) )
106+ console . log ( messages . warnings . join ( '\n\n' ) )
107+
108+ // Teach some ESLint tricks.
109+ console . log (
110+ '\nSearch for the ' +
111+ chalk . underline ( chalk . yellow ( 'keywords' ) ) +
112+ ' to learn more about each warning.'
113+ )
114+ console . log (
115+ 'To ignore, add ' +
116+ chalk . cyan ( '// eslint-disable-next-line' ) +
117+ ' to the line before.\n'
118+ )
119+ }
120+ } )
121+ return compiler
122+ }
169123
170- openBrowser ( 'http://localhost:' + port + '/' )
124+ // Tools like Cloud9 rely on this.
125+ const DEFAULT_PORT = parseInt ( process . env . PORT , 10 ) || 3000
126+ const HOST = process . env . HOST || '0.0.0.0'
127+
128+ // We attempt to use the default port but if it is busy, we offer the user to
129+ // run on a different port. `detect()` Promise resolves to the next free port.
130+ choosePort ( HOST , DEFAULT_PORT )
131+ . then ( port => {
132+ if ( port == null ) {
133+ // We have not found a port.
134+ return
135+ }
136+ const protocol = process . env . HTTPS === 'true' ? 'https' : 'http'
137+ const appName = require ( paths . elmPkg ) . name
138+ const urls = prepareUrls ( protocol , HOST , port )
139+ // Create a webpack compiler that is configured with custom messages.
140+ const compiler = createCompiler ( webpack , config , appName , urls )
141+ // Load proxy config
142+ const proxySetting = require ( paths . elmPkg ) . proxy
143+ const proxyConfig = prepareProxy ( proxySetting , '/' )
144+ // Serve webpack assets generated by the compiler over a web sever.
145+ const serverConfig = createDevServerConfig (
146+ proxyConfig ,
147+ urls . lanUrlForConfig
148+ )
149+ const devServer = new WebpackDevServer ( compiler , serverConfig )
150+ // Launch WebpackDevServer.
151+ devServer . listen ( port , HOST , err => {
152+ if ( err ) {
153+ return console . log ( err )
154+ }
155+ if ( isInteractive ) {
156+ clearConsole ( )
157+ }
158+ console . log ( chalk . cyan ( 'Starting the development server...\n' ) )
159+ openBrowser ( urls . localUrlForBrowser )
160+ } ) ;
161+
162+ [ 'SIGINT' , 'SIGTERM' ] . forEach ( function ( sig ) {
163+ process . on ( sig , function ( ) {
164+ devServer . close ( )
165+ process . exit ( )
166+ } )
167+ } )
168+ } )
169+ . catch ( err => {
170+ if ( err && err . message ) {
171+ console . log ( err . message )
172+ }
173+ process . exit ( 1 )
174+ } )
0 commit comments