From 092a6c6f6cf180cab8de17854d893b3d830254fa Mon Sep 17 00:00:00 2001 From: Stanislav Sysoev Date: Mon, 24 Apr 2017 09:44:26 +1000 Subject: [PATCH] fix(dev-server): Fix port detecting --- .eslintrc | 7 +- package.json | 2 +- src/analytics.js | 21 +-- src/build-command.js | 6 +- src/dev-server-command.js | 53 +++++--- src/eslint-config.js | 156 +++++++++++----------- src/utils/detect-port.js | 25 ++++ src/utils/error-helpers.js | 44 ++++--- src/utils/get-template-path.js | 15 +-- src/utils/messages.js | 231 ++++++++++++++++++++------------- src/webpack-dev-server.js | 102 ++++++++++----- 11 files changed, 399 insertions(+), 263 deletions(-) create mode 100644 src/utils/detect-port.js diff --git a/.eslintrc b/.eslintrc index d299a8f..af9c5a0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,3 +1,4 @@ +// prettier-ignore { "parser": "babel-eslint", "env": { @@ -157,7 +158,7 @@ "func-names": [0], "func-style": [0], "indent": [2, 2], - "jsx-quotes": [2, "prefer-single"], + "jsx-quotes": [2, "prefer-double"], "key-spacing": [2, { "beforeColon": false, "afterColon": true @@ -200,10 +201,10 @@ }], "one-var-declaration-per-line": [0], "operator-assignment": [0], - "operator-linebreak": [2, "before"], + "operator-linebreak": [0], "padded-blocks": [0], "quote-props": [2, "as-needed"], - "quotes": [2, "single"], + "quotes": [0], "require-jsdoc": [0], "semi": [2, "always"], "semi-spacing": [2, { diff --git a/package.json b/package.json index aa51f28..d1bbb78 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ "chalk": "1.1.3", "connect-history-api-fallback": "1.3.0", "css-loader": "0.28.0", - "detect-port": "1.1.1", "eslint": "3.19.0", "eslint-loader": "1.7.1", "eslint-plugin-react": "6.10.3", @@ -100,6 +99,7 @@ "nsp": "2.6.3", "pmm": "1.3.1", "pre-commit": "1.2.2", + "prettier": "^1.2.2", "rimraf": "2.6.1" }, "config": { diff --git a/src/analytics.js b/src/analytics.js index 71597a7..a41d194 100644 --- a/src/analytics.js +++ b/src/analytics.js @@ -1,15 +1,16 @@ /* @flow */ -import querystring from 'querystring'; -import Insight from 'insight'; -import pkg from '../package.json'; +import querystring from "querystring"; +import Insight from "insight"; +import pkg from "../package.json"; -const trackingCode = 'UA-88006586-1'; -const trackingProvider = 'google'; +const trackingCode = "UA-88006586-1"; +const trackingProvider = "google"; const insight = new Insight({ trackingCode, trackingProvider, pkg }); export function askPermission(cb: Function) { - if (insight.optOut === undefined) { // eslint-disable-line + // eslint-disable-next-line + if (insight.optOut === undefined) { return insight.askPermission(null, cb); } cb(); @@ -28,9 +29,11 @@ export function track(path: string[], input: string[], flags: CLIFlags) { if (!input[0]) { return flags.version - ? setImmediate(() => insight.track('aik', 'version')) - : setImmediate(() => insight.track('aik', 'help')); + ? setImmediate(() => insight.track("aik", "version")) + : setImmediate(() => insight.track("aik", "help")); } - setImmediate(() => insight.track('aik', ...path, '?' + querystring.stringify(filteredFlags))); + setImmediate(() => + insight.track("aik", ...path, "?" + querystring.stringify(filteredFlags)) + ); } diff --git a/src/build-command.js b/src/build-command.js index 1ce8d85..6fe9508 100644 --- a/src/build-command.js +++ b/src/build-command.js @@ -1,12 +1,12 @@ /* @flow */ -import runWebpackBuilder from './webpack-build'; -import createParams from './utils/params'; +import runWebpackBuilder from "./webpack-build"; +import createParams from "./utils/params"; /** * Aik build command */ export default function aikBuild(input: string[], flags: CLIFlags): Promise<*> { const [filename] = input; - const params = createParams(filename, flags, '', true); + const params = createParams(filename, flags, "", true); return runWebpackBuilder(filename, flags, params); } diff --git a/src/dev-server-command.js b/src/dev-server-command.js index 882835d..4c0e2cf 100644 --- a/src/dev-server-command.js +++ b/src/dev-server-command.js @@ -1,29 +1,35 @@ /* @flow */ -import { execSync } from 'child_process'; -import fs from 'fs'; -import readline from 'readline'; -import opn from 'opn'; -import { outputFile } from 'fs-extra'; -import resolveModule from 'resolve'; -import createWebpackDevServer from './webpack-dev-server'; -import createNgrokTunnel from './ngrok'; -import createParams from './utils/params'; +import { execSync } from "child_process"; +import fs from "fs"; +import readline from "readline"; +import opn from "opn"; +import { outputFile } from "fs-extra"; +import resolveModule from "resolve"; +import createWebpackDevServer from "./webpack-dev-server"; +import createNgrokTunnel from "./ngrok"; +import createParams from "./utils/params"; import { - devServerFileDoesNotExistMsg, devServerInvalidBuildMsg, fileDoesNotExistMsg, - devServerReactRequired, devServerInstallingModuleMsg, devServerSkipInstallingModuleMsg -} from './utils/messages'; + devServerFileDoesNotExistMsg, + devServerInvalidBuildMsg, + fileDoesNotExistMsg, + devServerReactRequired, + devServerInstallingModuleMsg, + devServerSkipInstallingModuleMsg +} from "./utils/messages"; -export function requestCreatingAnEntryPoint(filename: string): Promise { +export function requestCreatingAnEntryPoint( + filename: string +): Promise { return new Promise((resolve, reject) => { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); - rl.question('Create it? (Y/n): ', (answer: string) => { + rl.question("Create it? (Y/n): ", (answer: string) => { rl.close(); - if (!answer || answer === 'Y' || answer === 'y') { + if (!answer || answer === "Y" || answer === "y") { return resolve(true); } @@ -36,7 +42,7 @@ export function requestCreatingAnEntryPoint(filename: string): Promise export function createFile(filename: string): Promise<*> { return new Promise((resolve, reject) => { - outputFile(filename, '', err => { + outputFile(filename, "", err => { if (err) { return reject(err); } @@ -62,7 +68,10 @@ function installModule(moduleName: string) { /** * Aik dev server command */ -export default async function aikDevServer(input: string[], flags: CLIFlags): Promise<*> { +export default async function aikDevServer( + input: string[], + flags: CLIFlags +): Promise<*> { const [filename] = input; try { @@ -70,7 +79,9 @@ export default async function aikDevServer(input: string[], flags: CLIFlags): Pr } catch (error) { devServerFileDoesNotExistMsg(filename); - const shouldCreateAnEntryPoint = await requestCreatingAnEntryPoint(filename); + const shouldCreateAnEntryPoint = await requestCreatingAnEntryPoint( + filename + ); if (shouldCreateAnEntryPoint) { await createFile(filename); } @@ -80,11 +91,11 @@ export default async function aikDevServer(input: string[], flags: CLIFlags): Pr if (flags.react) { devServerReactRequired(); - await installModule('react'); - await installModule('react-dom'); + await installModule("react"); + await installModule("react-dom"); } - const ngrokUrl: NgrokUrl = flags.ngrok && await createNgrokTunnel(flags); + const ngrokUrl: NgrokUrl = flags.ngrok && (await createNgrokTunnel(flags)); const params: AikParams = createParams(filename, flags, ngrokUrl, false); await createWebpackDevServer(filename, flags, params); diff --git a/src/eslint-config.js b/src/eslint-config.js index ed4e262..e272873 100644 --- a/src/eslint-config.js +++ b/src/eslint-config.js @@ -10,7 +10,7 @@ module.exports = { parserOptions: { ecmaVersion: 2017, - sourceType: 'module', + sourceType: "module", impliedStrict: true, ecmaFeatures: { jsx: true, @@ -18,95 +18,95 @@ module.exports = { } }, - plugins: ['react'], + plugins: ["react"], rules: { // http://eslint.org/docs/rules/ // Possible errors - 'no-cond-assign': 'warn', - 'no-constant-condition': 'warn', - 'no-dupe-args': 'warn', - 'no-dupe-keys': 'warn', - 'no-duplicate-case': 'warn', - 'no-empty-character-class': 'warn', - 'no-empty': 'warn', - 'no-ex-assign': 'warn', - 'no-extra-semi': 'warn', - 'no-func-assign': 'warn', - 'no-invalid-regexp': 'warn', - 'no-irregular-whitespace': 'warn', - 'no-negated-in-lhs': 'warn', - 'no-obj-calls': 'warn', - 'no-unexpected-multiline': 'warn', - 'no-unreachable': 'warn', - 'use-isnan': 'warn', - 'valid-typeof': 'warn', - 'require-await': 'warn', + "no-cond-assign": "warn", + "no-constant-condition": "warn", + "no-dupe-args": "warn", + "no-dupe-keys": "warn", + "no-duplicate-case": "warn", + "no-empty-character-class": "warn", + "no-empty": "warn", + "no-ex-assign": "warn", + "no-extra-semi": "warn", + "no-func-assign": "warn", + "no-invalid-regexp": "warn", + "no-irregular-whitespace": "warn", + "no-negated-in-lhs": "warn", + "no-obj-calls": "warn", + "no-unexpected-multiline": "warn", + "no-unreachable": "warn", + "use-isnan": "warn", + "valid-typeof": "warn", + "require-await": "warn", // Best practices - 'array-callback-return': 'warn', - 'guard-for-in': 'warn', - 'no-div-regex': 'warn', - 'no-empty-pattern': 'warn', - 'no-eq-null': 'warn', - 'no-eval': 'warn', - 'no-extend-native': 'warn', - 'no-extra-bind': 'warn', - 'no-extra-label': 'warn', - 'no-fallthrough': 'warn', - 'no-global-assign': 'warn', - 'no-implied-eval': 'warn', - 'no-invalid-this': 'warn', - 'no-labels': 'warn', - 'no-lone-blocks': 'warn', - 'no-new-func': 'warn', - 'no-self-assign': 'warn', - 'no-self-compare': 'warn', - 'no-unmodified-loop-condition': 'warn', - 'no-unused-expressions': 'warn', - 'no-unused-labels': 'warn', - 'no-useless-call': 'warn', - 'no-useless-escape': 'warn', - 'no-useless-concat': 'warn', - 'no-useless-return': 'warn', - 'no-void': 'warn', - 'no-with': 'warn', + "array-callback-return": "warn", + "guard-for-in": "warn", + "no-div-regex": "warn", + "no-empty-pattern": "warn", + "no-eq-null": "warn", + "no-eval": "warn", + "no-extend-native": "warn", + "no-extra-bind": "warn", + "no-extra-label": "warn", + "no-fallthrough": "warn", + "no-global-assign": "warn", + "no-implied-eval": "warn", + "no-invalid-this": "warn", + "no-labels": "warn", + "no-lone-blocks": "warn", + "no-new-func": "warn", + "no-self-assign": "warn", + "no-self-compare": "warn", + "no-unmodified-loop-condition": "warn", + "no-unused-expressions": "warn", + "no-unused-labels": "warn", + "no-useless-call": "warn", + "no-useless-escape": "warn", + "no-useless-concat": "warn", + "no-useless-return": "warn", + "no-void": "warn", + "no-with": "warn", // Variables - 'no-delete-var': 'warn', - 'no-label-var': 'warn', - 'no-undef-init': 'warn', - 'no-undef': 'warn', - 'no-unused-vars': 'warn', + "no-delete-var": "warn", + "no-label-var": "warn", + "no-undef-init": "warn", + "no-undef": "warn", + "no-unused-vars": "warn", // ECMAScript 6 - 'constructor-super': 'warn', - 'no-class-assign': 'warn', - 'no-const-assign': 'warn', - 'no-dupe-class-members': 'warn', - 'no-duplicate-imports': 'warn', - 'no-new-symbol': 'warn', - 'no-this-before-super': 'warn', - 'no-useless-computed-key': 'warn', - 'no-useless-constructor': 'warn', - 'no-useless-rename': 'warn', - 'require-yield': 'warn', + "constructor-super": "warn", + "no-class-assign": "warn", + "no-const-assign": "warn", + "no-dupe-class-members": "warn", + "no-duplicate-imports": "warn", + "no-new-symbol": "warn", + "no-this-before-super": "warn", + "no-useless-computed-key": "warn", + "no-useless-constructor": "warn", + "no-useless-rename": "warn", + "require-yield": "warn", // https://github.com/yannickcr/eslint-plugin-react - 'react/jsx-key': 'warn', - 'react/jsx-no-comment-textnodes': 'warn', - 'react/jsx-no-duplicate-props': ['warn', { ignoreCase: true }], - 'react/jsx-no-undef': 'warn', - 'react/jsx-pascal-case': ['warn', { allowAllCaps: true }], - 'react/jsx-uses-react': 'warn', - 'react/jsx-uses-vars': 'warn', - 'react/no-danger-with-children': 'warn', - 'react/no-deprecated': 'warn', - 'react/no-direct-mutation-state': 'warn', - 'react/no-is-mounted': 'warn', - 'react/react-in-jsx-scope': 'warn', - 'react/require-render-return': 'warn', - 'react/style-prop-object': 'warn' + "react/jsx-key": "warn", + "react/jsx-no-comment-textnodes": "warn", + "react/jsx-no-duplicate-props": ["warn", { ignoreCase: true }], + "react/jsx-no-undef": "warn", + "react/jsx-pascal-case": ["warn", { allowAllCaps: true }], + "react/jsx-uses-react": "warn", + "react/jsx-uses-vars": "warn", + "react/no-danger-with-children": "warn", + "react/no-deprecated": "warn", + "react/no-direct-mutation-state": "warn", + "react/no-is-mounted": "warn", + "react/react-in-jsx-scope": "warn", + "react/require-render-return": "warn", + "react/style-prop-object": "warn" } }; diff --git a/src/utils/detect-port.js b/src/utils/detect-port.js new file mode 100644 index 0000000..926e4b8 --- /dev/null +++ b/src/utils/detect-port.js @@ -0,0 +1,25 @@ +/* @flow */ +import net from "net"; + +export function tryPort(port: number, host: string): Promise { + return new Promise(resolve => { + const server = net.createServer(); + server.on("error", () => { + server.close(); + resolve(tryPort(++port, host)); + }); + + server.listen({ port, host }, () => { + port = server.address().port; + server.close(); + resolve(port); + }); + }); +} + +export default function detectPort( + port: number, + host: string = "localhost" +): Promise { + return tryPort(port, host); +} diff --git a/src/utils/error-helpers.js b/src/utils/error-helpers.js index 23c4228..27a509c 100644 --- a/src/utils/error-helpers.js +++ b/src/utils/error-helpers.js @@ -1,34 +1,38 @@ /* @flow */ -const SYNTAX_ERROR_LABEL = 'SyntaxError:'; -const SYNTAX_ERROR_LABEL_HUMAN_FRIENDLY = 'Syntax Error:'; +const SYNTAX_ERROR_LABEL = "SyntaxError:"; +const SYNTAX_ERROR_LABEL_HUMAN_FRIENDLY = "Syntax Error:"; /** * Checks whether error is syntax error. */ export function isLikelyASyntaxError(message: string): boolean { - return message.indexOf(SYNTAX_ERROR_LABEL) !== -1 - || message.indexOf(SYNTAX_ERROR_LABEL_HUMAN_FRIENDLY) !== -1; + return ( + message.indexOf(SYNTAX_ERROR_LABEL) !== -1 || + message.indexOf(SYNTAX_ERROR_LABEL_HUMAN_FRIENDLY) !== -1 + ); } /** * Makes some common errors shorter. */ export function formatMessage(message: string): string { - return message - // Babel syntax error - .replace( - 'Module build failed: SyntaxError:', - SYNTAX_ERROR_LABEL_HUMAN_FRIENDLY - ) - // Webpack file not found error - .replace( - /Module not found: Error: Cannot resolve 'file' or 'directory'/, - 'Module not found:' - ) - // Internal stacks are generally useless so we strip them - .replace(/^\s*at\s((?!webpack:).)*:\d+:\d+[\s\)]*(\n|$)/gm, '') // at ... ...:x:y - // Webpack loader names obscure CSS filenames - .replace('./~/css-loader!./~/postcss-loader!', '') - .replace(/\s@ multi .+/, ''); + return ( + message + // Babel syntax error + .replace( + "Module build failed: SyntaxError:", + SYNTAX_ERROR_LABEL_HUMAN_FRIENDLY + ) + // Webpack file not found error + .replace( + /Module not found: Error: Cannot resolve 'file' or 'directory'/, + "Module not found:" + ) + // Internal stacks are generally useless so we strip them + .replace(/^\s*at\s((?!webpack:).)*:\d+:\d+[\s\)]*(\n|$)/gm, "") // at ... ...:x:y + // Webpack loader names obscure CSS filenames + .replace("./~/css-loader!./~/postcss-loader!", "") + .replace(/\s@ multi .+/, "") + ); } diff --git a/src/utils/get-template-path.js b/src/utils/get-template-path.js index 2095469..817b13d 100644 --- a/src/utils/get-template-path.js +++ b/src/utils/get-template-path.js @@ -1,8 +1,8 @@ /* @flow */ -import path from 'path'; -import fs from 'fs'; -import resolveToCwd from './resolve-to-cwd'; +import path from "path"; +import fs from "fs"; +import resolveToCwd from "./resolve-to-cwd"; /** * Checks whether templatePath is a file. @@ -21,10 +21,9 @@ export function isTemplateExists(templatePath: string): boolean { * * For example: index.js -> index.html */ -export default function getTemplatePath(filename: string = ''): string { - const basename = path.basename(filename, '.js'); +export default function getTemplatePath(filename: string = ""): string { + const basename = path.basename(filename, ".js"); const dirname = path.dirname(filename); - const templatePath = resolveToCwd(path.join(dirname, basename + '.html')); - return isTemplateExists(templatePath) ? templatePath : ''; + const templatePath = resolveToCwd(path.join(dirname, basename + ".html")); + return isTemplateExists(templatePath) ? templatePath : ""; } - diff --git a/src/utils/messages.js b/src/utils/messages.js index e05445a..48c37f6 100644 --- a/src/utils/messages.js +++ b/src/utils/messages.js @@ -1,21 +1,24 @@ /* @flow */ -import chalk from 'chalk'; -import { isLikelyASyntaxError, formatMessage } from './error-helpers'; +import chalk from "chalk"; +import { isLikelyASyntaxError, formatMessage } from "./error-helpers"; /** * Moves current line to the most top of console. */ export function clearConsole(sep?: boolean) { - sep && process.stdout.write(chalk.dim('----------------------------------\n')); - process.stdout.write(process.platform === 'win32' ? '\x1Bc' : '\x1B[2J\x1B[3J\x1B[H'); + sep && + process.stdout.write(chalk.dim("----------------------------------\n")); + process.stdout.write( + process.platform === "win32" ? "\x1Bc" : "\x1B[2J\x1B[3J\x1B[H" + ); } /** * Actually prints message to the console */ export function print(msg: string[]) { - return console.log(msg.join('\n')); // eslint-disable-line + return console.log(msg.join("\n")); // eslint-disable-line } /** @@ -26,19 +29,19 @@ export function print(msg: string[]) { */ export function doneBadge() { - return chalk.bgGreen.black(' DONE '); + return chalk.bgGreen.black(" DONE "); } export function warningBadge() { - return chalk.bgYellow.black(' WARNING '); + return chalk.bgYellow.black(" WARNING "); } export function waitBadge() { - return chalk.bgBlue.black(' WAIT '); + return chalk.bgBlue.black(" WAIT "); } export function errorBadge() { - return chalk.bgRed.black(' ERROR '); + return chalk.bgRed.black(" ERROR "); } /** @@ -49,17 +52,21 @@ export function errorBadge() { export function eslintExtraWarningMsg() { return print([ - 'You may use special comments to disable some warnings.', - 'Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.', - 'Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.' + "You may use special comments to disable some warnings.", + "Use " + + chalk.yellow("// eslint-disable-next-line") + + " to ignore the next line.", + "Use " + + chalk.yellow("/* eslint-disable */") + + " to ignore all warnings in a file." ]); } export function fileDoesNotExistMsg(filename: string) { clearConsole(); return print([ - errorBadge() + chalk.red(' File doesn\'t exist.'), - '', + errorBadge() + chalk.red(" File doesn't exist."), + "", `You are trying to use ${chalk.yellow('"' + filename + '"')} as entry point, but this file doesn't exist.`, `Please, choose existing file or create ${chalk.yellow('"' + filename + '"')} manualy.` ]); @@ -71,33 +78,39 @@ export function fileDoesNotExistMsg(filename: string) { * */ -export function devServerBanner(filename: string, flags: CLIFlags, params: AikParams): string[] { - const msg:string[] = [ - '', - chalk.magenta('Entry point: ') + filename - ]; +export function devServerBanner( + filename: string, + flags: CLIFlags, + params: AikParams +): string[] { + const msg: string[] = ["", chalk.magenta("Entry point: ") + filename]; if (params.template.short) { - msg.push(chalk.magenta('Custom template: ') + params.template.short); + msg.push(chalk.magenta("Custom template: ") + params.template.short); } - msg.push(chalk.magenta('Server: ') + chalk.cyan(`http://${flags.host}:${flags.port}`)); + msg.push( + chalk.magenta("Server: ") + + chalk.cyan(`http://${flags.host}:${flags.port}`) + ); if (flags.oldPort) { - msg.push(chalk.magenta('Port changed: ') - + `${chalk.bgRed.black(' ' + flags.oldPort + ' ')} -> ${chalk.bgGreen.black(' ' + flags.port + ' ')}`); + msg.push( + chalk.magenta("Port changed: ") + + `${chalk.bgRed.black(" " + flags.oldPort + " ")} -> ${chalk.bgGreen.black(" " + flags.port + " ")}` + ); } if (params.ngrok) { - msg.push(chalk.magenta('Ngrok: ') + chalk.cyan(params.ngrok)); + msg.push(chalk.magenta("Ngrok: ") + chalk.cyan(params.ngrok)); } if (flags.cssmodules) { - msg.push(chalk.magenta('CSS Modules: ') + chalk.yellow('enabled')); + msg.push(chalk.magenta("CSS Modules: ") + chalk.yellow("enabled")); } if (flags.react) { - msg.push(chalk.magenta('React Hot Loader: ') + chalk.yellow('enabled')); + msg.push(chalk.magenta("React Hot Loader: ") + chalk.yellow("enabled")); } return msg; @@ -105,24 +118,44 @@ export function devServerBanner(filename: string, flags: CLIFlags, params: AikPa export function devServerInvalidBuildMsg() { clearConsole(); - return print([waitBadge() + ' ' + chalk.blue('Compiling...')]); + return print([waitBadge() + " " + chalk.blue("Compiling...")]); } -export function devServerCompiledSuccessfullyMsg(filename: string, flags: CLIFlags, params: AikParams, buildDuration: number) { // eslint-disable-line +export function devServerCompiledSuccessfullyMsg( + filename: string, + flags: CLIFlags, + params: AikParams, + buildDuration: number +) { + // eslint-disable-line const msg = devServerBanner(filename, flags, params); - msg.unshift(doneBadge() + ' ' + chalk.green(`Compiled successfully in ${buildDuration}ms!`)); + msg.unshift( + doneBadge() + + " " + + chalk.green(`Compiled successfully in ${buildDuration}ms!`) + ); return print(msg); } export function devServerFailedToCompileMsg() { clearConsole(true); - return print([errorBadge() + ' ' + chalk.red('Failed to compile.')]); + return print([errorBadge() + " " + chalk.red("Failed to compile.")]); } -export function devServerCompiledWithWarningsMsg(filename: string, flags: CLIFlags, params: AikParams, buildDuration: number) { // eslint-disable-line +export function devServerCompiledWithWarningsMsg( + filename: string, + flags: CLIFlags, + params: AikParams, + buildDuration: number +) { + // eslint-disable-line const msg = devServerBanner(filename, flags, params); - msg.unshift(warningBadge() + ' ' + chalk.yellow(`Compiled with warnings in ${buildDuration}ms.`)); - msg.push('', chalk.dim('---------')); + msg.unshift( + warningBadge() + + " " + + chalk.yellow(`Compiled with warnings in ${buildDuration}ms.`) + ); + msg.push("", chalk.dim("---------")); return print(msg); } @@ -130,44 +163,46 @@ export function devServerFileDoesNotExistMsg(filename: string) { clearConsole(); return print([ warningBadge() + ` File "${chalk.yellow(filename)}" doesn\'t exist.`, - '' + "" ]); } export function devServerRestartMsg(module: string) { clearConsole(true); return print([ - warningBadge() + ' ' + chalk.yellow(`New npm module was added (${module}).`), - '', - 'Restarting webpack-dev-server is requried.', - '', - 'Please be patient and wait until restart completes, otherwise some changes might not be tracked.', - '' + warningBadge() + + " " + + chalk.yellow(`New npm module was added (${module}).`), + "", + "Restarting webpack-dev-server is requried.", + "", + "Please be patient and wait until restart completes, otherwise some changes might not be tracked.", + "" ]); } export function devServerModuleDoesntExists(module: string, filename: string) { clearConsole(true); return print([ - errorBadge() + ' ' + chalk.red(`Module '${module}' doesn't exists.`), - '', + errorBadge() + " " + chalk.red(`Module '${module}' doesn't exists.`), + "", `Error in ${filename}`, - '', - `Webpack tried to resolve module ${chalk.bgYellow.black(' ' + module + ' ')} which doesn't exist.`, - '', - `It's likely caused by ${chalk.yellow('typo')} in the module name.`, - '' + "", + `Webpack tried to resolve module ${chalk.bgYellow.black(" " + module + " ")} which doesn't exist.`, + "", + `It's likely caused by ${chalk.yellow("typo")} in the module name.`, + "" ]); } export function devServerReactRequired() { return print([ - warningBadge() + ' ' + chalk.yellow('"react" required.'), - '', + warningBadge() + " " + chalk.yellow('"react" required.'), + "", 'In order to make "react-hot-loader" work, "react" and "react-dom" are required.', - '', - chalk.blue('Installing required modules...'), - '' + "", + chalk.blue("Installing required modules..."), + "" ]); } @@ -176,88 +211,108 @@ export function devServerInstallingModuleMsg(moduleName: string) { } export function devServerSkipInstallingModuleMsg(moduleName: string) { - return print([`Module "${chalk.yellow(moduleName)}" has already been installed ${chalk.dim('[skipping].')}`]); + return print([ + `Module "${chalk.yellow(moduleName)}" has already been installed ${chalk.dim("[skipping].")}` + ]); } - /** * * Build Messages * */ -export function builderBanner(filename: string, flags: CLIFlags, params: AikParams) { +export function builderBanner( + filename: string, + flags: CLIFlags, + params: AikParams +) { clearConsole(); const msg = [ - waitBadge() + ' ' + chalk.blue('Building...'), - '', - chalk.magenta('Entry point: ') + filename + waitBadge() + " " + chalk.blue("Building..."), + "", + chalk.magenta("Entry point: ") + filename ]; - if (params.template.short) { - msg.push(chalk.magenta('Custom template: ') + params.template.short); + msg.push(chalk.magenta("Custom template: ") + params.template.short); } const base = flags.base; - if (base && typeof base === 'string') { - msg.push(chalk.magenta('Base path: ') + base); + if (base && typeof base === "string") { + msg.push(chalk.magenta("Base path: ") + base); } - msg.push(chalk.magenta('CSS Modules: ') + (flags.cssmodules ? chalk.green('enabled') : 'disabled')); + msg.push( + chalk.magenta("CSS Modules: ") + + (flags.cssmodules ? chalk.green("enabled") : "disabled") + ); return print(msg); } export function builderRemovingDistMsg(distPath: string) { - return print([ - '', - chalk.yellow('Removing folder: ') + distPath - ]); + return print(["", chalk.yellow("Removing folder: ") + distPath]); } export function builderRunningBuildMsg() { - return print([ - chalk.yellow('Running webpack production build...') - ]); + return print([chalk.yellow("Running webpack production build...")]); } export function builderErrorMsg(err: { message: string } | string) { clearConsole(true); - let msg: string = typeof err.message === 'string' ? err.message : err.toString(); + let msg: string = typeof err.message === "string" + ? err.message + : err.toString(); if (isLikelyASyntaxError(msg)) { msg = formatMessage(msg); } return print([ - errorBadge() + ' ' + chalk.red('Failed to create a production build. Reason:'), + errorBadge() + + " " + + chalk.red("Failed to create a production build. Reason:"), msg ]); } -export function builderSuccessMsg(distShortName: string, buildStats: BuildStats) { +export function builderSuccessMsg( + distShortName: string, + buildStats: BuildStats +) { clearConsole(true); const assets = buildStats.assets; - const longestNameSize = assets.reduce((acc, item) => item.name.length > acc ? item.name.length : acc, 0) + 1; - const padStringPlaceholder = ' '.repeat(longestNameSize); - const padString = (placeholder: string, str: string) => (str + placeholder).substr(0, placeholder.length); + const longestNameSize = + assets.reduce( + (acc, item) => (item.name.length > acc ? item.name.length : acc), + 0 + ) + 1; + const padStringPlaceholder = " ".repeat(longestNameSize); + const padString = (placeholder: string, str: string) => + (str + placeholder).substr(0, placeholder.length); return print([ doneBadge() + ` in ${buildStats.buildDuration}ms`, - '', - chalk.green(`Successfully generated a bundle in the ${chalk.cyan('"' + distShortName + '"')} folder!`), - chalk.green('The bundle is optimized and ready to be deployed to production.'), - '', - chalk.bgMagenta.black(' ASSETS '), - '', - buildStats.assets.map(asset => { - return [ - `${chalk.magenta(padString(padStringPlaceholder, asset.name + ':'))}`, - `${asset.size.toFixed(2)}kb, ${asset.sizeGz.toFixed(2)}kb gzip` - ].join(' '); - }).join('\n') + "", + chalk.green( + `Successfully generated a bundle in the ${chalk.cyan('"' + distShortName + '"')} folder!` + ), + chalk.green( + "The bundle is optimized and ready to be deployed to production." + ), + "", + chalk.bgMagenta.black(" ASSETS "), + "", + buildStats.assets + .map(asset => { + return [ + `${chalk.magenta(padString(padStringPlaceholder, asset.name + ":"))}`, + `${asset.size.toFixed(2)}kb, ${asset.sizeGz.toFixed(2)}kb gzip` + ].join(" "); + }) + .join("\n") ]); } diff --git a/src/webpack-dev-server.js b/src/webpack-dev-server.js index 6f003db..3969737 100644 --- a/src/webpack-dev-server.js +++ b/src/webpack-dev-server.js @@ -1,13 +1,13 @@ /* @flow */ -import detectPort from 'detect-port'; -import historyApiFallback from 'connect-history-api-fallback'; -import resolveModule from 'resolve'; -import webpack from 'webpack'; -import WebpackDevServer from 'webpack-dev-server'; -import webpackConfigBuilder from './webpack/config-builder'; -import testUtils from './utils/test-utils'; -import { isLikelyASyntaxError, formatMessage } from './utils/error-helpers'; +import historyApiFallback from "connect-history-api-fallback"; +import resolveModule from "resolve"; +import webpack from "webpack"; +import WebpackDevServer from "webpack-dev-server"; +import webpackConfigBuilder from "./webpack/config-builder"; +import detectPort from "./utils/detect-port"; +import testUtils from "./utils/test-utils"; +import { isLikelyASyntaxError, formatMessage } from "./utils/error-helpers"; import { clearConsole, eslintExtraWarningMsg, @@ -17,12 +17,20 @@ import { devServerCompiledWithWarningsMsg, devServerRestartMsg, devServerModuleDoesntExists -} from './utils/messages'; +} from "./utils/messages"; /** * On done handler for webpack compiler. */ -export function onDone(filename: string, flags: CLIFlags, params: AikParams, compiler: any, invalidate: Function, stats: Object) { // eslint-disable-line +export function onDone( + filename: string, + flags: CLIFlags, + params: AikParams, + compiler: any, + invalidate: Function, + stats: Object +) { + // eslint-disable-line const hasErrors = stats.hasErrors(); const hasWarnings = stats.hasWarnings(); const buildDuration: number = stats.endTime - stats.startTime; @@ -36,11 +44,17 @@ export function onDone(filename: string, flags: CLIFlags, params: AikParams, com } const json = stats.toJson({}, true); - const formattedWarnings = json.warnings.map(message => 'Warning in ' + formatMessage(message)); - let formattedErrors = json.errors.map(message => 'Error in ' + formatMessage(message)); + const formattedWarnings = json.warnings.map( + message => "Warning in " + formatMessage(message) + ); + let formattedErrors = json.errors.map( + message => "Error in " + formatMessage(message) + ); if (hasErrors) { - if (formattedErrors.filter(err => err.match('Cannot resolve module')).length) { + if ( + formattedErrors.filter(err => err.match("Cannot resolve module")).length + ) { invalidate(formattedErrors); testUtils(); return; @@ -55,16 +69,15 @@ export function onDone(filename: string, flags: CLIFlags, params: AikParams, com formattedErrors = formattedErrors.filter(isLikelyASyntaxError); } - // If errors exist, ignore warnings. - formattedErrors.forEach(message => console.log('\n', message)); // eslint-disable-line + formattedErrors.forEach(message => console.log("\n", message)); // eslint-disable-line testUtils(); return; } if (hasWarnings) { devServerCompiledWithWarningsMsg(filename, flags, params, buildDuration); - formattedWarnings.forEach(message => console.log('\n', message)); // eslint-disable-line + formattedWarnings.forEach(message => console.log("\n", message)); // eslint-disable-line eslintExtraWarningMsg(); } @@ -74,18 +87,32 @@ export function onDone(filename: string, flags: CLIFlags, params: AikParams, com /** * Creates webpack compiler. */ -export function createWebpackCompiler(filename: string, flags: CLIFlags, params: AikParams, config: Object, invalidate: Function) { // eslint-disable-line +export function createWebpackCompiler( + filename: string, + flags: CLIFlags, + params: AikParams, + config: Object, + invalidate: Function +) { + // eslint-disable-line const compiler = webpack(config); - compiler.plugin('invalid', devServerInvalidBuildMsg); - compiler.plugin('done', onDone.bind(null, filename, flags, params, compiler, invalidate)); + compiler.plugin("invalid", devServerInvalidBuildMsg); + compiler.plugin( + "done", + onDone.bind(null, filename, flags, params, compiler, invalidate) + ); return compiler; } /** * Creates webpack dev server. */ -export default function createWebpackDevServer(filename: string, flags: CLIFlags, params: AikParams): Promise { - return detectPort(flags.port).then(port => { +export default function createWebpackDevServer( + filename: string, + flags: CLIFlags, + params: AikParams +): Promise { + return detectPort(parseInt(flags.port, 10), flags.host).then(port => { if (port !== flags.port) { flags.oldPort = flags.port; flags.port = port; @@ -95,25 +122,34 @@ export default function createWebpackDevServer(filename: string, flags: CLIFlags const invalidate = (errors: string[]) => { if (!server) return; - const error = errors[0] || ''; + const error = errors[0] || ""; const fileWithError = (error.match(/Error in (.+)\n/) || [])[1]; - let moduleName = (error.match(/Module not found: Error: Cannot resolve module '(.+)'/) || [])[1]; + let moduleName = (error.match( + /Module not found: Error: Cannot resolve module '(.+)'/ + ) || [])[1]; if (!moduleName) return; - moduleName = moduleName.replace(/'/gmi, ''); + moduleName = moduleName.replace(/'/gim, ""); try { resolveModule.sync(moduleName, { basedir: process.cwd() }); devServerRestartMsg(moduleName); server.close(); createWebpackDevServer(filename, flags, params); - } catch (e) { // eslint-disable-line + } catch (e) { + // eslint-disable-line devServerModuleDoesntExists(moduleName, fileWithError); } }; const config = webpackConfigBuilder(filename, flags, params); - const compiler = createWebpackCompiler(filename, flags, params, config, invalidate); + const compiler = createWebpackCompiler( + filename, + flags, + params, + config, + invalidate + ); server = new WebpackDevServer(compiler, { // Enable gzip compression of generated files. @@ -121,7 +157,7 @@ export default function createWebpackDevServer(filename: string, flags: CLIFlags // Silence WebpackDevServer's own logs since they're generally not useful. // It will still show compile warnings and errors with this setting. - clientLogLevel: 'none', + clientLogLevel: "none", historyApiFallback: true, hot: true, overlay: { @@ -132,13 +168,15 @@ export default function createWebpackDevServer(filename: string, flags: CLIFlags }); return new Promise((resolve, reject) => { - server.listen(flags.port, flags.host, (err) => { + server.listen(flags.port, flags.host, err => { if (err) return reject(err); - server.use(historyApiFallback({ - disableDotRule: true, - htmlAcceptHeaders: ['text/html'] - })); + server.use( + historyApiFallback({ + disableDotRule: true, + htmlAcceptHeaders: ["text/html"] + }) + ); resolve(server); });