webpack-dev-server.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. #!/usr/bin/env node
  2. 'use strict';
  3. /* eslint-disable
  4. import/order,
  5. no-shadow,
  6. no-console
  7. */
  8. const debug = require('debug')('webpack-dev-server');
  9. const fs = require('fs');
  10. const net = require('net');
  11. const importLocal = require('import-local');
  12. const yargs = require('yargs');
  13. const webpack = require('webpack');
  14. const options = require('./options');
  15. const Server = require('../lib/Server');
  16. const colors = require('../lib/utils/colors');
  17. const createConfig = require('../lib/utils/createConfig');
  18. const createDomain = require('../lib/utils/createDomain');
  19. const createLogger = require('../lib/utils/createLogger');
  20. const defaultTo = require('../lib/utils/defaultTo');
  21. const findPort = require('../lib/utils/findPort');
  22. const getVersions = require('../lib/utils/getVersions');
  23. const runBonjour = require('../lib/utils/runBonjour');
  24. const status = require('../lib/utils/status');
  25. const tryParseInt = require('../lib/utils/tryParseInt');
  26. let server;
  27. const signals = ['SIGINT', 'SIGTERM'];
  28. signals.forEach((signal) => {
  29. process.on(signal, () => {
  30. if (server) {
  31. server.close(() => {
  32. // eslint-disable-next-line no-process-exit
  33. process.exit();
  34. });
  35. } else {
  36. // eslint-disable-next-line no-process-exit
  37. process.exit();
  38. }
  39. });
  40. });
  41. // Prefer the local installation of webpack-dev-server
  42. if (importLocal(__filename)) {
  43. debug('Using local install of webpack-dev-server');
  44. return;
  45. }
  46. try {
  47. require.resolve('webpack-cli');
  48. } catch (err) {
  49. console.error('The CLI moved into a separate package: webpack-cli');
  50. console.error(
  51. "Please install 'webpack-cli' in addition to webpack itself to use the CLI"
  52. );
  53. console.error('-> When using npm: npm i -D webpack-cli');
  54. console.error('-> When using yarn: yarn add -D webpack-cli');
  55. process.exitCode = 1;
  56. }
  57. yargs.usage(
  58. `${getVersions()}\nUsage: https://webpack.js.org/configuration/dev-server/`
  59. );
  60. // webpack-cli@3.3 path : 'webpack-cli/bin/config/config-yargs'
  61. let configYargsPath;
  62. try {
  63. require.resolve('webpack-cli/bin/config/config-yargs');
  64. configYargsPath = 'webpack-cli/bin/config/config-yargs';
  65. } catch (e) {
  66. configYargsPath = 'webpack-cli/bin/config-yargs';
  67. }
  68. // eslint-disable-next-line import/no-extraneous-dependencies
  69. // eslint-disable-next-line import/no-dynamic-require
  70. require(configYargsPath)(yargs);
  71. // It is important that this is done after the webpack yargs config,
  72. // so it overrides webpack's version info.
  73. yargs.version(getVersions());
  74. yargs.options(options);
  75. const argv = yargs.argv;
  76. // webpack-cli@3.3 path : 'webpack-cli/bin/utils/convert-argv'
  77. let convertArgvPath;
  78. try {
  79. require.resolve('webpack-cli/bin/utils/convert-argv');
  80. convertArgvPath = 'webpack-cli/bin/utils/convert-argv';
  81. } catch (e) {
  82. convertArgvPath = 'webpack-cli/bin/convert-argv';
  83. }
  84. // eslint-disable-next-line import/no-extraneous-dependencies
  85. // eslint-disable-next-line import/no-dynamic-require
  86. const config = require(convertArgvPath)(yargs, argv, {
  87. outputFilename: '/bundle.js',
  88. });
  89. // Taken out of yargs because we must know if
  90. // it wasn't given by the user, in which case
  91. // we should use portfinder.
  92. const DEFAULT_PORT = 8080;
  93. // Try to find unused port and listen on it for 3 times,
  94. // if port is not specified in options.
  95. // Because NaN == null is false, defaultTo fails if parseInt returns NaN
  96. // so the tryParseInt function is introduced to handle NaN
  97. const defaultPortRetry = defaultTo(
  98. tryParseInt(process.env.DEFAULT_PORT_RETRY),
  99. 3
  100. );
  101. function processOptions(config) {
  102. // processOptions {Promise}
  103. if (typeof config.then === 'function') {
  104. config.then(processOptions).catch((err) => {
  105. console.error(err.stack || err);
  106. // eslint-disable-next-line no-process-exit
  107. process.exit();
  108. });
  109. return;
  110. }
  111. const options = createConfig(config, argv, { port: DEFAULT_PORT });
  112. startDevServer(config, options);
  113. }
  114. function startDevServer(config, options) {
  115. const log = createLogger(options);
  116. let compiler;
  117. try {
  118. compiler = webpack(config);
  119. } catch (err) {
  120. if (err instanceof webpack.WebpackOptionsValidationError) {
  121. log.error(colors.error(options.stats.colors, err.message));
  122. // eslint-disable-next-line no-process-exit
  123. process.exit(1);
  124. }
  125. throw err;
  126. }
  127. if (options.progress) {
  128. new webpack.ProgressPlugin({
  129. profile: argv.profile,
  130. }).apply(compiler);
  131. }
  132. const suffix =
  133. options.inline !== false || options.lazy === true
  134. ? '/'
  135. : '/webpack-dev-server/';
  136. try {
  137. server = new Server(compiler, options, log);
  138. } catch (err) {
  139. if (err.name === 'ValidationError') {
  140. log.error(colors.error(options.stats.colors, err.message));
  141. // eslint-disable-next-line no-process-exit
  142. process.exit(1);
  143. }
  144. throw err;
  145. }
  146. if (options.socket) {
  147. server.listeningApp.on('error', (e) => {
  148. if (e.code === 'EADDRINUSE') {
  149. const clientSocket = new net.Socket();
  150. clientSocket.on('error', (err) => {
  151. if (err.code === 'ECONNREFUSED') {
  152. // No other server listening on this socket so it can be safely removed
  153. fs.unlinkSync(options.socket);
  154. server.listen(options.socket, options.host, (error) => {
  155. if (error) {
  156. throw error;
  157. }
  158. });
  159. }
  160. });
  161. clientSocket.connect({ path: options.socket }, () => {
  162. throw new Error('This socket is already used');
  163. });
  164. }
  165. });
  166. server.listen(options.socket, options.host, (err) => {
  167. if (err) {
  168. throw err;
  169. }
  170. // chmod 666 (rw rw rw)
  171. const READ_WRITE = 438;
  172. fs.chmod(options.socket, READ_WRITE, (err) => {
  173. if (err) {
  174. throw err;
  175. }
  176. const uri = createDomain(options, server.listeningApp) + suffix;
  177. status(uri, options, log, argv.color);
  178. });
  179. });
  180. return;
  181. }
  182. const startServer = () => {
  183. server.listen(options.port, options.host, (err) => {
  184. if (err) {
  185. throw err;
  186. }
  187. if (options.bonjour) {
  188. runBonjour(options);
  189. }
  190. const uri = createDomain(options, server.listeningApp) + suffix;
  191. status(uri, options, log, argv.color);
  192. });
  193. };
  194. if (options.port) {
  195. startServer();
  196. return;
  197. }
  198. // only run port finder if no port as been specified
  199. findPort(server, DEFAULT_PORT, defaultPortRetry, (err, port) => {
  200. if (err) {
  201. throw err;
  202. }
  203. options.port = port;
  204. startServer();
  205. });
  206. }
  207. processOptions(config);