diff --git a/.editorconfig b/.editorconfig index 60a0bc444e..28e1806f34 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,16 +1,13 @@ -root = true +# editorconfig.org [*] -indent_style = tab -indent_size = 4 charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true -max_line_length = 233 - -[*.json] indent_style = space indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true -[*.md] +[.md] +insert_final_newline = false trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.eslintrc b/.eslintrc index a6afa62ce1..a7f05e00fb 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,50 +1,19 @@ { - "root": true, - "extends": [ - "eslint:recommended" - ], - "env": { - "node": true, - "mocha": true, - "browser": true, - "es6": true - }, - "rules": { - "quotes": ["error", "double"], - "no-undef": "error", - "brace-style": "error", - "eol-last": "error", - "indent": ["error", "tab", { "SwitchCase": 1 }], - "no-extra-bind": "warn", - "no-empty": "off", - "no-multiple-empty-lines": "error", - "no-multi-spaces": "error", - "no-process-exit": "warn", - "space-in-parens": "error", - "no-trailing-spaces": "error", - "no-unused-vars": "error", - "key-spacing": "error", - "space-infix-ops": "error", - "no-unsafe-negation": "error", - "no-loop-func": "warn", - "space-before-function-paren": ["error", "never"], - "space-before-blocks": "error", - "object-curly-spacing": ["error", "always"], - "keyword-spacing": ["error", { - "after": false, - "overrides": { - "try": {"after": true}, - "else": {"after": true}, - "throw": {"after": true}, - "case": {"after": true}, - "return": {"after": true}, - "finally": {"after": true}, - "do": {"after": true} - } - }], - "no-console": "off", - "valid-jsdoc": "error", - "no-useless-computed-key": "error", - "prefer-const": "error" - } + "extends": "webpack", + "globals": { + "document": true, + "window": true + }, + "parserOptions": { + "sourceType": "script" + }, + "rules": { + "comma-dangle": ["error", "never"], + "consistent-return": "off", + "no-param-reassign": "off", + "no-underscore-dangle": "off", + "prefer-destructuring": ["error", {"object": false, "array": false}], + "prefer-rest-params": "off", + "strict": ["error", "safe"] + } } diff --git a/.gitignore b/.gitignore index 7058d9d22f..e4503a17c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +npm-debug.log node_modules /client/live.bundle.js /client/index.bundle.js diff --git a/.travis.yml b/.travis.yml index b8ae2b1747..a92770c711 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,18 @@ sudo: false branches: only: - master -language: node_js + +language: + node_js + node_js: - - "6" - - "4" -script: npm run travis + - '8' + - '6' + - '4' + +script: + npm run ci + after_success: - cat ./coverage/coverage.json | node_modules/codecov.io/bin/codecov.io.js - rm -rf ./coverage diff --git a/bin/webpack-dev-server.js b/bin/webpack-dev-server.js index 314495c70b..5bf1a24d21 100755 --- a/bin/webpack-dev-server.js +++ b/bin/webpack-dev-server.js @@ -1,62 +1,69 @@ #!/usr/bin/env node -"use strict"; -const path = require("path"); -const open = require("opn"); -const fs = require("fs"); -const net = require("net"); -const portfinder = require("portfinder"); -const addDevServerEntrypoints = require("../lib/util/addDevServerEntrypoints"); -const createDomain = require("../lib/util/createDomain"); +'use strict'; + +/* eslint global-require: off, import/order: off, no-console: off */ + +const fs = require('fs'); +const net = require('net'); +const path = require('path'); +const open = require('opn'); +const portfinder = require('portfinder'); +const addDevServerEntrypoints = require('../lib/util/addDevServerEntrypoints'); +const createDomain = require('../lib/util/createDomain'); // eslint-disable-line // Local version replaces global one try { - const localWebpackDevServer = require.resolve(path.join(process.cwd(), "node_modules", "webpack-dev-server", "bin", "webpack-dev-server.js")); - if(__filename !== localWebpackDevServer) { - return require(localWebpackDevServer); - } -} catch(e) {} + const localWebpackDevServer = require.resolve(path.join(process.cwd(), 'node_modules', 'webpack-dev-server', 'bin', 'webpack-dev-server.js')); + if (__filename !== localWebpackDevServer) { + require(localWebpackDevServer); // eslint-disable-line + } +} catch (e) { + // eslint-disable-line +} -const Server = require("../lib/Server"); -const webpack = require("webpack"); +const Server = require('../lib/Server'); +const webpack = require('webpack'); // eslint-disable-line function versionInfo() { - return `webpack-dev-server ${require("../package.json").version}\n` + - `webpack ${require("webpack/package.json").version}`; + return `webpack-dev-server ${require('../package.json').version}\n` + + `webpack ${require('webpack/package.json').version}`; } function colorInfo(useColor, msg) { - if(useColor) - // Make text blue and bold, so it *pops* - return `\u001b[1m\u001b[34m${msg}\u001b[39m\u001b[22m`; - return msg; + if (useColor) { + // Make text blue and bold, so it *pops* + return `\u001b[1m\u001b[34m${msg}\u001b[39m\u001b[22m`; + } + return msg; } function colorError(useColor, msg) { - if(useColor) - // Make text red and bold, so it *pops* - return `\u001b[1m\u001b[31m${msg}\u001b[39m\u001b[22m`; - return msg; + if (useColor) { + // Make text red and bold, so it *pops* + return `\u001b[1m\u001b[31m${msg}\u001b[39m\u001b[22m`; + } + return msg; } +// eslint-disable-next-line const defaultTo = (value, def) => value == null ? def : value; -const yargs = require("yargs") - .usage(`${versionInfo() - }\nUsage: https://webpack.js.org/configuration/dev-server/`); +const yargs = require('yargs') + .usage(`${versionInfo()}\nUsage: https://webpack.js.org/configuration/dev-server/`); -require("webpack/bin/config-yargs")(yargs); +require('webpack/bin/config-yargs')(yargs); // It is important that this is done after the webpack yargs config, // so it overrides webpack's version info. yargs.version(versionInfo); -const ADVANCED_GROUP = "Advanced options:"; -const DISPLAY_GROUP = "Stats options:"; -const SSL_GROUP = "SSL options:"; -const CONNECTION_GROUP = "Connection options:"; -const RESPONSE_GROUP = "Response options:"; -const BASIC_GROUP = "Basic options:"; +const ADVANCED_GROUP = 'Advanced options:'; +const DISPLAY_GROUP = 'Stats options:'; +const SSL_GROUP = 'SSL options:'; +const CONNECTION_GROUP = 'Connection options:'; +const RESPONSE_GROUP = 'Response options:'; +const BASIC_GROUP = 'Basic options:'; // Taken out of yargs because we must know if // it wasn't given by the user, in which case @@ -64,433 +71,401 @@ const BASIC_GROUP = "Basic options:"; const DEFAULT_PORT = 8080; yargs.options({ - "bonjour": { - type: "boolean", - describe: "Broadcasts the server via ZeroConf networking on start" - }, - "lazy": { - type: "boolean", - describe: "Lazy" - }, - "inline": { - type: "boolean", - default: true, - describe: "Inline mode (set to false to disable including client scripts like livereload)" - }, - "progress": { - type: "boolean", - describe: "Print compilation progress in percentage", - group: BASIC_GROUP - }, - "hot-only": { - type: "boolean", - describe: "Do not refresh page if HMR fails", - group: ADVANCED_GROUP - }, - "stdin": { - type: "boolean", - describe: "close when stdin ends" - }, - "open": { - type: "boolean", - describe: "Open default browser" - }, - "useLocalIp": { - type: "boolean", - describe: "Open default browser with local IP" - }, - "open-page": { - type: "string", - describe: "Open default browser with the specified page", - requiresArg: true, - }, - "color": { - type: "boolean", - alias: "colors", - default: function supportsColor() { - return require("supports-color"); - }, - group: DISPLAY_GROUP, - describe: "Enables/Disables colors on the console" - }, - "info": { - type: "boolean", - group: DISPLAY_GROUP, - default: true, - describe: "Info" - }, - "quiet": { - type: "boolean", - group: DISPLAY_GROUP, - describe: "Quiet" - }, - "client-log-level": { - type: "string", - group: DISPLAY_GROUP, - default: "info", - describe: "Log level in the browser (info, warning, error or none)" - }, - "https": { - type: "boolean", - group: SSL_GROUP, - describe: "HTTPS" - }, - "key": { - type: "string", - describe: "Path to a SSL key.", - group: SSL_GROUP - }, - "cert": { - type: "string", - describe: "Path to a SSL certificate.", - group: SSL_GROUP - }, - "cacert": { - type: "string", - describe: "Path to a SSL CA certificate.", - group: SSL_GROUP - }, - "pfx": { - type: "string", - describe: "Path to a SSL pfx file.", - group: SSL_GROUP - }, - "pfx-passphrase": { - type: "string", - describe: "Passphrase for pfx file.", - group: SSL_GROUP - }, - "content-base": { - type: "string", - describe: "A directory or URL to serve HTML content from.", - group: RESPONSE_GROUP - }, - "watch-content-base": { - type: "boolean", - describe: "Enable live-reloading of the content-base.", - group: RESPONSE_GROUP - }, - "history-api-fallback": { - type: "boolean", - describe: "Fallback to /index.html for Single Page Applications.", - group: RESPONSE_GROUP - }, - "compress": { - type: "boolean", - describe: "Enable gzip compression", - group: RESPONSE_GROUP - }, - "port": { - describe: "The port", - group: CONNECTION_GROUP - }, - "disable-host-check": { - type: "boolean", - describe: "Will not check the host", - group: CONNECTION_GROUP - }, - "socket": { - type: "String", - describe: "Socket to listen", - group: CONNECTION_GROUP - }, - "public": { - type: "string", - describe: "The public hostname/ip address of the server", - group: CONNECTION_GROUP - }, - "host": { - type: "string", - default: "localhost", - describe: "The hostname/ip address the server will bind to", - group: CONNECTION_GROUP - }, - "allowed-hosts": { - type: "string", - describe: "A comma-delimited string of hosts that are allowed to access the dev server", - group: CONNECTION_GROUP - } + bonjour: { + type: 'boolean', + describe: 'Broadcasts the server via ZeroConf networking on start' + }, + lazy: { + type: 'boolean', + describe: 'Lazy' + }, + inline: { + type: 'boolean', + default: true, + describe: 'Inline mode (set to false to disable including client scripts like livereload)' + }, + progress: { + type: 'boolean', + describe: 'Print compilation progress in percentage', + group: BASIC_GROUP + }, + 'hot-only': { + type: 'boolean', + describe: 'Do not refresh page if HMR fails', + group: ADVANCED_GROUP + }, + stdin: { + type: 'boolean', + describe: 'close when stdin ends' + }, + open: { + type: 'boolean', + describe: 'Open default browser' + }, + useLocalIp: { + type: 'boolean', + describe: 'Open default browser with local IP' + }, + 'open-page': { + type: 'string', + describe: 'Open default browser with the specified page', + requiresArg: true + }, + color: { + type: 'boolean', + alias: 'colors', + default: function supportsColor() { + return require('supports-color'); + }, + group: DISPLAY_GROUP, + describe: 'Enables/Disables colors on the console' + }, + info: { + type: 'boolean', + group: DISPLAY_GROUP, + default: true, + describe: 'Info' + }, + quiet: { + type: 'boolean', + group: DISPLAY_GROUP, + describe: 'Quiet' + }, + 'client-log-level': { + type: 'string', + group: DISPLAY_GROUP, + default: 'info', + describe: 'Log level in the browser (info, warning, error or none)' + }, + https: { + type: 'boolean', + group: SSL_GROUP, + describe: 'HTTPS' + }, + key: { + type: 'string', + describe: 'Path to a SSL key.', + group: SSL_GROUP + }, + cert: { + type: 'string', + describe: 'Path to a SSL certificate.', + group: SSL_GROUP + }, + cacert: { + type: 'string', + describe: 'Path to a SSL CA certificate.', + group: SSL_GROUP + }, + pfx: { + type: 'string', + describe: 'Path to a SSL pfx file.', + group: SSL_GROUP + }, + 'pfx-passphrase': { + type: 'string', + describe: 'Passphrase for pfx file.', + group: SSL_GROUP + }, + 'content-base': { + type: 'string', + describe: 'A directory or URL to serve HTML content from.', + group: RESPONSE_GROUP + }, + 'watch-content-base': { + type: 'boolean', + describe: 'Enable live-reloading of the content-base.', + group: RESPONSE_GROUP + }, + 'history-api-fallback': { + type: 'boolean', + describe: 'Fallback to /index.html for Single Page Applications.', + group: RESPONSE_GROUP + }, + compress: { + type: 'boolean', + describe: 'Enable gzip compression', + group: RESPONSE_GROUP + }, + port: { + describe: 'The port', + group: CONNECTION_GROUP + }, + 'disable-host-check': { + type: 'boolean', + describe: 'Will not check the host', + group: CONNECTION_GROUP + }, + socket: { + type: 'String', + describe: 'Socket to listen', + group: CONNECTION_GROUP + }, + public: { + type: 'string', + describe: 'The public hostname/ip address of the server', + group: CONNECTION_GROUP + }, + host: { + type: 'string', + default: 'localhost', + describe: 'The hostname/ip address the server will bind to', + group: CONNECTION_GROUP + }, + 'allowed-hosts': { + type: 'string', + describe: 'A comma-delimited string of hosts that are allowed to access the dev server', + group: CONNECTION_GROUP + } }); const argv = yargs.argv; - -const wpOpt = require("webpack/bin/convert-argv")(yargs, argv, { - outputFilename: "/bundle.js" +const wpOpt = require('webpack/bin/convert-argv')(yargs, argv, { + outputFilename: '/bundle.js' }); -function processOptions(wpOpt) { - // process Promise - if(typeof wpOpt.then === "function") { - wpOpt.then(processOptions).catch(function(err) { - console.error(err.stack || err); - process.exit(); // eslint-disable-line - }); - return; - } +function processOptions(webpackOptions) { + // process Promise + if (typeof webpackOptions.then === 'function') { + webpackOptions.then(processOptions).catch((err) => { + console.error(err.stack || err); + process.exit(); // eslint-disable-line + }); + return; + } + + const firstWpOpt = Array.isArray(webpackOptions) ? webpackOptions[0] : webpackOptions; + + const options = webpackOptions.devServer || firstWpOpt.devServer || {}; + + if (argv.bonjour) { options.bonjour = true; } + + if (argv.host !== 'localhost' || !options.host) { options.host = argv.host; } + + if (argv['allowed-hosts']) { options.allowedHosts = argv['allowed-hosts'].split(','); } + + if (argv.public) { options.public = argv.public; } + + if (argv.socket) { options.socket = argv.socket; } + + if (!options.publicPath) { + // eslint-disable-next-line + options.publicPath = firstWpOpt.output && firstWpOpt.output.publicPath || ''; + if (!/^(https?:)?\/\//.test(options.publicPath) && options.publicPath[0] !== '/') { + options.publicPath = `/${options.publicPath}`; + } + } + + if (!options.filename) { options.filename = firstWpOpt.output && firstWpOpt.output.filename; } + + if (!options.watchOptions) { options.watchOptions = firstWpOpt.watchOptions; } + + if (argv.stdin) { + process.stdin.on('end', () => { + process.exit(0); // eslint-disable-line no-process-exit + }); + process.stdin.resume(); + } + + if (!options.hot) { options.hot = argv.hot; } - const firstWpOpt = Array.isArray(wpOpt) ? wpOpt[0] : wpOpt; + if (!options.hotOnly) { options.hotOnly = argv['hot-only']; } - const options = wpOpt.devServer || firstWpOpt.devServer || {}; + if (!options.clientLogLevel) { options.clientLogLevel = argv['client-log-level']; } - if(argv.bonjour) - options.bonjour = true; + // eslint-disable-next-line + if (options.contentBase === undefined) { + if (argv['content-base']) { + options.contentBase = argv['content-base']; + if (Array.isArray(options.contentBase)) { + options.contentBase = options.contentBase.map(val => path.resolve(val)); + } else if (/^[0-9]$/.test(options.contentBase)) { options.contentBase = +options.contentBase; } else if (!/^(https?:)?\/\//.test(options.contentBase)) { options.contentBase = path.resolve(options.contentBase); } + // It is possible to disable the contentBase by using `--no-content-base`, which results in arg["content-base"] = false + } else if (argv['content-base'] === false) { + options.contentBase = false; + } + } - if(argv.host !== "localhost" || !options.host) - options.host = argv.host; + if (argv['watch-content-base']) { options.watchContentBase = true; } - if(argv["allowed-hosts"]) - options.allowedHosts = argv["allowed-hosts"].split(","); + if (!options.stats) { + options.stats = { + cached: false, + cachedAssets: false + }; + } - if(argv.public) - options.public = argv.public; + if (typeof options.stats === 'object' && typeof options.stats.colors === 'undefined') { options.stats.colors = argv.color; } - if(argv.socket) - options.socket = argv.socket; + if (argv.lazy) { options.lazy = true; } - if(!options.publicPath) { - options.publicPath = firstWpOpt.output && firstWpOpt.output.publicPath || ""; - if(!/^(https?:)?\/\//.test(options.publicPath) && options.publicPath[0] !== "/") - options.publicPath = `/${options.publicPath}`; - } + if (!argv.info) { options.noInfo = true; } - if(!options.filename) - options.filename = firstWpOpt.output && firstWpOpt.output.filename; + if (argv.quiet) { options.quiet = true; } - if(!options.watchOptions) - options.watchOptions = firstWpOpt.watchOptions; + if (argv.https) { options.https = true; } - if(argv["stdin"]) { - process.stdin.on("end", function() { - process.exit(0); // eslint-disable-line no-process-exit - }); - process.stdin.resume(); - } + if (argv.cert) { options.cert = fs.readFileSync(path.resolve(argv.cert)); } - if(!options.hot) - options.hot = argv["hot"]; - - if(!options.hotOnly) - options.hotOnly = argv["hot-only"]; - - if(!options.clientLogLevel) - options.clientLogLevel = argv["client-log-level"]; - - if(options.contentBase === undefined) { - if(argv["content-base"]) { - options.contentBase = argv["content-base"]; - if(Array.isArray(options.contentBase)) { - options.contentBase = options.contentBase.map(function(val) { - return path.resolve(val); - }); - } else if(/^[0-9]$/.test(options.contentBase)) - options.contentBase = +options.contentBase; - else if(!/^(https?:)?\/\//.test(options.contentBase)) - options.contentBase = path.resolve(options.contentBase); - // It is possible to disable the contentBase by using `--no-content-base`, which results in arg["content-base"] = false - } else if(argv["content-base"] === false) { - options.contentBase = false; - } - } - - if(argv["watch-content-base"]) - options.watchContentBase = true; - - if(!options.stats) { - options.stats = { - cached: false, - cachedAssets: false - }; - } - - if(typeof options.stats === "object" && typeof options.stats.colors === "undefined") - options.stats.colors = argv.color; - - if(argv["lazy"]) - options.lazy = true; - - if(!argv["info"]) - options.noInfo = true; - - if(argv["quiet"]) - options.quiet = true; - - if(argv["https"]) - options.https = true; - - if(argv["cert"]) - options.cert = fs.readFileSync(path.resolve(argv["cert"])); - - if(argv["key"]) - options.key = fs.readFileSync(path.resolve(argv["key"])); - - if(argv["cacert"]) - options.ca = fs.readFileSync(path.resolve(argv["cacert"])); + if (argv.key) { options.key = fs.readFileSync(path.resolve(argv.key)); } - if(argv["pfx"]) - options.pfx = fs.readFileSync(path.resolve(argv["pfx"])); + if (argv.cacert) { options.ca = fs.readFileSync(path.resolve(argv.cacert)); } - if(argv["pfx-passphrase"]) - options.pfxPassphrase = argv["pfx-passphrase"]; - - if(argv["inline"] === false) - options.inline = false; - - if(argv["history-api-fallback"]) - options.historyApiFallback = true; - - if(argv["compress"]) - options.compress = true; - - if(argv["disable-host-check"]) - options.disableHostCheck = true; - - if(argv["open"] || argv["open-page"]) { - options.open = true; - options.openPage = argv["open-page"]; - } - - if(options.open && !options.openPage) - options.openPage = ""; - - if(argv["useLocalIp"]) - options.useLocalIp = true; - - // Kind of weird, but ensures prior behavior isn't broken in cases - // that wouldn't throw errors. E.g. both argv.port and options.port - // were specified, but since argv.port is 8080, options.port will be - // tried first instead. - options.port = argv.port === DEFAULT_PORT ? defaultTo(options.port, argv.port) : defaultTo(argv.port, options.port); - if(options.port != null) { - startDevServer(wpOpt, options); - return; - } - - portfinder.basePort = DEFAULT_PORT; - portfinder.getPort(function(err, port) { - if(err) throw err; - options.port = port; - startDevServer(wpOpt, options); - }); + if (argv.pfx) { options.pfx = fs.readFileSync(path.resolve(argv.pfx)); } + + if (argv['pfx-passphrase']) { options.pfxPassphrase = argv['pfx-passphrase']; } + + if (argv.inline === false) { options.inline = false; } + + if (argv['history-api-fallback']) { options.historyApiFallback = true; } + + if (argv.compress) { options.compress = true; } + + if (argv['disable-host-check']) { options.disableHostCheck = true; } + + if (argv.open || argv['open-page']) { + options.open = true; + options.openPage = argv['open-page']; + } + + if (options.open && !options.openPage) { options.openPage = ''; } + + if (argv.useLocalIp) { options.useLocalIp = true; } + + // Kind of weird, but ensures prior behavior isn't broken in cases + // that wouldn't throw errors. E.g. both argv.port and options.port + // were specified, but since argv.port is 8080, options.port will be + // tried first instead. + options.port = argv.port === DEFAULT_PORT ? defaultTo(options.port, argv.port) : defaultTo(argv.port, options.port); + if (options.port != null) { + startDevServer(wpOpt, options); + return; + } + + portfinder.basePort = DEFAULT_PORT; + portfinder.getPort((err, port) => { + if (err) throw err; + options.port = port; + startDevServer(wpOpt, options); + }); } -function startDevServer(wpOpt, options) { - addDevServerEntrypoints(wpOpt, options); - - let compiler; - try { - compiler = webpack(wpOpt); - } catch(e) { - if(e instanceof webpack.WebpackOptionsValidationError) { - console.error(colorError(options.stats.colors, e.message)); - process.exit(1); // eslint-disable-line - } - throw e; - } - - if(argv["progress"]) { - compiler.apply(new webpack.ProgressPlugin({ - profile: argv["profile"] - })); - } - - const uri = createDomain(options) + (options.inline !== false || options.lazy === true ? "/" : "/webpack-dev-server/"); - - let server; - try { - server = new Server(compiler, options); - } catch(e) { - const OptionsValidationError = require("../lib/OptionsValidationError"); - if(e instanceof OptionsValidationError) { - console.error(colorError(options.stats.colors, e.message)); - process.exit(1); // eslint-disable-line - } - throw e; - } - - ["SIGINT", "SIGTERM"].forEach(function(sig) { - process.on(sig, function() { - server.close(); - process.exit(); // eslint-disable-line no-process-exit - }); - }); - - if(options.socket) { - server.listeningApp.on("error", function(e) { - if(e.code === "EADDRINUSE") { - const clientSocket = new net.Socket(); - clientSocket.on("error", function(e) { - if(e.code === "ECONNREFUSED") { - // No other server listening on this socket so it can be safely removed - fs.unlinkSync(options.socket); - server.listen(options.socket, options.host, function(err) { - if(err) throw err; - }); - } - }); - clientSocket.connect({ path: options.socket }, function() { - throw new Error("This socket is already used"); - }); - } - }); - server.listen(options.socket, options.host, function(err) { - if(err) throw err; - const READ_WRITE = 438; // chmod 666 (rw rw rw) - fs.chmod(options.socket, READ_WRITE, function(err) { - if(err) throw err; - reportReadiness(uri, options); - }); - }); - } else { - server.listen(options.port, options.host, function(err) { - if(err) throw err; - if(options.bonjour) broadcastZeroconf(options); - reportReadiness(uri, options); - }); - } +function startDevServer(webpackOptions, options) { + addDevServerEntrypoints(webpackOptions, options); + + let compiler; + try { + compiler = webpack(webpackOptions); + } catch (e) { + if (e instanceof webpack.WebpackOptionsValidationError) { + console.error(colorError(options.stats.colors, e.message)); + process.exit(1); // eslint-disable-line + } + throw e; + } + + if (argv.progress) { + compiler.apply(new webpack.ProgressPlugin({ + profile: argv.profile + })); + } + + const uri = createDomain(options) + (options.inline !== false || options.lazy === true ? '/' : '/webpack-dev-server/'); + + let server; + try { + server = new Server(compiler, options); + } catch (e) { + const OptionsValidationError = require('../lib/OptionsValidationError'); + if (e instanceof OptionsValidationError) { + console.error(colorError(options.stats.colors, e.message)); + process.exit(1); // eslint-disable-line + } + throw e; + } + + ['SIGINT', 'SIGTERM'].forEach((sig) => { + process.on(sig, () => { + server.close(); + process.exit(); // eslint-disable-line no-process-exit + }); + }); + + if (options.socket) { + server.listeningApp.on('error', (e) => { + if (e.code === 'EADDRINUSE') { + const clientSocket = new net.Socket(); + clientSocket.on('error', (clientError) => { + if (clientError.code === 'ECONNREFUSED') { + // No other server listening on this socket so it can be safely removed + fs.unlinkSync(options.socket); + server.listen(options.socket, options.host, (err) => { + if (err) throw err; + }); + } + }); + clientSocket.connect({ path: options.socket }, () => { + throw new Error('This socket is already used'); + }); + } + }); + server.listen(options.socket, options.host, (err) => { + if (err) throw err; + // chmod 666 (rw rw rw) + const READ_WRITE = 438; + fs.chmod(options.socket, READ_WRITE, (fsError) => { + if (fsError) throw fsError; + reportReadiness(uri, options); + }); + }); + } else { + server.listen(options.port, options.host, (err) => { + if (err) throw err; + if (options.bonjour) broadcastZeroconf(options); + reportReadiness(uri, options); + }); + } } function reportReadiness(uri, options) { - const useColor = argv.color; - const contentBase = Array.isArray(options.contentBase) ? options.contentBase.join(", ") : options.contentBase; - - if(!options.quiet) { - let startSentence = `Project is running at ${colorInfo(useColor, uri)}` - if(options.socket) { - startSentence = `Listening to socket at ${colorInfo(useColor, options.socket)}`; - } - console.log((argv["progress"] ? "\n" : "") + startSentence); - - console.log(`webpack output is served from ${colorInfo(useColor, options.publicPath)}`); - - if(contentBase) - console.log(`Content not from webpack is served from ${colorInfo(useColor, contentBase)}`); - - if(options.historyApiFallback) - console.log(`404s will fallback to ${colorInfo(useColor, options.historyApiFallback.index || "/index.html")}`); - - if(options.bonjour) - console.log("Broadcasting \"http\" with subtype of \"webpack\" via ZeroConf DNS (Bonjour)"); - } - if(options.open) { - open(uri + options.openPage).catch(function() { - console.log("Unable to open browser. If you are running in a headless environment, please do not use the open flag."); - }); - } + const useColor = argv.color; + const contentBase = Array.isArray(options.contentBase) ? options.contentBase.join(', ') : options.contentBase; + + if (!options.quiet) { + let startSentence = `Project is running at ${colorInfo(useColor, uri)}`; + if (options.socket) { + startSentence = `Listening to socket at ${colorInfo(useColor, options.socket)}`; + } + console.log((argv.progress ? '\n' : '') + startSentence); + + console.log(`webpack output is served from ${colorInfo(useColor, options.publicPath)}`); + + if (contentBase) { console.log(`Content not from webpack is served from ${colorInfo(useColor, contentBase)}`); } + + if (options.historyApiFallback) { console.log(`404s will fallback to ${colorInfo(useColor, options.historyApiFallback.index || '/index.html')}`); } + + if (options.bonjour) { console.log('Broadcasting "http" with subtype of "webpack" via ZeroConf DNS (Bonjour)'); } + } + if (options.open) { + open(uri + options.openPage).catch(() => { + console.log('Unable to open browser. If you are running in a headless environment, please do not use the open flag.'); + }); + } } function broadcastZeroconf(options) { - const bonjour = require("bonjour")(); - bonjour.publish({ - name: "Webpack Dev Server", - port: options.port, - type: "http", - subtypes: ["webpack"] - }); - process.on("exit", function() { - bonjour.unpublishAll(function() { - bonjour.destroy(); - }); - }); + const bonjour = require('bonjour')(); + bonjour.publish({ + name: 'Webpack Dev Server', + port: options.port, + type: 'http', + subtypes: ['webpack'] + }); + process.on('exit', () => { + bonjour.unpublishAll(() => { + bonjour.destroy(); + }); + }); } processOptions(wpOpt); diff --git a/client/index.js b/client/index.js index 1eb8ff1643..a9780e258a 100644 --- a/client/index.js +++ b/client/index.js @@ -1,207 +1,207 @@ -/* global __resourceQuery WorkerGlobalScope */ -var url = require("url"); -var stripAnsi = require("strip-ansi"); -var log = require("loglevel") -var socket = require("./socket"); -var overlay = require("./overlay"); +'use strict'; + +/* global __resourceQuery WorkerGlobalScope self */ +/* eslint prefer-destructuring: off */ + +const url = require('url'); +const stripAnsi = require('strip-ansi'); +const log = require('loglevel'); +const socket = require('./socket'); +const overlay = require('./overlay'); function getCurrentScriptSource() { - // `document.currentScript` is the most accurate way to find the current script, - // but is not supported in all browsers. - if(document.currentScript) - return document.currentScript.getAttribute("src"); - // Fall back to getting all scripts in the document. - var scriptElements = document.scripts || []; - var currentScript = scriptElements[scriptElements.length - 1]; - if(currentScript) - return currentScript.getAttribute("src"); - // Fail as there was no script to use. - throw new Error("[WDS] Failed to get current script source"); + // `document.currentScript` is the most accurate way to find the current script, + // but is not supported in all browsers. + if (document.currentScript) { return document.currentScript.getAttribute('src'); } + // Fall back to getting all scripts in the document. + const scriptElements = document.scripts || []; + const currentScript = scriptElements[scriptElements.length - 1]; + if (currentScript) { return currentScript.getAttribute('src'); } + // Fail as there was no script to use. + throw new Error('[WDS] Failed to get current script source'); } -var urlParts; -if(typeof __resourceQuery === "string" && __resourceQuery) { - // If this bundle is inlined, use the resource query to get the correct url. - urlParts = url.parse(__resourceQuery.substr(1)); +let urlParts; +if (typeof __resourceQuery === 'string' && __resourceQuery) { + // If this bundle is inlined, use the resource query to get the correct url. + urlParts = url.parse(__resourceQuery.substr(1)); } else { - // Else, get the url from the '); - /* eslint-enable quotes */ - } catch(e) { - return next(); - } -} +Server.prototype.listen = function (port, hostname) { + this.listenHostname = hostname; + // eslint-disable-next-line + const returnValue = this.listeningApp.listen.apply(this.listeningApp, arguments); + const sockServer = sockjs.createServer({ + // Use provided up-to-date sockjs-client + sockjs_url: '/__webpack_dev_server__/sockjs.bundle.js', + // Limit useless logs + log(severity, line) { + if (severity === 'error') { + console.log(line); + } + } + }); + sockServer.on('connection', (conn) => { + if (!conn) return; + if (!this.checkHost(conn.headers)) { + this.sockWrite([conn], 'error', 'Invalid Host header'); + conn.close(); + return; + } + this.sockets.push(conn); + + conn.on('close', () => { + const connIndex = this.sockets.indexOf(conn); + if (connIndex >= 0) { + this.sockets.splice(connIndex, 1); + } + }); + + if (this.clientLogLevel) { this.sockWrite([conn], 'log-level', this.clientLogLevel); } + + if (this.clientOverlay) { this.sockWrite([conn], 'overlay', this.clientOverlay); } + + if (this.hot) this.sockWrite([conn], 'hot'); + + if (!this._stats) return; + this._sendStats([conn], this._stats.toJson(clientStats), true); + }); + + sockServer.installHandlers(this.listeningApp, { + prefix: '/sockjs-node' + }); + return returnValue; +}; + +Server.prototype.close = function (callback) { + this.sockets.forEach((sock) => { + sock.close(); + }); + this.sockets = []; + this.listeningApp.close(() => { + this.middleware.close(callback); + }); + + this.contentBaseWatchers.forEach((watcher) => { + watcher.close(); + }); + this.contentBaseWatchers = []; +}; + +Server.prototype.sockWrite = function (sockets, type, data) { + sockets.forEach((sock) => { + sock.write(JSON.stringify({ + type, + data + })); + }); +}; + +Server.prototype.serveMagicHtml = function (req, res, next) { + const _path = req.path; + try { + if (!this.middleware.fileSystem.statSync(this.middleware.getFilenameFromUrl(`${_path}.js`)).isFile()) { return next(); } + // Serve a page that executes the javascript + /* eslint-disable quotes */ + res.write(''); + /* eslint-enable quotes */ + } catch (e) { + return next(); + } +}; // send stats to a socket or multiple sockets -Server.prototype._sendStats = function(sockets, stats, force) { - if(!force && - stats && - (!stats.errors || stats.errors.length === 0) && - stats.assets && - stats.assets.every((asset) => !asset.emitted) - ) - return this.sockWrite(sockets, "still-ok"); - this.sockWrite(sockets, "hash", stats.hash); - if(stats.errors.length > 0) - this.sockWrite(sockets, "errors", stats.errors); - else if(stats.warnings.length > 0) - this.sockWrite(sockets, "warnings", stats.warnings); - else - this.sockWrite(sockets, "ok"); -} - -Server.prototype._watch = function(path) { - const watcher = chokidar.watch(path).on("change", () => { - this.sockWrite(this.sockets, "content-changed"); - }); - - this.contentBaseWatchers.push(watcher); -} - -Server.prototype.invalidate = function() { - if(this.middleware) this.middleware.invalidate(); -} +Server.prototype._sendStats = function (sockets, stats, force) { + if (!force && + stats && + (!stats.errors || stats.errors.length === 0) && + stats.assets && + stats.assets.every(asset => !asset.emitted) + ) { return this.sockWrite(sockets, 'still-ok'); } + this.sockWrite(sockets, 'hash', stats.hash); + if (stats.errors.length > 0) { this.sockWrite(sockets, 'errors', stats.errors); } else if (stats.warnings.length > 0) { this.sockWrite(sockets, 'warnings', stats.warnings); } else { this.sockWrite(sockets, 'ok'); } +}; + +Server.prototype._watch = function (watchPath) { + const watcher = chokidar.watch(watchPath).on('change', () => { + this.sockWrite(this.sockets, 'content-changed'); + }); + + this.contentBaseWatchers.push(watcher); +}; + +Server.prototype.invalidate = function () { + if (this.middleware) this.middleware.invalidate(); +}; // Export this logic, so that other implementations, like task-runners can use it -Server.addDevServerEntrypoints = require("./util/addDevServerEntrypoints"); +Server.addDevServerEntrypoints = require('./util/addDevServerEntrypoints'); module.exports = Server; diff --git a/lib/polyfills.js b/lib/polyfills.js new file mode 100644 index 0000000000..887c5b3f3f --- /dev/null +++ b/lib/polyfills.js @@ -0,0 +1,9 @@ +'use strict'; + +/* polyfills for Node 4.8.x users */ +/* eslint no-extend-native: off, global-require: off */ + +// internal-ip@2.x uses [].includes +if (!Array.prototype.includes) { + Array.prototype.includes = require('array-includes'); +} diff --git a/lib/util/addDevServerEntrypoints.js b/lib/util/addDevServerEntrypoints.js index 0a22d47a66..f68b6d0bb5 100644 --- a/lib/util/addDevServerEntrypoints.js +++ b/lib/util/addDevServerEntrypoints.js @@ -1,26 +1,26 @@ -"use strict"; -const createDomain = require("./createDomain"); +'use strict'; + +/* eslint no-param-reassign: 'off' */ + +const createDomain = require('./createDomain'); module.exports = function addDevServerEntrypoints(webpackOptions, devServerOptions) { - if(devServerOptions.inline !== false) { - const domain = createDomain(devServerOptions); - const devClient = [`${require.resolve("../../client/")}?${domain}`]; + if (devServerOptions.inline !== false) { + const domain = createDomain(devServerOptions); + const devClient = [`${require.resolve('../../client/')}?${domain}`]; - if(devServerOptions.hotOnly) - devClient.push("webpack/hot/only-dev-server"); - else if(devServerOptions.hot) - devClient.push("webpack/hot/dev-server"); + if (devServerOptions.hotOnly) { devClient.push('webpack/hot/only-dev-server'); } else if (devServerOptions.hot) { devClient.push('webpack/hot/dev-server'); } - [].concat(webpackOptions).forEach((wpOpt) => { - if(typeof wpOpt.entry === "object" && !Array.isArray(wpOpt.entry)) { - Object.keys(wpOpt.entry).forEach((key) => { - wpOpt.entry[key] = devClient.concat(wpOpt.entry[key]); - }); - } else if(typeof wpOpt.entry === "function") { - wpOpt.entry = wpOpt.entry(devClient); - } else { - wpOpt.entry = devClient.concat(wpOpt.entry); - } - }); - } + [].concat(webpackOptions).forEach((wpOpt) => { + if (typeof wpOpt.entry === 'object' && !Array.isArray(wpOpt.entry)) { + Object.keys(wpOpt.entry).forEach((key) => { + wpOpt.entry[key] = devClient.concat(wpOpt.entry[key]); + }); + } else if (typeof wpOpt.entry === 'function') { + wpOpt.entry = wpOpt.entry(devClient); + } else { + wpOpt.entry = devClient.concat(wpOpt.entry); + } + }); + } }; diff --git a/lib/util/createDomain.js b/lib/util/createDomain.js index 00a9c762d1..54fa36ccc9 100644 --- a/lib/util/createDomain.js +++ b/lib/util/createDomain.js @@ -1,14 +1,15 @@ -"use strict"; -const url = require("url"); -const internalIp = require("internal-ip"); +'use strict'; + +const url = require('url'); +const internalIp = require('internal-ip'); module.exports = function createDomain(options) { - const protocol = options.https ? "https" : "http"; + const protocol = options.https ? 'https' : 'http'; - // the formatted domain (url without path) of the webpack server - return options.public ? `${protocol}://${options.public}` : url.format({ - protocol: protocol, - hostname: options.useLocalIp ? internalIp.v4() : options.host, - port: options.socket ? 0 : options.port.toString() - }); + // the formatted domain (url without path) of the webpack server + return options.public ? `${protocol}://${options.public}` : url.format({ + protocol, + hostname: options.useLocalIp ? internalIp.v4() : options.host, + port: options.socket ? 0 : options.port.toString() + }); }; diff --git a/package.json b/package.json index 9255aeaf35..180faf8cc6 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,47 @@ { "name": "webpack-dev-server", "version": "2.7.1", - "author": "Tobias Koppers @sokra", "description": "Serves a webpack app. Updates the browser on changes.", + "license": "MIT", + "repository": "webpack/webpack-dev-server", + "author": "Tobias Koppers @sokra", + "homepage": "http://github.com/webpack/webpack-dev-server", + "maintainers": [ + { + "name": "Andrew Powell", + "email": "andrew@shellscape.org", + "url": "shellscape.org" + } + ], + "main": "lib/Server.js", + "bin": "bin/webpack-dev-server.js", + "engines": { + "node": ">=4.7" + }, + "scripts": { + "beautify": "npm run lint -- --fix", + "ci": "npm run cover -- --report lcovonly && npm run test", + "cover": "istanbul cover node_modules/mocha/bin/_mocha", + "lint": "eslint bin lib test examples client/{index,live,socket,sockjs,overlay,webpack.config}.js", + "mocha": "mocha --full-trace --check-leaks", + "prepublish": "npm run -s build:live && npm run -s build:index && npm run -s build:sockjs", + "test": "npm run lint && npm run mocha", + "build:live": "webpack ./client/live.js client/live.bundle.js --color --config client/webpack.config.js", + "build:index": "webpack ./client/index.js client/index.bundle.js --color --config client/webpack.config.js", + "build:sockjs": "webpack ./client/sockjs.js client/sockjs.bundle.js --color --config client/webpack.sockjs.config.js" + }, + "files": [ + "lib/", + "bin", + "client/", + "ssl/" + ], "peerDependencies": { "webpack": "^2.2.0 || ^3.0.0" }, "dependencies": { "ansi-html": "0.0.7", + "array-includes": "^3.0.3", "bonjour": "^3.5.0", "chokidar": "^1.6.0", "compression": "^1.5.2", @@ -16,69 +50,43 @@ "express": "^4.13.3", "html-entities": "^1.2.0", "http-proxy-middleware": "~0.17.4", - "internal-ip": "^1.2.0", + "internal-ip": "^2.0.2", "ip": "^1.1.5", "loglevel": "^1.4.1", - "opn": "4.0.2", + "opn": "^5.1.0", "portfinder": "^1.0.9", "selfsigned": "^1.9.1", "serve-index": "^1.7.2", "sockjs": "0.3.18", "sockjs-client": "1.1.4", "spdy": "^3.4.1", - "strip-ansi": "^3.0.0", - "supports-color": "^3.1.1", + "strip-ansi": "^4.0.0", + "supports-color": "^4.2.1", "webpack-dev-middleware": "^1.11.0", - "yargs": "^6.0.0" + "yargs": "^8.0.2" }, "devDependencies": { "codecov.io": "^0.1.6", - "css-loader": "~0.26.1", - "eslint": "^3.4.0", - "file-loader": "~0.10.0", + "css-loader": "^0.28.5", + "eslint": "^4.5.0", + "eslint-config-webpack": "^1.2.5", + "eslint-plugin-import": "^2.7.0", + "file-loader": "^0.11.2", "istanbul": "^0.4.5", - "jquery": "^2.2.0", + "jquery": "^3.2.1", "less": "^2.5.1", - "less-loader": "~2.2.0", + "less-loader": "^4.0.5", "mocha": "^3.0.2", - "mocha-sinon": "^1.1.6", + "mocha-sinon": "^2.0.0", "pug": "^2.0.0-beta5", "pug-loader": "^2.3.0", - "should": "^11.1.0", - "sinon": "^1.17.6", - "style-loader": "~0.13.0", - "supertest": "^2.0.1", + "should": "^12.0.0", + "sinon": "^3.2.1", + "style-loader": "^0.18.2", + "supertest": "^3.0.0", + "uglifyjs-webpack-plugin": "^1.0.0-beta.2", "url-loader": "~0.5.6", "webpack": "^3.0.0", - "ws": "^1.1.1" - }, - "license": "MIT", - "repository": { - "type": "git", - "url": "git://github.com/webpack/webpack-dev-server.git" - }, - "homepage": "http://github.com/webpack/webpack-dev-server", - "main": "lib/Server.js", - "bin": "bin/webpack-dev-server.js", - "engines": { - "node": ">=4.7" - }, - "files": [ - "lib/", - "bin", - "client/", - "ssl/" - ], - "scripts": { - "prepublish": "npm run -s client-live && npm run -s client-index && npm run -s client-sockjs", - "client-live": "webpack ./client/live.js client/live.bundle.js --color --config client/webpack.config.js -p", - "client-index": "webpack ./client/index.js client/index.bundle.js --color --config client/webpack.config.js -p", - "client-sockjs": "webpack ./client/sockjs.js client/sockjs.bundle.js --color --config client/webpack.sockjs.config.js -p", - "lint": "eslint bin lib test examples client/{index,live,socket,sockjs,overlay,webpack.config}.js", - "beautify": "npm run lint -- --fix", - "test": "mocha --full-trace --check-leaks", - "posttest": "npm run -s lint", - "cover": "istanbul cover node_modules/mocha/bin/_mocha", - "travis": "npm run cover -- --report lcovonly && npm run lint" + "ws": "^3.1.0" } } diff --git a/test/.eslintrc b/test/.eslintrc new file mode 100644 index 0000000000..d5ba8f9d9c --- /dev/null +++ b/test/.eslintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "no-console": "off" + } +} diff --git a/test/Compress.test.js b/test/Compress.test.js index 15eb90e69b..3b6d4ad7a0 100644 --- a/test/Compress.test.js +++ b/test/Compress.test.js @@ -1,25 +1,25 @@ -"use strict"; +'use strict'; -const request = require("supertest"); -const helper = require("./helper"); -const config = require("./fixtures/simple-config/webpack.config"); +const request = require('supertest'); +const helper = require('./helper'); +const config = require('./fixtures/simple-config/webpack.config'); -describe("Compress", function() { - let server; - let req; +describe('Compress', () => { + let server; + let req; - before(function(done) { - server = helper.start(config, { - compress: true - }, done); - req = request(server.app); - }); + before((done) => { + server = helper.start(config, { + compress: true + }, done); + req = request(server.app); + }); - after(helper.close); + after(helper.close); - it("request to bundle file", function(done) { - req.get("/bundle.js") - .expect("Content-Encoding", "gzip") - .expect(200, done); - }); + it('request to bundle file', (done) => { + req.get('/bundle.js') + .expect('Content-Encoding', 'gzip') + .expect(200, done); + }); }); diff --git a/test/ContentBase.test.js b/test/ContentBase.test.js index b4273979a4..c2d105f50e 100644 --- a/test/ContentBase.test.js +++ b/test/ContentBase.test.js @@ -1,124 +1,124 @@ -"use strict"; - -const request = require("supertest"); -const path = require("path"); -const helper = require("./helper"); -const config = require("./fixtures/contentbase-config/webpack.config"); -require("mocha-sinon"); - -const contentBasePublic = path.join(__dirname, "fixtures/contentbase-config/public"); -const contentBaseOther = path.join(__dirname, "fixtures/contentbase-config/other"); - -describe("ContentBase", function() { - let server; - let req; - afterEach(helper.close); - - describe("to directory", function() { - before(function(done) { - server = helper.start(config, { - contentBase: contentBasePublic, - }, done); - req = request(server.app); - }); - - it("Request to index", function(done) { - req.get("/") - .expect(200, /Heyo/, done); - }); - - it("Request to other file", function(done) { - req.get("/other.html") - .expect(200, /Other html/, done); - }); - }); - - describe("to directories", function() { - before(function(done) { - server = helper.start(config, { - contentBase: [contentBasePublic, contentBaseOther], - }, done); - req = request(server.app); - }); - - it("Request to first directory", function(done) { - req.get("/") - .expect(200, /Heyo/, done); - }); - - it("Request to second directory", function(done) { - req.get("/foo.html") - .expect(200, /Foo!/, done); - }); - }); - - describe("to port", function() { - before(function(done) { - server = helper.start(config, { - contentBase: 9099999, - }, done); - req = request(server.app); - }); - - it("Request to page", function(done) { - req.get("/other.html") - .expect("Location", "//localhost:9099999/other.html") - .expect(302, done); - }); - }); - - describe("to external url", function() { - before(function(done) { - server = helper.start(config, { - contentBase: "http://example.com/", - }, done); - req = request(server.app); - }); - - it("Request to page", function(done) { - req.get("/foo.html") - // TODO: hmm, two slashes seems to be a bug? - .expect("Location", "http://example.com//foo.html") - .expect(302, done); - }); - - it("Request to page with search params", function(done) { - req.get("/foo.html?space=ship") - // TODO: hmm, two slashes seems to be a bug? - .expect("Location", "http://example.com//foo.html?space=ship") - .expect(302, done); - }); - }); - - describe("default to PWD", function() { - before(function(done) { - this.sinon.stub(process, "cwd"); - process.cwd.returns(contentBasePublic); - server = helper.start(config, {}, done); - req = request(server.app); - }); - - it("Request to page", function(done) { - req.get("/other.html") - .expect(200, done); - }); - }); - - describe("disable", function() { - before(function(done) { - // This is a somewhat weird test, but it is important that we mock - // the PWD here, and test if /other.html in our "fake" PWD really is not requested. - this.sinon.stub(process, "cwd"); - process.cwd.returns(contentBasePublic); - server = helper.start(config, { - contentBase: false - }, done); - req = request(server.app); - }); - - it("Request to page", function(done) { - req.get("/other.html") - .expect(404, done); - }); - }); +'use strict'; + +const path = require('path'); +const request = require('supertest'); +const helper = require('./helper'); +const config = require('./fixtures/contentbase-config/webpack.config'); +require('mocha-sinon'); + +const contentBasePublic = path.join(__dirname, 'fixtures/contentbase-config/public'); +const contentBaseOther = path.join(__dirname, 'fixtures/contentbase-config/other'); + +describe('ContentBase', () => { + let server; + let req; + afterEach(helper.close); + + describe('to directory', () => { + before((done) => { + server = helper.start(config, { + contentBase: contentBasePublic + }, done); + req = request(server.app); + }); + + it('Request to index', (done) => { + req.get('/') + .expect(200, /Heyo/, done); + }); + + it('Request to other file', (done) => { + req.get('/other.html') + .expect(200, /Other html/, done); + }); + }); + + describe('to directories', () => { + before((done) => { + server = helper.start(config, { + contentBase: [contentBasePublic, contentBaseOther] + }, done); + req = request(server.app); + }); + + it('Request to first directory', (done) => { + req.get('/') + .expect(200, /Heyo/, done); + }); + + it('Request to second directory', (done) => { + req.get('/foo.html') + .expect(200, /Foo!/, done); + }); + }); + + describe('to port', () => { + before((done) => { + server = helper.start(config, { + contentBase: 9099999 + }, done); + req = request(server.app); + }); + + it('Request to page', (done) => { + req.get('/other.html') + .expect('Location', '//localhost:9099999/other.html') + .expect(302, done); + }); + }); + + describe('to external url', () => { + before((done) => { + server = helper.start(config, { + contentBase: 'http://example.com/' + }, done); + req = request(server.app); + }); + + it('Request to page', (done) => { + req.get('/foo.html') + // TODO: hmm, two slashes seems to be a bug? + .expect('Location', 'http://example.com//foo.html') + .expect(302, done); + }); + + it('Request to page with search params', (done) => { + req.get('/foo.html?space=ship') + // TODO: hmm, two slashes seems to be a bug? + .expect('Location', 'http://example.com//foo.html?space=ship') + .expect(302, done); + }); + }); + + describe('default to PWD', () => { + before(function before(done) { + this.sinon.stub(process, 'cwd'); + process.cwd.returns(contentBasePublic); + server = helper.start(config, {}, done); + req = request(server.app); + }); + + it('Request to page', (done) => { + req.get('/other.html') + .expect(200, done); + }); + }); + + describe('disable', () => { + before(function before(done) { + // This is a somewhat weird test, but it is important that we mock + // the PWD here, and test if /other.html in our "fake" PWD really is not requested. + this.sinon.stub(process, 'cwd'); + process.cwd.returns(contentBasePublic); + server = helper.start(config, { + contentBase: false + }, done); + req = request(server.app); + }); + + it('Request to page', (done) => { + req.get('/other.html') + .expect(404, done); + }); + }); }); diff --git a/test/HistoryApiFallback.test.js b/test/HistoryApiFallback.test.js index 4d44807aee..c034131125 100644 --- a/test/HistoryApiFallback.test.js +++ b/test/HistoryApiFallback.test.js @@ -1,150 +1,157 @@ -"use strict"; - -const path = require("path"); -const request = require("supertest"); -const helper = require("./helper"); -const config = require("./fixtures/historyapifallback-config/webpack.config"); -const config2 = require("./fixtures/historyapifallback-2-config/webpack.config"); -const config3 = require("./fixtures/historyapifallback-3-config/webpack.config"); - -describe("HistoryApiFallback", function() { - let server; - let req; - - afterEach(helper.close); - - describe("as boolean", function() { - before(function(done) { - server = helper.start(config, { - historyApiFallback: true - }, done); - req = request(server.app); - }); - - it("request to directory", function(done) { - req.get("/foo") - .accept("html") - .expect(200, /Heyyy/, done); - }); - }); - - describe("as object", function() { - before(function(done) { - server = helper.start(config, { - historyApiFallback: { - index: "/bar.html" - } - }, done); - req = request(server.app); - }); - - it("request to directory", function(done) { - req.get("/foo") - .accept("html") - .expect(200, /Foobar/, done); - }); - }); - - describe("as object with contentBase", function() { - before(function(done) { - server = helper.start(config2, { - contentBase: path.join(__dirname, "fixtures/historyapifallback-2-config"), - historyApiFallback: { - index: "/bar.html" - } - }, done); - req = request(server.app); - }); - - it("historyApiFallback should take preference above directory index", function(done) { - req.get("/") - .accept("html") - .expect(200, /Foobar/, done); - }); - - it("request to directory", function(done) { - req.get("/foo") - .accept("html") - .expect(200, /Foobar/, done); - }); - - it("contentBase file should take preference above historyApiFallback", function(done) { - req.get("/random-file") - .accept("html") - .expect(200, /Random file/, done); - }); - }); - - describe("as object with contentBase set to false", function() { - before(function(done) { - server = helper.start(config3, { - contentBase: false, - historyApiFallback: { - index: "/bar.html" - } - }, done); - req = request(server.app); - }); - - it("historyApiFallback should work and ignore static content", function(done) { - req.get("/index.html") - .accept("html") - .expect(200, /In-memory file/, done); - }); - }); - - describe("as object with contentBase and rewrites", function() { - before(function(done) { - server = helper.start(config2, { - contentBase: path.join(__dirname, "fixtures/historyapifallback-2-config"), - historyApiFallback: { - rewrites: [ - { - from: /other/, - to: "/other.html" - }, - { - from: /.*/, - to: "/bar.html" - } - ] - } - }, done); - req = request(server.app); - }); - - it("historyApiFallback respect rewrites for index", function(done) { - req.get("/") - .accept("html") - .expect(200, /Foobar/, done); - }); - - it("historyApiFallback respect rewrites and shows index for unknown urls", function(done) { - req.get("/acme") - .accept("html") - .expect(200, /Foobar/, done); - }); - - it("historyApiFallback respect any other specified rewrites", function(done) { - req.get("/other") - .accept("html") - .expect(200, /Other file/, done); - }); - }); - - describe("in-memory files", function() { - before(function(done) { - server = helper.start(config3, { - contentBase: path.join(__dirname, "fixtures/historyapifallback-3-config"), - historyApiFallback: true - }, done); - req = request(server.app); - }); - - it("should take precedence over contentBase files", function(done) { - req.get("/foo") - .accept("html") - .expect(200, /In-memory file/, done); - }); - }); +'use strict'; + +const assert = require('assert'); +const path = require('path'); +const request = require('supertest'); +const helper = require('./helper'); +const config = require('./fixtures/historyapifallback-config/webpack.config'); +const config2 = require('./fixtures/historyapifallback-2-config/webpack.config'); +const config3 = require('./fixtures/historyapifallback-3-config/webpack.config'); + +describe('HistoryApiFallback', () => { + let server; + let req; + + afterEach(helper.close); + + describe('as boolean', () => { + before((done) => { + server = helper.start(config, { + historyApiFallback: true + }, done); + req = request(server.app); + }); + + it('request to directory', (done) => { + req.get('/foo') + .accept('html') + .expect(200, /Heyyy/, done); + }); + }); + + describe('as object', () => { + before((done) => { + server = helper.start(config, { + historyApiFallback: { + index: '/bar.html' + } + }, done); + req = request(server.app); + }); + + it('request to directory', (done) => { + req.get('/foo') + .accept('html') + .expect(200, /Foobar/, done); + }); + }); + + describe('as object with contentBase', () => { + before((done) => { + server = helper.start(config2, { + contentBase: path.join(__dirname, 'fixtures/historyapifallback-2-config'), + historyApiFallback: { + index: '/bar.html' + } + }, done); + req = request(server.app); + }); + + it('historyApiFallback should take preference above directory index', (done) => { + req.get('/') + .accept('html') + .expect(200, /Foobar/, done); + }); + + it('request to directory', (done) => { + req.get('/foo') + .accept('html') + .expect(200, /Foobar/, done); + }); + + it('contentBase file should take preference above historyApiFallback', (done) => { + req.get('/random-file') + .accept('html') + .end((err, res) => { // eslint-disable-line + if (err) { + done(err); + } + assert(res.body.toString(), 'Random file'); + done(); + }); + }); + }); + + describe('as object with contentBase set to false', () => { + before((done) => { + server = helper.start(config3, { + contentBase: false, + historyApiFallback: { + index: '/bar.html' + } + }, done); + req = request(server.app); + }); + + it('historyApiFallback should work and ignore static content', (done) => { + req.get('/index.html') + .accept('html') + .expect(200, /In-memory file/, done); + }); + }); + + describe('as object with contentBase and rewrites', () => { + before((done) => { + server = helper.start(config2, { + contentBase: path.join(__dirname, 'fixtures/historyapifallback-2-config'), + historyApiFallback: { + rewrites: [ + { + from: /other/, + to: '/other.html' + }, + { + from: /.*/, + to: '/bar.html' + } + ] + } + }, done); + req = request(server.app); + }); + + it('historyApiFallback respect rewrites for index', (done) => { + req.get('/') + .accept('html') + .expect(200, /Foobar/, done); + }); + + it('historyApiFallback respect rewrites and shows index for unknown urls', (done) => { + req.get('/acme') + .accept('html') + .expect(200, /Foobar/, done); + }); + + it('historyApiFallback respect any other specified rewrites', (done) => { + req.get('/other') + .accept('html') + .expect(200, /Other file/, done); + }); + }); + + describe('in-memory files', () => { + before((done) => { + server = helper.start(config3, { + contentBase: path.join(__dirname, 'fixtures/historyapifallback-3-config'), + historyApiFallback: true + }, done); + req = request(server.app); + }); + + it('should take precedence over contentBase files', (done) => { + req.get('/foo') + .accept('html') + .expect(200, /In-memory file/, done); + }); + }); }); diff --git a/test/Lazy.test.js b/test/Lazy.test.js index e8c031c768..afc54e5716 100644 --- a/test/Lazy.test.js +++ b/test/Lazy.test.js @@ -1,24 +1,24 @@ -"use strict"; +'use strict'; -const should = require("should"); -const helper = require("./helper"); -const config = require("./fixtures/simple-config/webpack.config"); +const should = require('should'); +const helper = require('./helper'); +const config = require('./fixtures/simple-config/webpack.config'); -describe("Lazy", function() { - afterEach(helper.close); +describe('Lazy', () => { + afterEach(helper.close); - it("without filename option it should throw an error", function() { - should.throws(function() { - helper.start(config, { - lazy: true - }); - }, /'filename' option must be set/); - }); + it('without filename option it should throw an error', () => { + should.throws(() => { + helper.start(config, { + lazy: true + }); + }, /'filename' option must be set/); + }); - it("with filename option should not throw an error", function(done) { - helper.start(config, { - lazy: true, - filename: "bundle.js" - }, done); - }); + it('with filename option should not throw an error', (done) => { + helper.start(config, { + lazy: true, + filename: 'bundle.js' + }, done); + }); }); diff --git a/test/Proxy.test.js b/test/Proxy.test.js index ba6c75936f..49c40f9e9a 100644 --- a/test/Proxy.test.js +++ b/test/Proxy.test.js @@ -1,234 +1,234 @@ -"use strict"; +'use strict'; -const request = require("supertest"); -const path = require("path"); -const express = require("express"); -const WebSocket = require("ws"); -const helper = require("./helper"); -const should = require("should"); -const config = require("./fixtures/proxy-config/webpack.config"); +const path = require('path'); +const request = require('supertest'); +const express = require('express'); +const WebSocket = require('ws'); +const should = require('should'); +const helper = require('./helper'); +const config = require('./fixtures/proxy-config/webpack.config'); const WebSocketServer = WebSocket.Server; -const contentBase = path.join(__dirname, "fixtures/proxy-config"); +const contentBase = path.join(__dirname, 'fixtures/proxy-config'); const proxyOption = { - "/proxy1": { - target: "http://localhost:9000", - }, - "/api/proxy2": { - target: "http://localhost:9001", - pathRewrite: { "^/api": "" }, - }, - "/foo": { - bypass: function(req) { - if(/\.html$/.test(req.path)) { - return "/index.html"; - } - }, - }, + '/proxy1': { + target: 'http://localhost:9000' + }, + '/api/proxy2': { + target: 'http://localhost:9001', + pathRewrite: { '^/api': '' } + }, + '/foo': { + bypass(req) { + if (/\.html$/.test(req.path)) { + return '/index.html'; + } + } + } }; const proxyOptionOfArray = [ - { context: "/proxy1", target: proxyOption["/proxy1"].target }, - function() { - return { - context: "/api/proxy2", - target: "http://localhost:9001", - pathRewrite: { "^/api": "" }, - }; - }, + { context: '/proxy1', target: proxyOption['/proxy1'].target }, + function proxy() { + return { + context: '/api/proxy2', + target: 'http://localhost:9001', + pathRewrite: { '^/api': '' } + }; + } ]; function startProxyServers() { - const listeners = []; - const proxy1 = express(); - const proxy2 = express(); - proxy1.get("/proxy1", function(req, res) { - res.send("from proxy1"); - }); - proxy1.get("/api", function(req, res) { - res.send("api response from proxy1"); - }); - proxy2.get("/proxy2", function(req, res) { - res.send("from proxy2"); - }); - listeners.push(proxy1.listen(9000)); - listeners.push(proxy2.listen(9001)); - // return a function to shutdown proxy servers - return function() { - listeners.forEach(function(listener) { - listener.close(); - }); - }; + const listeners = []; + const proxy1 = express(); + const proxy2 = express(); + proxy1.get('/proxy1', (req, res) => { + res.send('from proxy1'); + }); + proxy1.get('/api', (req, res) => { + res.send('api response from proxy1'); + }); + proxy2.get('/proxy2', (req, res) => { + res.send('from proxy2'); + }); + listeners.push(proxy1.listen(9000)); + listeners.push(proxy2.listen(9001)); + // return a function to shutdown proxy servers + return function proxy() { + listeners.forEach((listener) => { + listener.close(); + }); + }; } -describe("Proxy", function() { - context("proxy options is a object", function() { - let server; - let req; - let closeProxyServers; - - before(function(done) { - closeProxyServers = startProxyServers(); - server = helper.start(config, { - contentBase, - proxy: proxyOption, - }, done); - req = request(server.app); - }); - - after(function(done) { - helper.close(function() { - closeProxyServers(); - done(); - }); - }); - - describe("target", function() { - it("respects a proxy option when a request path is matched", function(done) { - req.get("/proxy1") - .expect(200, "from proxy1", done); - }); - }); - - describe("pathRewrite", function() { - it("respects a pathRewrite option", function(done) { - req.get("/api/proxy2") - .expect(200, "from proxy2", done); - }); - }); - - describe("bypass", function() { - it("can rewrite a request path", function(done) { - req.get("/foo/bar.html") - .expect(200, /Hello/, done); - }); - - it("can rewrite a request path regardless of the target defined a bypass option", function(done) { - req.get("/baz/hoge.html") - .expect(200, /Hello/, done); - }); - - it("should pass through a proxy when a bypass function returns null", function(done) { - req.get("/foo.js") - .expect(200, /Hey/, done); - }); - }); - }); - - context("proxy option is an array", function() { - let server; - let req; - let closeProxyServers; - - before(function(done) { - closeProxyServers = startProxyServers(); - server = helper.start(config, { - contentBase, - proxy: proxyOptionOfArray, - }, done); - req = request(server.app); - }); - - after(function(done) { - helper.close(function() { - closeProxyServers(); - done(); - }); - }); - - it("respects a proxy option", function(done) { - req.get("/proxy1") - .expect(200, "from proxy1", done); - }); - - it("respects a proxy option of function", function(done) { - req.get("/api/proxy2") - .expect(200, "from proxy2", done); - }); - }); - - context("sharing a proxy option", function() { - let server; - let req; - let listener; - const proxyTarget = { - target: "http://localhost:9000" - }; - - before(function(done) { - const proxy = express(); - proxy.get("*", function(req, res) { - res.send("from proxy"); - }); - listener = proxy.listen(9000); - server = helper.start(config, { - contentBase, - proxy: { - "/proxy1": proxyTarget, - "/proxy2": proxyTarget, - } - }, done); - req = request(server.app); - }); - - after(function(done) { - helper.close(function() { - listener.close(); - done(); - }); - }); - - it("respects proxy1 option", function(done) { - req.get("/proxy1").expect(200, "from proxy", done); - }); - - it("respects proxy2 option", function(done) { - req.get("/proxy2").expect(200, "from proxy", done); - }); - }); - - context("External websocket upgrade", function() { - let ws; - let wsServer; - let responseMessage; - - before(function(done) { - helper.start(config, { - contentBase, - proxy: [{ - context: "/", - target: "http://localhost:9003", - ws: true - }] - }, done); - - wsServer = new WebSocketServer({ port: 9003 }); - wsServer.on("connection", function connection(ws) { - ws.on("message", function incoming(message) { - ws.send(message); - }); - }); - }); - - beforeEach(function(done) { - ws = new WebSocket("ws://localhost:8080/proxy3/socket"); - ws.on("message", function(message) { - responseMessage = message; - done() - }); - ws.on("open", function open() { - ws.send("foo"); - }); - }) - - it("Should receive response", function() { - should(responseMessage).equal("foo"); - }); - - after(function(done) { - wsServer.close(); - helper.close(done); - }); - }); +describe('Proxy', () => { + context('proxy options is a object', () => { + let server; + let req; + let closeProxyServers; + + before((done) => { + closeProxyServers = startProxyServers(); + server = helper.start(config, { + contentBase, + proxy: proxyOption + }, done); + req = request(server.app); + }); + + after((done) => { + helper.close(() => { + closeProxyServers(); + done(); + }); + }); + + describe('target', () => { + it('respects a proxy option when a request path is matched', (done) => { + req.get('/proxy1') + .expect(200, 'from proxy1', done); + }); + }); + + describe('pathRewrite', () => { + it('respects a pathRewrite option', (done) => { + req.get('/api/proxy2') + .expect(200, 'from proxy2', done); + }); + }); + + describe('bypass', () => { + it('can rewrite a request path', (done) => { + req.get('/foo/bar.html') + .expect(200, /Hello/, done); + }); + + it('can rewrite a request path regardless of the target defined a bypass option', (done) => { + req.get('/baz/hoge.html') + .expect(200, /Hello/, done); + }); + + it('should pass through a proxy when a bypass function returns null', (done) => { + req.get('/foo.js') + .expect(200, /Hey/, done); + }); + }); + }); + + context('proxy option is an array', () => { + let server; + let req; + let closeProxyServers; + + before((done) => { + closeProxyServers = startProxyServers(); + server = helper.start(config, { + contentBase, + proxy: proxyOptionOfArray + }, done); + req = request(server.app); + }); + + after((done) => { + helper.close(() => { + closeProxyServers(); + done(); + }); + }); + + it('respects a proxy option', (done) => { + req.get('/proxy1') + .expect(200, 'from proxy1', done); + }); + + it('respects a proxy option of function', (done) => { + req.get('/api/proxy2') + .expect(200, 'from proxy2', done); + }); + }); + + context('sharing a proxy option', () => { + let server; + let req; + let listener; + const proxyTarget = { + target: 'http://localhost:9000' + }; + + before((done) => { + const proxy = express(); + proxy.get('*', (proxyReq, res) => { + res.send('from proxy'); + }); + listener = proxy.listen(9000); + server = helper.start(config, { + contentBase, + proxy: { + '/proxy1': proxyTarget, + '/proxy2': proxyTarget + } + }, done); + req = request(server.app); + }); + + after((done) => { + helper.close(() => { + listener.close(); + done(); + }); + }); + + it('respects proxy1 option', (done) => { + req.get('/proxy1').expect(200, 'from proxy', done); + }); + + it('respects proxy2 option', (done) => { + req.get('/proxy2').expect(200, 'from proxy', done); + }); + }); + + context('External websocket upgrade', () => { + let ws; + let wsServer; + let responseMessage; + + before((done) => { + helper.start(config, { + contentBase, + proxy: [{ + context: '/', + target: 'http://localhost:9003', + ws: true + }] + }, done); + + wsServer = new WebSocketServer({ port: 9003 }); + wsServer.on('connection', (server) => { + server.on('message', (message) => { + server.send(message); + }); + }); + }); + + beforeEach((done) => { + ws = new WebSocket('ws://localhost:8080/proxy3/socket'); + ws.on('message', (message) => { + responseMessage = message; + done(); + }); + ws.on('open', () => { + ws.send('foo'); + }); + }); + + it('Should receive response', () => { + should(responseMessage).equal('foo'); + }); + + after((done) => { + wsServer.close(); + helper.close(done); + }); + }); }); diff --git a/test/Routes.test.js b/test/Routes.test.js index a86b7ac538..c226415624 100644 --- a/test/Routes.test.js +++ b/test/Routes.test.js @@ -1,65 +1,65 @@ -"use strict"; +'use strict'; -const request = require("supertest"); -const fs = require("fs"); -const path = require("path"); -const helper = require("./helper"); -const config = require("./fixtures/simple-config/webpack.config"); +const fs = require('fs'); +const path = require('path'); +const request = require('supertest'); +const helper = require('./helper'); +const config = require('./fixtures/simple-config/webpack.config'); -const directoryIndex = fs.readFileSync(path.join(__dirname, "fixtures/directory-index.txt"), "utf-8"); -const magicHtml = fs.readFileSync(path.join(__dirname, "fixtures/magic-html.txt"), "utf-8"); +const directoryIndex = fs.readFileSync(path.join(__dirname, 'fixtures/directory-index.txt'), 'utf-8'); +const magicHtml = fs.readFileSync(path.join(__dirname, 'fixtures/magic-html.txt'), 'utf-8'); -describe("Routes", function() { - let server; - let req; +describe('Routes', () => { + let server; + let req; - before(function(done) { - server = helper.start(config, { - headers: { "X-Foo": "1" } - }, done); - req = request(server.app); - }); + before((done) => { + server = helper.start(config, { + headers: { 'X-Foo': '1' } + }, done); + req = request(server.app); + }); - after(helper.close); + after(helper.close); - it("GET request to inline bundle", function(done) { - req.get("/webpack-dev-server.js") - .expect("Content-Type", "application/javascript") - .expect(200, done); - }); + it('GET request to inline bundle', (done) => { + req.get('/webpack-dev-server.js') + .expect('Content-Type', 'application/javascript') + .expect(200, done); + }); - it("GET request to live bundle", function(done) { - req.get("/__webpack_dev_server__/live.bundle.js") - .expect("Content-Type", "application/javascript") - .expect(200, done); - }); + it('GET request to live bundle', (done) => { + req.get('/__webpack_dev_server__/live.bundle.js') + .expect('Content-Type', 'application/javascript') + .expect(200, done); + }); - it("GET request to sockjs bundle", function(done) { - req.get("/__webpack_dev_server__/sockjs.bundle.js") - .expect("Content-Type", "application/javascript") - .expect(200, done); - }); + it('GET request to sockjs bundle', (done) => { + req.get('/__webpack_dev_server__/sockjs.bundle.js') + .expect('Content-Type', 'application/javascript') + .expect(200, done); + }); - it("GET request to live html", function(done) { - req.get("/webpack-dev-server/") - .expect("Content-Type", "text/html") - .expect(200, /__webpack_dev_server__/, done); - }); + it('GET request to live html', (done) => { + req.get('/webpack-dev-server/') + .expect('Content-Type', 'text/html') + .expect(200, /__webpack_dev_server__/, done); + }); - it("GET request to directory index", function(done) { - req.get("/webpack-dev-server") - .expect("Content-Type", "text/html") - .expect(200, directoryIndex.trim(), done); - }); + it('GET request to directory index', (done) => { + req.get('/webpack-dev-server') + .expect('Content-Type', 'text/html') + .expect(200, directoryIndex.trim(), done); + }); - it("GET request to magic html", function(done) { - req.get("/bundle") - .expect(200, magicHtml.trim(), done); - }); + it('GET request to magic html', (done) => { + req.get('/bundle') + .expect(200, magicHtml.trim(), done); + }); - it("GET request with headers", function(done) { - req.get("/bundle") - .expect("X-Foo", "1") - .expect(200, done); - }); + it('GET request with headers', (done) => { + req.get('/bundle') + .expect('X-Foo', '1') + .expect(200, done); + }); }); diff --git a/test/Validation.test.js b/test/Validation.test.js index 6ccc0809fc..cfe7f368fd 100644 --- a/test/Validation.test.js +++ b/test/Validation.test.js @@ -1,185 +1,185 @@ -"use strict"; +'use strict'; -const config = require("./fixtures/simple-config/webpack.config"); -const OptionsValidationError = require("../lib/OptionsValidationError"); -const Server = require("../lib/Server"); -const webpack = require("webpack"); +const webpack = require('webpack'); +const OptionsValidationError = require('../lib/OptionsValidationError'); +const Server = require('../lib/Server'); +const config = require('./fixtures/simple-config/webpack.config'); -describe("Validation", function() { - let compiler; - before(function() { - compiler = webpack(config); - }); - const testCases = [{ - name: "invalid `hot` configuration", - config: { hot: "asdf" }, - message: [ - " - configuration.hot should be a boolean." - ] - }, { - name: "invalid `public` configuration", - config: { public: 1 }, - message: [ - " - configuration.public should be a string." - ] - }, { - name: "invalid `allowedHosts` configuration", - config: { allowedHosts: 1 }, - message: [ - " - configuration.allowedHosts should be an array:", - " [string]", - " Specifies which hosts are allowed to access the dev server." - ] - }, { - name: "invalid `contentBase` configuration", - config: { contentBase: [0] }, - message: [ - " - configuration.contentBase should be one of these:", - " [string] | false | number | string", - " A directory to serve files non-webpack files from.", - " Details:", - " * configuration.contentBase[0] should be a string.", - " * configuration.contentBase should be false", - " * configuration.contentBase should be a number.", - " * configuration.contentBase should be a string." - ] - }, { - name: "non-existing key configuration", - config: { asdf: true }, - message: [ - " - configuration has an unknown property 'asdf'. These properties are valid:", - " object { hot?, hotOnly?, lazy?, bonjour?, host?, allowedHosts?, filename?, publicPath?, port?, socket?, " + - "watchOptions?, headers?, clientLogLevel?, overlay?, key?, cert?, ca?, pfx?, pfxPassphrase?, " + - "inline?, disableHostCheck?, public?, https?, contentBase?, watchContentBase?, open?, useLocalIp?, openPage?, features?, " + - "compress?, proxy?, historyApiFallback?, staticOptions?, setup?, stats?, reporter?, " + - "noInfo?, quiet?, serverSideRender?, index?, log?, warn? }" - ] - }]; - testCases.forEach(function(testCase) { - it(`should fail validation for ${testCase.name}`, function() { - try { - new Server(compiler, testCase.config); - } catch(e) { - if(!(e instanceof OptionsValidationError)) - throw e; - e.message.should.startWith("Invalid configuration object."); - e.message.split("\n").slice(1).should.be.eql(testCase.message); - return; - } - throw new Error("Validation didn't fail"); - }) - }); +describe('Validation', () => { + let compiler; + before(() => { + compiler = webpack(config); + }); + const testCases = [{ + name: 'invalid `hot` configuration', + config: { hot: 'asdf' }, + message: [ + ' - configuration.hot should be a boolean.' + ] + }, { + name: 'invalid `public` configuration', + config: { public: 1 }, + message: [ + ' - configuration.public should be a string.' + ] + }, { + name: 'invalid `allowedHosts` configuration', + config: { allowedHosts: 1 }, + message: [ + ' - configuration.allowedHosts should be an array:', + ' [string]', + ' Specifies which hosts are allowed to access the dev server.' + ] + }, { + name: 'invalid `contentBase` configuration', + config: { contentBase: [0] }, + message: [ + ' - configuration.contentBase should be one of these:', + ' [string] | false | number | string', + ' A directory to serve files non-webpack files from.', + ' Details:', + ' * configuration.contentBase[0] should be a string.', + ' * configuration.contentBase should be false', + ' * configuration.contentBase should be a number.', + ' * configuration.contentBase should be a string.' + ] + }, { + name: 'non-existing key configuration', + config: { asdf: true }, + message: [ + " - configuration has an unknown property 'asdf'. These properties are valid:", + ' object { hot?, hotOnly?, lazy?, bonjour?, host?, allowedHosts?, filename?, publicPath?, port?, socket?, ' + + 'watchOptions?, headers?, clientLogLevel?, overlay?, key?, cert?, ca?, pfx?, pfxPassphrase?, ' + + 'inline?, disableHostCheck?, public?, https?, contentBase?, watchContentBase?, open?, useLocalIp?, openPage?, features?, ' + + 'compress?, proxy?, historyApiFallback?, staticOptions?, setup?, stats?, reporter?, ' + + 'noInfo?, quiet?, serverSideRender?, index?, log?, warn? }' + ] + }]; + testCases.forEach((testCase) => { + it(`should fail validation for ${testCase.name}`, () => { + try { + // eslint-disable-next-line no-new + new Server(compiler, testCase.config); + } catch (e) { + if (!(e instanceof OptionsValidationError)) { throw e; } + e.message.should.startWith('Invalid configuration object.'); + e.message.split('\n').slice(1).should.be.eql(testCase.message); + return; + } + throw new Error("Validation didn't fail"); + }); + }); - describe("checkHost", function() { - it("should always allow any host if options.disableHostCheck is set", function() { - const options = { - public: "test.host:80", - disableHostCheck: true - }; - const headers = { - host: "bad.host" - }; - const server = new Server(compiler, options); - if(!server.checkHost(headers)) { - throw new Error("Validation didn't fail"); - } - }); + describe('checkHost', () => { + it('should always allow any host if options.disableHostCheck is set', () => { + const options = { + public: 'test.host:80', + disableHostCheck: true + }; + const headers = { + host: 'bad.host' + }; + const server = new Server(compiler, options); + if (!server.checkHost(headers)) { + throw new Error("Validation didn't fail"); + } + }); - it("should allow any valid options.public when host is localhost", function() { - const options = { - public: "test.host:80" - }; - const headers = { - host: "localhost" - }; - const server = new Server(compiler, options); - if(!server.checkHost(headers)) { - throw new Error("Validation didn't fail"); - } - }); + it('should allow any valid options.public when host is localhost', () => { + const options = { + public: 'test.host:80' + }; + const headers = { + host: 'localhost' + }; + const server = new Server(compiler, options); + if (!server.checkHost(headers)) { + throw new Error("Validation didn't fail"); + } + }); - it("should allow any valid options.public when host is 127.0.0.1", function() { - const options = { - public: "test.host:80" - }; - const headers = { - host: "127.0.0.1" - }; - const server = new Server(compiler, options); - if(!server.checkHost(headers)) { - throw new Error("Validation didn't fail"); - } - }); + it('should allow any valid options.public when host is 127.0.0.1', () => { + const options = { + public: 'test.host:80' + }; + const headers = { + host: '127.0.0.1' + }; + const server = new Server(compiler, options); + if (!server.checkHost(headers)) { + throw new Error("Validation didn't fail"); + } + }); - it("should allow access for every requests using an IP", function() { - const options = {}; - const testHosts = [ - "192.168.1.123", - "192.168.1.2:8080", - "[::1]", - "[::1]:8080", - "[ad42::1de2:54c2:c2fa:1234]", - "[ad42::1de2:54c2:c2fa:1234]:8080" - ]; + it('should allow access for every requests using an IP', () => { + const options = {}; + const testHosts = [ + '192.168.1.123', + '192.168.1.2:8080', + '[::1]', + '[::1]:8080', + '[ad42::1de2:54c2:c2fa:1234]', + '[ad42::1de2:54c2:c2fa:1234]:8080' + ]; - const server = new Server(compiler, options); - testHosts.forEach(function(testHost) { - const headers = { host: testHost }; - if(!server.checkHost(headers)) { - throw new Error("Validation didn't pass"); - } - }); - }); + const server = new Server(compiler, options); + testHosts.forEach((testHost) => { + const headers = { host: testHost }; + if (!server.checkHost(headers)) { + throw new Error("Validation didn't pass"); + } + }); + }); - it("should not allow hostnames that don't match options.public", function() { - const options = { - public: "test.host:80", - }; - const headers = { - host: "test.hostname:80" - }; - const server = new Server(compiler, options); - if(server.checkHost(headers)) { - throw new Error("Validation didn't fail"); - } - }); + it("should not allow hostnames that don't match options.public", () => { + const options = { + public: 'test.host:80' + }; + const headers = { + host: 'test.hostname:80' + }; + const server = new Server(compiler, options); + if (server.checkHost(headers)) { + throw new Error("Validation didn't fail"); + } + }); - describe("allowedHosts", function() { - it("should allow hosts in allowedHosts", function() { - const testHosts = [ - "test.host", - "test2.host", - "test3.host" - ]; - const options = { allowedHosts: testHosts }; - const server = new Server(compiler, options); + describe('allowedHosts', () => { + it('should allow hosts in allowedHosts', () => { + const testHosts = [ + 'test.host', + 'test2.host', + 'test3.host' + ]; + const options = { allowedHosts: testHosts }; + const server = new Server(compiler, options); - testHosts.forEach(function(testHost) { - const headers = { host: testHost }; - if(!server.checkHost(headers)) { - throw new Error("Validation didn't fail"); - } - }); - }); - it("should allow hosts that pass a wildcard in allowedHosts", function() { - const options = { allowedHosts: [".example.com"] }; - const server = new Server(compiler, options); - const testHosts = [ - "www.example.com", - "subdomain.example.com", - "example.com", - "subsubcomain.subdomain.example.com", - "example.com:80", - "subdomain.example.com:80" - ]; + testHosts.forEach((testHost) => { + const headers = { host: testHost }; + if (!server.checkHost(headers)) { + throw new Error("Validation didn't fail"); + } + }); + }); + it('should allow hosts that pass a wildcard in allowedHosts', () => { + const options = { allowedHosts: ['.example.com'] }; + const server = new Server(compiler, options); + const testHosts = [ + 'www.example.com', + 'subdomain.example.com', + 'example.com', + 'subsubcomain.subdomain.example.com', + 'example.com:80', + 'subdomain.example.com:80' + ]; - testHosts.forEach(function(testHost) { - const headers = { host: testHost }; - if(!server.checkHost(headers)) { - throw new Error("Validation didn't fail"); - } - }); - }); - }); - }) + testHosts.forEach((testHost) => { + const headers = { host: testHost }; + if (!server.checkHost(headers)) { + throw new Error("Validation didn't fail"); + } + }); + }); + }); + }); }); diff --git a/test/fixtures/contentbase-config/foo.js b/test/fixtures/contentbase-config/foo.js index 01a3a05c17..d1168ef6e3 100644 --- a/test/fixtures/contentbase-config/foo.js +++ b/test/fixtures/contentbase-config/foo.js @@ -1,3 +1,5 @@ -require("./index.html"); +'use strict'; -console.log("Hey."); +require('./index.html'); // eslint-disable-line + +console.log('Hey.'); diff --git a/test/fixtures/contentbase-config/webpack.config.js b/test/fixtures/contentbase-config/webpack.config.js index 8ec01d9183..ac0c9e881f 100644 --- a/test/fixtures/contentbase-config/webpack.config.js +++ b/test/fixtures/contentbase-config/webpack.config.js @@ -1,8 +1,10 @@ +'use strict'; + module.exports = { - context: __dirname, - entry: "./foo.js", - output: { - filename: "bundle.js", - publicPath: "/" - } + context: __dirname, + entry: './foo.js', + output: { + filename: 'bundle.js', + publicPath: '/' + } }; diff --git a/test/fixtures/historyapifallback-2-config/foo.js b/test/fixtures/historyapifallback-2-config/foo.js index 3dea82f991..5048f70fc8 100644 --- a/test/fixtures/historyapifallback-2-config/foo.js +++ b/test/fixtures/historyapifallback-2-config/foo.js @@ -1 +1,3 @@ -console.log("Hey."); +'use strict'; + +console.log("Hey."); // eslint-disable-line diff --git a/test/fixtures/historyapifallback-2-config/webpack.config.js b/test/fixtures/historyapifallback-2-config/webpack.config.js index 42acc2c118..8adf2fae5f 100644 --- a/test/fixtures/historyapifallback-2-config/webpack.config.js +++ b/test/fixtures/historyapifallback-2-config/webpack.config.js @@ -1,8 +1,10 @@ +'use strict'; + module.exports = { - context: __dirname, - entry: "./foo.js", - output: { - filename: "bundle.js", - path: "/" - } + context: __dirname, + entry: './foo.js', + output: { + filename: 'bundle.js', + path: '/' + } }; diff --git a/test/fixtures/historyapifallback-3-config/foo.js b/test/fixtures/historyapifallback-3-config/foo.js index 5f6793297e..de1b13b15b 100644 --- a/test/fixtures/historyapifallback-3-config/foo.js +++ b/test/fixtures/historyapifallback-3-config/foo.js @@ -1,2 +1,5 @@ -require("./bar.html"); -console.log("Hey."); +'use strict'; + +require('./bar.html'); + +console.log("Hey."); // eslint-disable-line diff --git a/test/fixtures/historyapifallback-3-config/webpack.config.js b/test/fixtures/historyapifallback-3-config/webpack.config.js index f179e4cf17..97e9e0efc4 100644 --- a/test/fixtures/historyapifallback-3-config/webpack.config.js +++ b/test/fixtures/historyapifallback-3-config/webpack.config.js @@ -1,17 +1,19 @@ +'use strict'; + module.exports = { - context: __dirname, - entry: "./foo.js", - output: { - filename: "bundle.js", - path: "/" - }, - module: { - loaders: [ - { - test: /\.html$/, - loader: "file-loader", - query: { name: "index.html" } - } - ] - } + context: __dirname, + entry: './foo.js', + output: { + filename: 'bundle.js', + path: '/' + }, + module: { + loaders: [ + { + test: /\.html$/, + loader: 'file-loader', + query: { name: 'index.html' } + } + ] + } }; diff --git a/test/fixtures/historyapifallback-config/foo.js b/test/fixtures/historyapifallback-config/foo.js index 63be220b2f..0da543f0a3 100644 --- a/test/fixtures/historyapifallback-config/foo.js +++ b/test/fixtures/historyapifallback-config/foo.js @@ -1,3 +1,6 @@ -require("./index.html"); -require("./bar.html"); -console.log("Hey."); +'use strict'; + +require('./index.html'); +require('./bar.html'); + +console.log("Hey."); // eslint-disable-line diff --git a/test/fixtures/historyapifallback-config/webpack.config.js b/test/fixtures/historyapifallback-config/webpack.config.js index 11aeca1d97..6c8b350f36 100644 --- a/test/fixtures/historyapifallback-config/webpack.config.js +++ b/test/fixtures/historyapifallback-config/webpack.config.js @@ -1,17 +1,19 @@ +'use strict'; + module.exports = { - context: __dirname, - entry: "./foo.js", - output: { - filename: "bundle.js", - path: "/" - }, - module: { - loaders: [ - { - test: /\.html$/, - loader: "file-loader", - query: { name: "[name].[ext]" } - } - ] - } + context: __dirname, + entry: './foo.js', + output: { + filename: 'bundle.js', + path: '/' + }, + module: { + loaders: [ + { + test: /\.html$/, + loader: 'file-loader', + query: { name: '[name].[ext]' } + } + ] + } }; diff --git a/test/fixtures/proxy-config/foo.js b/test/fixtures/proxy-config/foo.js index 3dea82f991..eab26534f3 100644 --- a/test/fixtures/proxy-config/foo.js +++ b/test/fixtures/proxy-config/foo.js @@ -1 +1,3 @@ -console.log("Hey."); +'use strict'; + +console.log('Hey.'); diff --git a/test/fixtures/proxy-config/webpack.config.js b/test/fixtures/proxy-config/webpack.config.js index 42acc2c118..8adf2fae5f 100644 --- a/test/fixtures/proxy-config/webpack.config.js +++ b/test/fixtures/proxy-config/webpack.config.js @@ -1,8 +1,10 @@ +'use strict'; + module.exports = { - context: __dirname, - entry: "./foo.js", - output: { - filename: "bundle.js", - path: "/" - } + context: __dirname, + entry: './foo.js', + output: { + filename: 'bundle.js', + path: '/' + } }; diff --git a/test/fixtures/simple-config/foo.js b/test/fixtures/simple-config/foo.js index 3dea82f991..eab26534f3 100644 --- a/test/fixtures/simple-config/foo.js +++ b/test/fixtures/simple-config/foo.js @@ -1 +1,3 @@ -console.log("Hey."); +'use strict'; + +console.log('Hey.'); diff --git a/test/fixtures/simple-config/webpack.config.js b/test/fixtures/simple-config/webpack.config.js index 42acc2c118..8adf2fae5f 100644 --- a/test/fixtures/simple-config/webpack.config.js +++ b/test/fixtures/simple-config/webpack.config.js @@ -1,8 +1,10 @@ +'use strict'; + module.exports = { - context: __dirname, - entry: "./foo.js", - output: { - filename: "bundle.js", - path: "/" - } + context: __dirname, + entry: './foo.js', + output: { + filename: 'bundle.js', + path: '/' + } }; diff --git a/test/helper.js b/test/helper.js index c25a4e1dfa..4a4dda7c7d 100644 --- a/test/helper.js +++ b/test/helper.js @@ -1,33 +1,34 @@ -"use strict"; +'use strict'; -const Server = require("../lib/Server"); -const webpack = require("webpack"); +const webpack = require('webpack'); +const Server = require('../lib/Server'); let server; module.exports = { - start: function(config, options, done) { - if(options.quiet === undefined) { - options.quiet = true; - } - const compiler = webpack(config); - server = new Server(compiler, options); + start(config, options, done) { + // eslint-disable-next-line no-undefined + if (options.quiet === undefined) { + options.quiet = true; + } + const compiler = webpack(config); + server = new Server(compiler, options); - server.listen(8080, "localhost", function(err) { - if(err) return done(err); - done(); - }); + server.listen(8080, 'localhost', (err) => { + if (err) return done(err); + done(); + }); - return server; - }, - close: function(done) { - if(server) { - server.close(function() { - server = null; - done(); - }); - } else { - done(); - } - } + return server; + }, + close(done) { + if (server) { + server.close(() => { + server = null; + done(); + }); + } else { + done(); + } + } };