1
1
'use strict' ;
2
2
3
+ const path = require ( 'path' ) ;
4
+ const fs = require ( 'fs' ) ;
3
5
const url = require ( 'url' ) ;
4
6
const ipaddr = require ( 'ipaddr.js' ) ;
5
7
const internalIp = require ( 'internal-ip' ) ;
@@ -11,9 +13,7 @@ const colors = require('./utils/colors');
11
13
const routes = require ( './utils/routes' ) ;
12
14
const getSocketServerImplementation = require ( './utils/getSocketServerImplementation' ) ;
13
15
const getCompilerConfigArray = require ( './utils/getCompilerConfigArray' ) ;
14
- const setupExitSignals = require ( './utils/setupExitSignals' ) ;
15
16
const getStatsOption = require ( './utils/getStatsOption' ) ;
16
- const getColorsOption = require ( './utils/getColorsOption' ) ;
17
17
const schema = require ( './options.json' ) ;
18
18
19
19
if ( ! process . env . WEBPACK_SERVE ) {
@@ -39,7 +39,12 @@ class Server {
39
39
// this value of web socket can be overwritten for tests
40
40
this . webSocketHeartbeatInterval = 30000 ;
41
41
42
- normalizeOptions ( this . compiler , this . options , this . logger ) ;
42
+ normalizeOptions (
43
+ this . compiler ,
44
+ this . options ,
45
+ this . logger ,
46
+ Server . findCacheDir ( )
47
+ ) ;
43
48
44
49
this . applyDevServerPlugin ( ) ;
45
50
@@ -64,7 +69,19 @@ class Server {
64
69
this . createServer ( ) ;
65
70
66
71
killable ( this . server ) ;
67
- setupExitSignals ( this ) ;
72
+
73
+ if ( this . options . setupExitSignals ) {
74
+ const signals = [ 'SIGINT' , 'SIGTERM' ] ;
75
+
76
+ signals . forEach ( ( signal ) => {
77
+ process . on ( signal , ( ) => {
78
+ this . close ( ( ) => {
79
+ // eslint-disable-next-line no-process-exit
80
+ process . exit ( ) ;
81
+ } ) ;
82
+ } ) ;
83
+ } ) ;
84
+ }
68
85
69
86
// Proxy WebSocket without the initial http request
70
87
// https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
@@ -74,6 +91,92 @@ class Server {
74
91
} , this ) ;
75
92
}
76
93
94
+ static get DEFAULT_STATS ( ) {
95
+ return {
96
+ all : false ,
97
+ hash : true ,
98
+ assets : true ,
99
+ warnings : true ,
100
+ errors : true ,
101
+ errorDetails : false ,
102
+ } ;
103
+ }
104
+
105
+ static getHostname ( hostname ) {
106
+ if ( hostname === 'local-ip' ) {
107
+ return internalIp . v4 . sync ( ) || internalIp . v6 . sync ( ) || '0.0.0.0' ;
108
+ } else if ( hostname === 'local-ipv4' ) {
109
+ return internalIp . v4 . sync ( ) || '0.0.0.0' ;
110
+ } else if ( hostname === 'local-ipv6' ) {
111
+ return internalIp . v6 . sync ( ) || '::' ;
112
+ }
113
+
114
+ return hostname ;
115
+ }
116
+
117
+ static getFreePort ( port ) {
118
+ const pRetry = require ( 'p-retry' ) ;
119
+ const portfinder = require ( 'portfinder' ) ;
120
+
121
+ if ( port && port !== 'auto' ) {
122
+ return Promise . resolve ( port ) ;
123
+ }
124
+
125
+ function runPortFinder ( ) {
126
+ return new Promise ( ( resolve , reject ) => {
127
+ // Default port
128
+ portfinder . basePort = process . env . WEBPACK_DEV_SERVER_BASE_PORT || 8080 ;
129
+ portfinder . getPort ( ( error , foundPort ) => {
130
+ if ( error ) {
131
+ return reject ( error ) ;
132
+ }
133
+
134
+ return resolve ( foundPort ) ;
135
+ } ) ;
136
+ } ) ;
137
+ }
138
+
139
+ // Try to find unused port and listen on it for 3 times,
140
+ // if port is not specified in options.
141
+ const defaultPortRetry =
142
+ parseInt ( process . env . WEBPACK_DEV_SERVER_PORT_RETRY , 10 ) || 3 ;
143
+
144
+ return pRetry ( runPortFinder , { retries : defaultPortRetry } ) ;
145
+ }
146
+
147
+ static findCacheDir ( ) {
148
+ const cwd = process . cwd ( ) ;
149
+
150
+ let dir = cwd ;
151
+
152
+ for ( ; ; ) {
153
+ try {
154
+ if ( fs . statSync ( path . join ( dir , 'package.json' ) ) . isFile ( ) ) break ;
155
+ // eslint-disable-next-line no-empty
156
+ } catch ( e ) { }
157
+
158
+ const parent = path . dirname ( dir ) ;
159
+
160
+ if ( dir === parent ) {
161
+ // eslint-disable-next-line no-undefined
162
+ dir = undefined ;
163
+ break ;
164
+ }
165
+
166
+ dir = parent ;
167
+ }
168
+
169
+ if ( ! dir ) {
170
+ return path . resolve ( cwd , '.cache/webpack-dev-server' ) ;
171
+ } else if ( process . versions . pnp === '1' ) {
172
+ return path . resolve ( dir , '.pnp/.cache/webpack-dev-server' ) ;
173
+ } else if ( process . versions . pnp === '3' ) {
174
+ return path . resolve ( dir , '.yarn/.cache/webpack-dev-server' ) ;
175
+ }
176
+
177
+ return path . resolve ( dir , 'node_modules/.cache/webpack-dev-server' ) ;
178
+ }
179
+
77
180
applyDevServerPlugin ( ) {
78
181
const DevServerPlugin = require ( './utils/DevServerPlugin' ) ;
79
182
@@ -539,7 +642,122 @@ class Server {
539
642
} ) ;
540
643
}
541
644
645
+ openBrowser ( uri ) {
646
+ const isAbsoluteUrl = require ( 'is-absolute-url' ) ;
647
+ const open = require ( 'open' ) ;
648
+
649
+ // https://github.com/webpack/webpack-dev-server/issues/1990
650
+ const defaultOpenOptions = { wait : false } ;
651
+ const openTasks = [ ] ;
652
+
653
+ const getOpenTask = ( item ) => {
654
+ if ( typeof item === 'boolean' ) {
655
+ return [ { target : uri , options : defaultOpenOptions } ] ;
656
+ }
657
+
658
+ if ( typeof item === 'string' ) {
659
+ return [ { target : item , options : defaultOpenOptions } ] ;
660
+ }
661
+
662
+ let targets ;
663
+
664
+ if ( item . target ) {
665
+ targets = Array . isArray ( item . target ) ? item . target : [ item . target ] ;
666
+ } else {
667
+ targets = [ uri ] ;
668
+ }
669
+
670
+ return targets . map ( ( target ) => {
671
+ const openOptions = defaultOpenOptions ;
672
+
673
+ if ( item . app ) {
674
+ if ( typeof item . app === 'string' ) {
675
+ openOptions . app = { name : item . app } ;
676
+ } else {
677
+ openOptions . app = item . app ;
678
+ }
679
+ }
680
+
681
+ return { target, options : openOptions } ;
682
+ } ) ;
683
+ } ;
684
+
685
+ if ( Array . isArray ( this . options . open ) ) {
686
+ this . options . open . forEach ( ( item ) => {
687
+ openTasks . push ( ...getOpenTask ( item ) ) ;
688
+ } ) ;
689
+ } else {
690
+ openTasks . push ( ...getOpenTask ( this . options . open ) ) ;
691
+ }
692
+
693
+ Promise . all (
694
+ openTasks . map ( ( openTask ) => {
695
+ let openTarget ;
696
+
697
+ if ( openTask . target ) {
698
+ if ( typeof openTask . target === 'boolean' ) {
699
+ openTarget = uri ;
700
+ } else {
701
+ openTarget = isAbsoluteUrl ( openTask . target )
702
+ ? openTask . target
703
+ : new URL ( openTask . target , uri ) . toString ( ) ;
704
+ }
705
+ } else {
706
+ openTarget = uri ;
707
+ }
708
+
709
+ return open ( openTarget , openTask . options ) . catch ( ( ) => {
710
+ this . logger . warn (
711
+ `Unable to open "${ openTarget } " page${
712
+ // eslint-disable-next-line no-nested-ternary
713
+ openTask . options . app
714
+ ? ` in "${ openTask . options . app . name } " app${
715
+ openTask . options . app . arguments
716
+ ? ` with "${ openTask . options . app . arguments . join (
717
+ ' '
718
+ ) } " arguments`
719
+ : ''
720
+ } `
721
+ : ''
722
+ } . If you are running in a headless environment, please do not use the "open" option or related flags like "--open", "--open-target", and "--open-app".`
723
+ ) ;
724
+ } ) ;
725
+ } )
726
+ ) ;
727
+ }
728
+
729
+ runBonjour ( ) {
730
+ const bonjour = require ( 'bonjour' ) ( ) ;
731
+ const os = require ( 'os' ) ;
732
+
733
+ bonjour . publish ( {
734
+ name : `Webpack Dev Server ${ os . hostname ( ) } :${ this . options . port } ` ,
735
+ port : this . options . port ,
736
+ type : this . options . https ? 'https' : 'http' ,
737
+ subtypes : [ 'webpack' ] ,
738
+ ...this . options . bonjour ,
739
+ } ) ;
740
+
741
+ process . on ( 'exit' , ( ) => {
742
+ bonjour . unpublishAll ( ( ) => {
743
+ bonjour . destroy ( ) ;
744
+ } ) ;
745
+ } ) ;
746
+ }
747
+
542
748
logStatus ( ) {
749
+ const getColorsOption = ( configArray ) => {
750
+ const statsOption = getStatsOption ( configArray ) ;
751
+
752
+ let colorsEnabled = false ;
753
+
754
+ if ( typeof statsOption === 'object' && statsOption . colors ) {
755
+ colorsEnabled = statsOption . colors ;
756
+ }
757
+
758
+ return colorsEnabled ;
759
+ } ;
760
+
543
761
const useColor = getColorsOption ( getCompilerConfigArray ( this . compiler ) ) ;
544
762
const protocol = this . options . https ? 'https' : 'http' ;
545
763
const { address, port } = this . server . address ( ) ;
@@ -661,11 +879,9 @@ class Server {
661
879
}
662
880
663
881
if ( this . options . open ) {
664
- const runOpen = require ( './utils/runOpen' ) ;
665
-
666
882
const openTarget = prettyPrintUrl ( this . options . host || 'localhost' ) ;
667
883
668
- runOpen ( openTarget , this . options . open , this . logger ) ;
884
+ this . openBrowser ( openTarget ) ;
669
885
}
670
886
}
671
887
@@ -702,14 +918,7 @@ class Server {
702
918
this . options . host = hostname ;
703
919
}
704
920
705
- if ( this . options . host === 'local-ip' ) {
706
- this . options . host =
707
- internalIp . v4 . sync ( ) || internalIp . v6 . sync ( ) || '0.0.0.0' ;
708
- } else if ( this . options . host === 'local-ipv4' ) {
709
- this . options . host = internalIp . v4 . sync ( ) || '0.0.0.0' ;
710
- } else if ( this . options . host === 'local-ipv6' ) {
711
- this . options . host = internalIp . v6 . sync ( ) || '::' ;
712
- }
921
+ this . options . host = Server . getHostname ( this . options . host ) ;
713
922
714
923
return Server . getFreePort ( this . options . port )
715
924
. then ( ( foundPort ) => {
@@ -724,9 +933,7 @@ class Server {
724
933
}
725
934
726
935
if ( this . options . bonjour ) {
727
- const runBonjour = require ( './utils/runBonjour' ) ;
728
-
729
- runBonjour ( this . options ) ;
936
+ this . runBonjour ( ) ;
730
937
}
731
938
732
939
this . logStatus ( ) ;
@@ -767,47 +974,6 @@ class Server {
767
974
} ) ;
768
975
}
769
976
770
- static get DEFAULT_STATS ( ) {
771
- return {
772
- all : false ,
773
- hash : true ,
774
- assets : true ,
775
- warnings : true ,
776
- errors : true ,
777
- errorDetails : false ,
778
- } ;
779
- }
780
-
781
- static getFreePort ( port ) {
782
- const pRetry = require ( 'p-retry' ) ;
783
- const portfinder = require ( 'portfinder' ) ;
784
-
785
- if ( port && port !== 'auto' ) {
786
- return Promise . resolve ( port ) ;
787
- }
788
-
789
- function runPortFinder ( ) {
790
- return new Promise ( ( resolve , reject ) => {
791
- // default port
792
- portfinder . basePort = process . env . WEBPACK_DEV_SERVER_BASE_PORT || 8080 ;
793
- portfinder . getPort ( ( error , foundPort ) => {
794
- if ( error ) {
795
- return reject ( error ) ;
796
- }
797
-
798
- return resolve ( foundPort ) ;
799
- } ) ;
800
- } ) ;
801
- }
802
-
803
- // Try to find unused port and listen on it for 3 times,
804
- // if port is not specified in options.
805
- const defaultPortRetry =
806
- parseInt ( process . env . WEBPACK_DEV_SERVER_PORT_RETRY , 10 ) || 3 ;
807
-
808
- return pRetry ( runPortFinder , { retries : defaultPortRetry } ) ;
809
- }
810
-
811
977
getStats ( statsObj ) {
812
978
const stats = Server . DEFAULT_STATS ;
813
979
0 commit comments