├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── _sandbox ├── .eslintrc ├── .gitignore ├── index.js ├── package.json ├── watch.js └── webpack.config.js ├── appveyor.yml ├── index.js ├── package.json ├── src ├── core │ ├── extractWebpackError.js │ ├── formatErrors.js │ └── transformErrors.js ├── formatters │ ├── defaultError.js │ ├── eslintError.js │ └── moduleNotFound.js ├── friendly-errors-plugin.js ├── reporters │ ├── base.js │ ├── consola.js │ └── index.js ├── transformers │ ├── babelSyntax.js │ ├── esLintError.js │ └── moduleNotFound.js └── utils │ ├── index.js │ └── log.js ├── test ├── fixtures │ ├── .gitignore │ ├── babel-syntax │ │ ├── index.js │ │ └── webpack.config.js │ ├── eslint-warnings │ │ ├── .eslintrc │ │ ├── index.js │ │ ├── module.js │ │ └── webpack.config.js │ ├── module-errors │ │ ├── index.js │ │ └── webpack.config.js │ ├── multi-compiler-module-errors │ │ ├── index.js │ │ ├── index2.js │ │ └── webpack.config.js │ ├── multi-compiler-success │ │ ├── index.js │ │ ├── index2.js │ │ └── webpack.config.js │ └── success │ │ ├── index.js │ │ └── webpack.config.js ├── integration.spec.js ├── unit │ ├── formatErrors.spec.js │ ├── formatters │ │ ├── defaultError.spec.js │ │ └── moduleNotFound.spec.js │ ├── plugin │ │ └── friendlyErrors.spec.js │ ├── transformers │ │ ├── babelSyntax.spec.js │ │ └── moduleNotFound.spec.js │ └── utils │ │ └── utils.spec.js └── utils │ └── index.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "only": ["test/"], 3 | "plugins": ["transform-async-to-generator"] 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test/fixtures -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "standard", 3 | "env": { 4 | "jest": true 5 | } 6 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (http://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # Typescript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | 64 | 65 | 66 | # End of https://www.gitignore.io/api/node -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 16 4 | branches: 5 | only: 6 | - master 7 | 8 | cache: 9 | yarn: true 10 | directories: 11 | - node_modules 12 | 13 | #after_success: npm run coverage 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | # [2.6.0](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.5.2...v2.6.0) (2023-11-23) 7 | 8 | 9 | ### Bug Fixes 10 | 11 | * bump consola dep to 3.x to align with other nuxt 2 packages ([#16](https://github.com/nuxt/friendly-errors-webpack-plugin/issues/16)) ([078d629](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/078d629)) 12 | * **doc:** update package name in readme sample ([#12](https://github.com/nuxt/friendly-errors-webpack-plugin/issues/12)) ([6cf5fbe](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/6cf5fbe)) 13 | 14 | 15 | 16 | 17 | ## [2.5.2](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.5.1...v2.5.2) (2021-10-14) 18 | 19 | 20 | 21 | 22 | ## [2.5.1](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.5.0...v2.5.1) (2021-04-09) 23 | 24 | 25 | 26 | 27 | # [2.5.0](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.0.0-beta.0...v2.5.0) (2019-05-20) 28 | 29 | 30 | 31 | 32 | # [2.4.0](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.3.2...v2.4.0) (2018-12-07) 33 | 34 | 35 | ### Features 36 | 37 | * display children errors and warnings ([812ffe4](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/812ffe4)) 38 | 39 | 40 | 41 | 42 | ## [2.3.2](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.3.1...v2.3.2) (2018-11-23) 43 | 44 | 45 | 46 | 47 | ## [2.3.1](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.3.0...v2.3.1) (2018-11-23) 48 | 49 | 50 | ### Bug Fixes 51 | 52 | * empty message in module not found error ([ac2c233](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/ac2c233)) 53 | 54 | 55 | 56 | 57 | # [2.3.0](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.2.0...v2.3.0) (2018-11-03) 58 | 59 | 60 | 61 | 62 | # [2.2.0](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.1.0...v2.2.0) (2018-11-03) 63 | 64 | 65 | ### Bug Fixes 66 | 67 | * default value for options.logLevel ([1ac5b70](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/1ac5b70)) 68 | * ignore WAIT info with consola ([93964fa](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/93964fa)) 69 | * logLevel can be lower case value ([8db42c9](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/8db42c9)) 70 | * logLevel can be lower case value ([27d166b](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/27d166b)) 71 | * message in windows ([ed4c84b](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/ed4c84b)) 72 | * remove duplicate SyntaxError ([8953760](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/8953760)) 73 | 74 | 75 | ### Features 76 | 77 | * add consola reporter ([60e447c](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/60e447c)) 78 | * add reporter options ([7547af3](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/7547af3)) 79 | * add reporters ([ccddab5](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/ccddab5)) 80 | * refactor reporter to standard logger ([00c979d](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/00c979d)) 81 | * skip branch build in pr ([25ca868](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/25ca868)) 82 | * **consola:** use tagged reporter ([e414613](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/e414613)) 83 | * **sandbox:** test with consola.wrapConsole ([8d98ae0](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/8d98ae0)) 84 | 85 | 86 | 87 | 88 | # [2.1.0](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.0.2...v2.1.0) (2018-10-26) 89 | 90 | 91 | ### Features 92 | 93 | * sync with friendly-errors-webpack-plugin ([75c5c3b](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/75c5c3b)) 94 | 95 | 96 | 97 | 98 | ## [2.0.2](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.0.1...v2.0.2) (2018-03-20) 99 | 100 | 101 | ### Features 102 | 103 | * clear console also according to log level ([655203d](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/655203d)) 104 | 105 | 106 | 107 | 108 | ## [2.0.1](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.0.0...v2.0.1) (2018-03-20) 109 | 110 | 111 | ### Bug Fixes 112 | 113 | * npm badge ([9bb4d3a](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/9bb4d3a)) 114 | 115 | 116 | ### Features 117 | 118 | * add log level ([ddcf45a](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/ddcf45a)) 119 | 120 | 121 | 122 | 123 | # [2.0.0](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v1.6.1...v2.0.0) (2018-03-16) 124 | 125 | 126 | ### Bug Fixes 127 | 128 | * comma-dangle error ([2360434](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/2360434)) 129 | * lint script ([c3e88c9](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/c3e88c9)) 130 | * move comma-dangle into rules ([9cd26c5](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/9cd26c5)) 131 | * test failures ([34ac16b](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/34ac16b)) 132 | * windows eslint ([01f8059](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/01f8059)) 133 | 134 | 135 | ### Features 136 | 137 | * add release script ([bcd208f](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/bcd208f)) 138 | * upgrade to webpack4 ([458efe9](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/458efe9)) 139 | * upgrade to webpack4 ([8927021](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/8927021)) 140 | 141 | 142 | 143 | 144 | # [2.4.0](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.3.2...v2.4.0) (2018-12-07) 145 | 146 | 147 | ### Features 148 | 149 | * display children errors and warnings ([812ffe4](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/812ffe4)) 150 | 151 | 152 | 153 | 154 | ## [2.3.2](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.3.1...v2.3.2) (2018-11-23) 155 | 156 | 157 | 158 | 159 | ## [2.3.1](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.3.0...v2.3.1) (2018-11-23) 160 | 161 | 162 | ### Bug Fixes 163 | 164 | * empty message in module not found error ([ac2c233](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/ac2c233)) 165 | 166 | 167 | 168 | 169 | # [2.3.0](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.2.0...v2.3.0) (2018-11-03) 170 | 171 | 172 | ### Bug Fixes 173 | 174 | * default value for options.logLevel ([1ac5b70](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/1ac5b70)) 175 | * ignore WAIT info with consola ([93964fa](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/93964fa)) 176 | 177 | 178 | ### Features 179 | 180 | * **consola:** use tagged reporter ([e414613](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/e414613)) 181 | * **sandbox:** test with consola.wrapConsole ([8d98ae0](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/8d98ae0)) 182 | 183 | 184 | 185 | 186 | # [2.2.0](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.1.0...v2.2.0) (2018-11-03) 187 | 188 | 189 | ### Bug Fixes 190 | 191 | * logLevel can be lower case value ([8db42c9](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/8db42c9)) 192 | * logLevel can be lower case value ([27d166b](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/27d166b)) 193 | * message in windows ([ed4c84b](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/ed4c84b)) 194 | * remove duplicate SyntaxError ([8953760](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/8953760)) 195 | 196 | 197 | ### Features 198 | 199 | * add consola reporter ([60e447c](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/60e447c)) 200 | * add reporter options ([7547af3](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/7547af3)) 201 | * add reporters ([ccddab5](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/ccddab5)) 202 | * refactor reporter to standard logger ([00c979d](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/00c979d)) 203 | * skip branch build in pr ([25ca868](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/25ca868)) 204 | 205 | 206 | 207 | 208 | # [2.1.0](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.0.2...v2.1.0) (2018-10-26) 209 | 210 | 211 | ### Features 212 | 213 | * sync with friendly-errors-webpack-plugin ([75c5c3b](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/75c5c3b)) 214 | 215 | 216 | 217 | 218 | # 1.7.0 (2018-04-05) 219 | 220 | 221 | 222 | 223 | ## [2.0.2](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.0.1...v2.0.2) (2018-03-20) 224 | 225 | 226 | ### Features 227 | 228 | * clear console also according to log level ([655203d](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/655203d)) 229 | 230 | 231 | 232 | 233 | ## [2.0.1](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v2.0.0...v2.0.1) (2018-03-20) 234 | 235 | 236 | ### Bug Fixes 237 | 238 | * npm badge ([9bb4d3a](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/9bb4d3a)) 239 | 240 | 241 | ### Features 242 | 243 | * add log level ([ddcf45a](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/ddcf45a)) 244 | 245 | 246 | 247 | 248 | # [2.0.0](https://github.com/nuxt/friendly-errors-webpack-plugin/compare/v1.6.1...v2.0.0) (2018-03-16) 249 | 250 | 251 | ### Bug Fixes 252 | 253 | * comma-dangle error ([2360434](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/2360434)) 254 | * lint script ([c3e88c9](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/c3e88c9)) 255 | * move comma-dangle into rules ([9cd26c5](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/9cd26c5)) 256 | * only transforms tests with babel. prevents [#35](https://github.com/nuxt/friendly-errors-webpack-plugin/issues/35) ([#41](https://github.com/nuxt/friendly-errors-webpack-plugin/issues/41)) ([c8c4f3e](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/c8c4f3e)) 257 | * test failures ([34ac16b](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/34ac16b)) 258 | * windows eslint ([01f8059](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/01f8059)) 259 | 260 | 261 | ### Features 262 | 263 | * add release script ([bcd208f](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/bcd208f)) 264 | * upgrade to webpack4 ([458efe9](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/458efe9)) 265 | * upgrade to webpack4 ([8927021](https://github.com/nuxt/friendly-errors-webpack-plugin/commit/8927021)) 266 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Geoffroy Warin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Friendly-errors-webpack-plugin 2 | 3 | [![npm](https://img.shields.io/npm/v/@nuxtjs/friendly-errors-webpack-plugin.svg)](https://www.npmjs.com/package/@nuxtjs/friendly-errors-webpack-plugin) 4 | [![Build Status](https://travis-ci.org/nuxt/friendly-errors-webpack-plugin.svg?branch=master)](https://travis-ci.org/nuxt/friendly-errors-webpack-plugin) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/hqi9g8u2e17epr2d?svg=true)](https://ci.appveyor.com/project/nuxt/friendly-errors-webpack-plugin/branch/master) 6 | 7 | Friendly-errors-webpack-plugin recognizes certain classes of webpack 8 | errors and cleans, aggregates and prioritizes them to provide a better 9 | Developer Experience. 10 | 11 | It is easy to add types of errors so if you would like to see more 12 | errors get handled, please open a [PR](https://help.github.com/articles/creating-a-pull-request/)! 13 | 14 | ## Getting started 15 | 16 | ### Installation 17 | 18 | ```bash 19 | npm install @nuxt/friendly-errors-webpack-plugin --save-dev 20 | ``` 21 | 22 | ### Basic usage 23 | 24 | Simply add `FriendlyErrorsWebpackPlugin` to the plugin section in your Webpack config. 25 | 26 | ```javascript 27 | var FriendlyErrorsWebpackPlugin = require('@nuxt/friendly-errors-webpack-plugin'); 28 | 29 | var webpackConfig = { 30 | // ... 31 | plugins: [ 32 | new FriendlyErrorsWebpackPlugin(), 33 | ], 34 | // ... 35 | } 36 | ``` 37 | 38 | ### Turn off errors 39 | 40 | You need to turn off all error logging by setting your webpack config quiet option to true. 41 | 42 | ```javascript 43 | app.use(require('webpack-dev-middleware')(compiler, { 44 | // ... 45 | logLevel: 'SILENT', 46 | // ... 47 | })); 48 | ``` 49 | 50 | If you use the webpack-dev-server, there is a setting in webpack's ```devServer``` options: 51 | 52 | ```javascript 53 | // webpack config root 54 | { 55 | // ... 56 | devServer: { 57 | // ... 58 | quiet: true, 59 | // ... 60 | }, 61 | // ... 62 | } 63 | ``` 64 | 65 | If you use webpack-hot-middleware, that is done by setting the log option to `false`. You can do something sort of like this, depending upon your setup: 66 | 67 | ```javascript 68 | app.use(require('webpack-hot-middleware')(compiler, { 69 | log: false 70 | })); 71 | ``` 72 | 73 | _Thanks to [webpack-dashboard](https://github.com/FormidableLabs/webpack-dashboard) for this piece of info._ 74 | 75 | ## Demo 76 | 77 | ### Build success 78 | 79 | ![success](http://i.imgur.com/MkUEhYz.gif) 80 | 81 | ### eslint-loader errors 82 | 83 | ![lint](http://i.imgur.com/xYRkldr.gif) 84 | 85 | ### babel-loader syntax errors 86 | 87 | ![babel](http://i.imgur.com/W59z8WF.gif) 88 | 89 | ### Module not found 90 | 91 | ![babel](http://i.imgur.com/OivW4As.gif) 92 | 93 | ## Options 94 | 95 | You can pass options to the plugin: 96 | 97 | ```js 98 | new FriendlyErrorsPlugin({ 99 | compilationSuccessInfo: { 100 | messages: ['You application is running here http://localhost:3000'], 101 | notes: ['Some additional notes to be displayed upon successful compilation'] 102 | }, 103 | onErrors: function (severity, errors) { 104 | // You can listen to errors transformed and prioritized by the plugin 105 | // severity can be 'error' or 'warn' 106 | }, 107 | // should the console be cleared between each compilation? 108 | // default is true 109 | clearConsole: true, 110 | 111 | // INFO: all logs 112 | // WARNING: warnings and errors 113 | // ERROR: only errors 114 | // SILENT: no log 115 | logLevel: true, 116 | 117 | // base: default 118 | // consola: consola adapter 119 | // can also be npm package name or reporter object 120 | reporter: 'consola' 121 | 122 | // add formatters and transformers (see below) 123 | additionalFormatters: [], 124 | additionalTransformers: [] 125 | }) 126 | ``` 127 | 128 | ## Adding desktop notifications 129 | 130 | The plugin has no native support for desktop notifications but it is easy 131 | to add them thanks to [node-notifier](https://www.npmjs.com/package/node-notifier) for instance. 132 | 133 | ```js 134 | var NotifierPlugin = require('@nuxt/friendly-errors-webpack-plugin'); 135 | var notifier = require('node-notifier'); 136 | var ICON = path.join(__dirname, 'icon.png'); 137 | 138 | new NotifierPlugin({ 139 | onErrors: (severity, errors) => { 140 | if (severity !== 'error') { 141 | return; 142 | } 143 | const error = errors[0]; 144 | notifier.notify({ 145 | title: "Webpack error", 146 | message: severity + ': ' + error.name, 147 | subtitle: error.file || '', 148 | icon: ICON 149 | }); 150 | } 151 | }) 152 | ] 153 | ``` 154 | 155 | ## API 156 | 157 | ### Transformers and formatters 158 | 159 | Webpack's errors processing, is done in four phases: 160 | 161 | 1. Extract relevant info from webpack errors. This is done by the plugin [here](https://github.com/nuxt/friendly-errors-webpack-plugin/blob/master/src/core/extractWebpackError.js) 162 | 2. Apply transformers to all errors to identify and annotate well know errors and give them a priority 163 | 3. Get only top priority error or top priority warnings if no errors are thrown 164 | 4. Apply formatters to all annotated errors 165 | 166 | You can add transformers and formatters. Please see [transformErrors](https://github.com/nuxt/friendly-errors-webpack-plugin/blob/master/src/core/transformErrors.js), 167 | and [formatErrors](https://github.com/nuxt/friendly-errors-webpack-plugin/blob/master/src/core/formatErrors.js) 168 | in the source code and take a look a the [default transformers](https://github.com/nuxt/friendly-errors-webpack-plugin/tree/master/src/transformers) 169 | and the [default formatters](https://github.com/nuxt/friendly-errors-webpack-plugin/tree/master/src/formatters). 170 | 171 | ### Customize Reporters 172 | 173 | Reporter is a class for generating output of errors messages, structure is: 174 | 175 | 1. Include following levels log methods: `success`, `info`, `note`, `warn`, `error`. 176 | 1. Include method `clearConsole` for clearing the terminal console. 177 | 178 | You can take a look at source code as example [base reporter](https://github.com/nuxt/friendly-errors-webpack-plugin/blob/master/src/reporters/base.js) 179 | -------------------------------------------------------------------------------- /_sandbox/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true, 6 | "commonjs": true, 7 | "es6": true, 8 | "jest": true, 9 | "node": true 10 | }, 11 | "parserOptions": { 12 | "ecmaVersion": 6, 13 | "sourceType": "module" 14 | }, 15 | "rules": { 16 | // http://eslint.org/docs/rules/ 17 | "no-unused-expressions": "warn", 18 | "no-unused-labels": "warn", 19 | "no-unused-vars": ["warn", { "vars": "local", "args": "none" }] 20 | } 21 | } -------------------------------------------------------------------------------- /_sandbox/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ -------------------------------------------------------------------------------- /_sandbox/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const unsed = 'I am unused'; 3 | 4 | export default class MyComponent extends React.Component { 5 | 6 | render() { 7 | return
Hello
8 | } 9 | } -------------------------------------------------------------------------------- /_sandbox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_sandbox", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": {}, 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "node watch.js" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /_sandbox/watch.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | const { consola } = require("consola"); 3 | const config = require("./webpack.config"); 4 | 5 | consola.wrapConsole(); 6 | 7 | const compiler = webpack(config); 8 | 9 | compiler.watch({}, (stats) => { 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /_sandbox/webpack.config.js: -------------------------------------------------------------------------------- 1 | const FriendlyErrorsWebpackPlugin = require('../index'); 2 | 3 | module.exports = { 4 | entry: __dirname + "/index.js", 5 | output: { 6 | path: __dirname + "/dist", 7 | filename: "bundle.js" 8 | }, 9 | plugins: [ 10 | new FriendlyErrorsWebpackPlugin({ 11 | reporter: 'consola' 12 | }) 13 | ], 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.js$/, 18 | loader: 'eslint-loader', 19 | enforce: 'pre', 20 | include: __dirname 21 | }, 22 | { 23 | test: /\.jsx?$/, 24 | loader: 'babel-loader', 25 | query: { 26 | presets: ['react'], 27 | }, 28 | exclude: /node_modules/ 29 | } 30 | ] 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: "16" 4 | 5 | cache: 6 | - "%LOCALAPPDATA%\\Yarn" 7 | - node_modules 8 | 9 | install: 10 | - ps: Install-Product node $env:nodejs_version 11 | - yarn install 12 | 13 | test_script: 14 | - node --version 15 | - yarn --version 16 | - yarn test 17 | 18 | build: false 19 | skip_branch_with_pr: true 20 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | const FriendlyErrorsWebpackPlugin = require('./src/friendly-errors-plugin') 3 | 4 | module.exports = FriendlyErrorsWebpackPlugin 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxt/friendly-errors-webpack-plugin", 3 | "publishConfig": { 4 | "access": "public" 5 | }, 6 | "version": "2.6.0", 7 | "description": "Recognizes certain classes of webpack errors and cleans, aggregates and prioritizes them to provide a better Developer Experience", 8 | "main": "index.js", 9 | "scripts": { 10 | "test": "eslint . && jest", 11 | "release": "standard-version" 12 | }, 13 | "files": [ 14 | "src", 15 | "index.js" 16 | ], 17 | "keywords": [ 18 | "friendly", 19 | "errors", 20 | "webpack", 21 | "plugin" 22 | ], 23 | "author": "Geoffroy Warin", 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/nuxt/friendly-errors-webpack-plugin.git" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/nuxt/friendly-errors-webpack-plugin/issues" 30 | }, 31 | "license": "MIT", 32 | "peerDependencies": { 33 | "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" 34 | }, 35 | "devDependencies": { 36 | "babel-core": "^6.26.3", 37 | "babel-eslint": "^8.2.6", 38 | "babel-loader": "^7.1.5", 39 | "babel-plugin-transform-async-to-generator": "^6.24.1", 40 | "babel-preset-react": "^6.24.1", 41 | "eslint": "^5.16.0", 42 | "eslint-config-standard": "^12.0.0", 43 | "eslint-loader": "^2.2.1", 44 | "eslint-plugin-import": "^2.29.0", 45 | "eslint-plugin-node": "^8.0.1", 46 | "eslint-plugin-promise": "^4.3.1", 47 | "eslint-plugin-standard": "^4.1.0", 48 | "expect": "^1.20.2", 49 | "jest": "^29.7.0", 50 | "memory-fs": "^0.4.1", 51 | "standard-version": "^4.4.0", 52 | "strip-ansi": "^5.2.0", 53 | "webpack": "^4.47.0" 54 | }, 55 | "dependencies": { 56 | "chalk": "^2.4.2", 57 | "consola": "^3.2.3", 58 | "error-stack-parser": "^2.1.4", 59 | "string-width": "^4.2.3" 60 | }, 61 | "jest": { 62 | "testEnvironment": "node", 63 | "transformIgnorePatterns": [ 64 | "/node_modules/", 65 | "/src/", 66 | "/index.js" 67 | ] 68 | }, 69 | "engines": { 70 | "node": ">=14.18.0", 71 | "npm": ">=5.0.0" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/core/extractWebpackError.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ErrorStackParser = require('error-stack-parser') 4 | const RequestShortener = require('webpack/lib/RequestShortener') 5 | 6 | // TODO: allow the location to be customized in options 7 | const requestShortener = new RequestShortener(process.cwd()) 8 | 9 | /* 10 | This logic is mostly duplicated from webpack/lib/Stats.js#toJson() 11 | See: https://github.com/webpack/webpack/blob/2f618e733aab4755deb42e9d8e859609005607c0/lib/Stats.js#L89 12 | */ 13 | 14 | function extractError (e) { 15 | return { 16 | message: e.message, 17 | file: getFile(e), 18 | origin: getOrigin(e), 19 | name: e.name, 20 | severity: 0, 21 | webpackError: e, 22 | originalStack: getOriginalErrorStack(e) 23 | } 24 | } 25 | 26 | function getOriginalErrorStack (e) { 27 | while (e.error != null) { 28 | e = e.error 29 | } 30 | if (e.stack) { 31 | return ErrorStackParser.parse(e) 32 | } 33 | return [] 34 | } 35 | 36 | function getFile (e) { 37 | if (e.file) { 38 | return e.file 39 | } else if (e.module && e.module.readableIdentifier && typeof e.module.readableIdentifier === 'function') { 40 | return e.module.readableIdentifier(requestShortener) 41 | } 42 | } 43 | 44 | function getOrigin (e) { 45 | let origin = '' 46 | if (e.dependencies && e.origin) { 47 | origin += '\n @ ' + e.origin.readableIdentifier(requestShortener) 48 | e.dependencies.forEach(function (dep) { 49 | if (!dep.loc) return 50 | if (typeof dep.loc === 'string') return 51 | if (!dep.loc.start) return 52 | if (!dep.loc.end) return 53 | origin += ' ' + dep.loc.start.line + ':' + dep.loc.start.column + '-' + 54 | (dep.loc.start.line !== dep.loc.end.line ? dep.loc.end.line + ':' : '') + dep.loc.end.column 55 | }) 56 | var current = e.origin 57 | while (current.issuer && typeof current.issuer.readableIdentifier === 'function') { 58 | current = current.issuer 59 | origin += '\n @ ' + current.readableIdentifier(requestShortener) 60 | } 61 | } 62 | return origin 63 | } 64 | 65 | module.exports = extractError 66 | -------------------------------------------------------------------------------- /src/core/formatErrors.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Applies formatters to all AnnotatedErrors. 5 | * 6 | * A formatter has the following signature: FormattedError => Array. 7 | * It takes a formatted error produced by a transformer and returns a list 8 | * of log statements to print. 9 | * 10 | */ 11 | function formatErrors (errors, formatters, errorType) { 12 | const format = (formatter) => formatter(errors, errorType) || [] 13 | const flatten = (accum, curr) => accum.concat(curr) 14 | 15 | return formatters.map(format).reduce(flatten, []) 16 | } 17 | 18 | module.exports = formatErrors 19 | -------------------------------------------------------------------------------- /src/core/transformErrors.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const extractError = require('./extractWebpackError') 4 | 5 | /** 6 | * Applies all transformers to all errors and returns "annotated" 7 | * errors. 8 | * 9 | * Each transformer should have the following signature WebpackError => AnnotatedError 10 | * 11 | * A WebpackError has the following fields: 12 | * - message 13 | * - file 14 | * - origin 15 | * - name 16 | * - severity 17 | * - webpackError (original error) 18 | * 19 | * An AnnotatedError should be an extension (Object.assign) of the WebpackError 20 | * and add whatever information is convenient for formatting. 21 | * In particular, they should have a 'priority' field. 22 | * 23 | * The plugin will only display errors having maximum priority at the same time. 24 | * 25 | * If they don't have a 'type' field, the will be handled by the default formatter. 26 | */ 27 | function processErrors (errors, transformers) { 28 | const transform = (error, transformer) => transformer(error) 29 | const applyTransformations = (error) => transformers.reduce(transform, error) 30 | 31 | return errors.map(extractError).map(applyTransformations) 32 | } 33 | 34 | module.exports = processErrors 35 | -------------------------------------------------------------------------------- /src/formatters/defaultError.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const concat = require('../utils').concat 4 | 5 | function displayError (severity, error) { 6 | const errors = concat( 7 | error.message, 8 | (error.origin ? error.origin : undefined), 9 | '', 10 | error.infos 11 | ) 12 | 13 | errors.unshift([severity, removeLoaders(error.file) || error.name]) 14 | 15 | return errors 16 | } 17 | 18 | function removeLoaders (file) { 19 | if (!file) { 20 | return '' 21 | } 22 | const split = file.split('!') 23 | const filePath = split[split.length - 1] 24 | return `in ${filePath}` 25 | } 26 | 27 | function isDefaultError (error) { 28 | return !error.type 29 | } 30 | 31 | /** 32 | * Format errors without a type 33 | */ 34 | function format (errors, type) { 35 | return errors 36 | .filter(isDefaultError) 37 | .reduce((accum, error) => ( 38 | accum.concat(displayError(type, error)) 39 | ), []) 40 | } 41 | 42 | module.exports = format 43 | -------------------------------------------------------------------------------- /src/formatters/eslintError.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const concat = require('../utils').concat 4 | const chalk = require('chalk') 5 | 6 | const infos = [ 7 | 'You may use special comments to disable some warnings.', 8 | 'Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.', 9 | 'Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.' 10 | ] 11 | 12 | function displayError (error) { 13 | return [error.message, ''] 14 | } 15 | 16 | function format (errors, type) { 17 | const lintErrors = errors.filter(e => e.type === 'lint-error') 18 | if (lintErrors.length > 0) { 19 | const flatten = (accum, curr) => accum.concat(curr) 20 | return concat( 21 | lintErrors 22 | .map(error => displayError(error)) 23 | .reduce(flatten, []), 24 | infos 25 | ) 26 | } 27 | 28 | return [] 29 | } 30 | 31 | module.exports = format 32 | -------------------------------------------------------------------------------- /src/formatters/moduleNotFound.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const concat = require('../utils').concat 3 | 4 | function isRelative (module) { 5 | return module.startsWith('./') || module.startsWith('../') 6 | } 7 | 8 | function formatFileList (files) { 9 | const length = files.length 10 | if (!length) return '' 11 | return ` in ${files[0]}${files[1] ? `, ${files[1]}` : ''}${length > 2 ? ` and ${length - 2} other${length === 3 ? '' : 's'}` : ''}` 12 | } 13 | 14 | function formatGroup (group) { 15 | const files = group.errors.map(e => e.file).filter(Boolean) 16 | return `* ${group.module}${formatFileList(files)}` 17 | } 18 | 19 | function forgetToInstall (missingDependencies) { 20 | const moduleNames = missingDependencies.map(missingDependency => missingDependency.module) 21 | 22 | if (missingDependencies.length === 1) { 23 | return `To install it, you can run: npm install --save ${moduleNames.join(' ')}` 24 | } 25 | 26 | return `To install them, you can run: npm install --save ${moduleNames.join(' ')}` 27 | } 28 | 29 | function dependenciesNotFound (dependencies) { 30 | if (dependencies.length === 0) return 31 | 32 | return concat( 33 | dependencies.length === 1 ? 'This dependency was not found:' : 'These dependencies were not found:', 34 | '', 35 | dependencies.map(formatGroup), 36 | '', 37 | forgetToInstall(dependencies) 38 | ) 39 | } 40 | 41 | function relativeModulesNotFound (modules) { 42 | if (modules.length === 0) return 43 | 44 | return concat( 45 | modules.length === 1 ? 'This relative module was not found:' : 'These relative modules were not found:', 46 | '', 47 | modules.map(formatGroup) 48 | ) 49 | } 50 | 51 | function groupModules (errors) { 52 | const missingModule = new Map() 53 | 54 | errors.forEach((error) => { 55 | if (!missingModule.has(error.module)) { 56 | missingModule.set(error.module, []) 57 | } 58 | missingModule.get(error.module).push(error) 59 | }) 60 | 61 | return Array.from(missingModule.keys()).map(module => ({ 62 | module: module, 63 | relative: isRelative(module), 64 | errors: missingModule.get(module) 65 | })) 66 | } 67 | 68 | function formatErrors (errors) { 69 | if (errors.length === 0) { 70 | return [] 71 | } 72 | 73 | const groups = groupModules(errors) 74 | 75 | const dependencies = groups.filter(group => !group.relative) 76 | const relativeModules = groups.filter(group => group.relative) 77 | 78 | return concat( 79 | dependenciesNotFound(dependencies), 80 | dependencies.length && relativeModules.length ? ['', ''] : null, 81 | relativeModulesNotFound(relativeModules) 82 | ) 83 | } 84 | 85 | function format (errors) { 86 | return formatErrors(errors.filter((e) => ( 87 | e.type === 'module-not-found' 88 | ))) 89 | } 90 | 91 | module.exports = format 92 | -------------------------------------------------------------------------------- /src/friendly-errors-plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const transformErrors = require('./core/transformErrors') 4 | const formatErrors = require('./core/formatErrors') 5 | const reporters = require('./reporters') 6 | const utils = require('./utils') 7 | const { titles } = require('./utils/log') 8 | 9 | const concat = utils.concat 10 | const uniqueBy = utils.uniqueBy 11 | 12 | const defaultTransformers = [ 13 | require('./transformers/babelSyntax'), 14 | require('./transformers/moduleNotFound'), 15 | require('./transformers/esLintError') 16 | ] 17 | 18 | const defaultFormatters = [ 19 | require('./formatters/moduleNotFound'), 20 | require('./formatters/eslintError'), 21 | require('./formatters/defaultError') 22 | ] 23 | 24 | const logLevels = { 25 | 'INFO': 0, 26 | 'WARNING': 1, 27 | 'ERROR': 2, 28 | 'SILENT': 3 29 | } 30 | 31 | class FriendlyErrorsWebpackPlugin { 32 | constructor (options) { 33 | options = options || {} 34 | this.compilationSuccessInfo = options.compilationSuccessInfo || {} 35 | this.onErrors = options.onErrors 36 | this.shouldClearConsole = options.clearConsole == null ? true : Boolean(options.clearConsole) 37 | this.logLevel = logLevels[(options.logLevel || 'info').toUpperCase()] 38 | this.formatters = concat(defaultFormatters, options.additionalFormatters) 39 | this.transformers = concat(defaultTransformers, options.additionalTransformers) 40 | this.previousEndTimes = {} 41 | 42 | let reporter = options.reporter || 'base' 43 | if (typeof reporter === 'string') { 44 | reporter = new (require(reporters[reporter] || reporter))() 45 | } 46 | this.reporter = reporter 47 | } 48 | 49 | apply (compiler) { 50 | const doneFn = stats => { 51 | this.clearConsole() 52 | 53 | const hasErrors = stats.hasErrors() 54 | const hasWarnings = stats.hasWarnings() 55 | 56 | let cleared = false 57 | 58 | if (!hasErrors && !hasWarnings && this.logLevel < 1) { 59 | cleared = this.clearConsole() 60 | this.displaySuccess(stats) 61 | return 62 | } 63 | 64 | if (hasWarnings && this.logLevel < 2) { 65 | cleared = this.clearConsole() 66 | this.displayErrors(extractErrorsFromStats(stats, 'warnings'), 'warn') 67 | } 68 | 69 | if (hasErrors && this.logLevel < 3) { 70 | cleared || this.clearConsole() 71 | this.displayErrors(extractErrorsFromStats(stats, 'errors'), 'error') 72 | } 73 | } 74 | 75 | const invalidFn = () => { 76 | this.clearConsole() 77 | this.reporter.info('WAIT', 'Compiling...') 78 | } 79 | 80 | if (compiler.hooks) { 81 | const plugin = { name: 'FriendlyErrorsWebpackPlugin' } 82 | 83 | compiler.hooks.done.tap(plugin, doneFn) 84 | compiler.hooks.invalid.tap(plugin, invalidFn) 85 | } else { 86 | compiler.plugin('done', doneFn) 87 | compiler.plugin('invalid', invalidFn) 88 | } 89 | } 90 | 91 | clearConsole () { 92 | if (this.shouldClearConsole) { 93 | this.reporter.clearConsole() 94 | } 95 | return true 96 | } 97 | 98 | displaySuccess (stats) { 99 | const time = isMultiStats(stats) ? this.getMultiStatsCompileTime(stats) : this.getStatsCompileTime(stats) 100 | this.reporter.success('DONE', 'Compiled successfully in ' + time + 'ms') 101 | 102 | if (this.compilationSuccessInfo.messages) { 103 | this.compilationSuccessInfo.messages.forEach(message => this.reporter.info(message)) 104 | } 105 | if (this.compilationSuccessInfo.notes) { 106 | this.reporter.log() 107 | this.compilationSuccessInfo.notes.forEach(note => this.reporter.note(note)) 108 | } 109 | } 110 | 111 | displayErrors (errors, severity) { 112 | const processedErrors = transformErrors(errors, this.transformers) 113 | 114 | const topErrors = getMaxSeverityErrors(processedErrors) 115 | const nbErrors = topErrors.length 116 | 117 | const subtitle = severity === 'error' 118 | ? `Failed to compile with ${nbErrors} ${titles[severity]}s` 119 | : `Compiled with ${nbErrors} ${titles[severity]}s` 120 | this.reporter[severity](severity.toUpperCase(), subtitle) 121 | 122 | if (this.onErrors) { 123 | this.onErrors(severity, topErrors) 124 | } 125 | 126 | formatErrors(topErrors, this.formatters, severity) 127 | .forEach(chunk => { 128 | this.reporter[severity].apply(this.reporter, [].concat(chunk)) 129 | }) 130 | } 131 | 132 | getStatsCompileTime (stats, statsIndex) { 133 | // When we have multi compilations but only one of them is rebuilt, we need to skip the 134 | // unchanged compilers to report the true rebuild time. 135 | if (statsIndex !== undefined) { 136 | if (this.previousEndTimes[statsIndex] === stats.endTime) { 137 | return 0 138 | } 139 | 140 | this.previousEndTimes[statsIndex] = stats.endTime 141 | } 142 | 143 | return stats.endTime - stats.startTime 144 | } 145 | 146 | getMultiStatsCompileTime (stats) { 147 | // Webpack multi compilations run in parallel so using the longest duration. 148 | // https://webpack.github.io/docs/configuration.html#multiple-configurations 149 | return stats.stats 150 | .reduce((time, stats, index) => Math.max(time, this.getStatsCompileTime(stats, index)), 0) 151 | } 152 | } 153 | 154 | function extractErrorsFromStats (stats, type) { 155 | let errors = [] 156 | if (isMultiStats(stats)) { 157 | errors = stats.stats 158 | .reduce((errors, stats) => errors.concat(extractErrorsFromStats(stats, type)), []) 159 | } else { 160 | if (Array.isArray(stats.compilation.children)) { 161 | for (const child of stats.compilation.children) { 162 | const childStats = child.getStats() 163 | errors.push.apply(errors, extractErrorsFromStats(childStats, type)) 164 | } 165 | } 166 | errors.push.apply(errors, stats.compilation[type]) 167 | } 168 | // Dedupe to avoid showing the same error many times when multiple 169 | // compilers depend on the same module. 170 | return uniqueBy(errors, error => error.message) 171 | } 172 | 173 | function isMultiStats (stats) { 174 | return stats.stats 175 | } 176 | 177 | function getMaxSeverityErrors (errors) { 178 | const maxSeverity = getMaxInt(errors, 'severity') 179 | return errors.filter(e => e.severity === maxSeverity) 180 | } 181 | 182 | function getMaxInt (collection, propertyName) { 183 | return collection.reduce((res, curr) => { 184 | return curr[propertyName] > res ? curr[propertyName] : res 185 | }, 0) 186 | } 187 | 188 | module.exports = FriendlyErrorsWebpackPlugin 189 | -------------------------------------------------------------------------------- /src/reporters/base.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { colors, formatTitle, formatText, clearConsole } = require('../utils/log') 4 | const chalk = require('chalk') 5 | const stringWidth = require('string-width') 6 | 7 | class BaseReporter { 8 | constructor () { 9 | this.enabled = true 10 | this.initLevels() 11 | } 12 | 13 | enable () { 14 | this.enabled = true 15 | } 16 | 17 | log () { 18 | if (this.enabled) { 19 | console.log.apply(console, arguments) 20 | } 21 | } 22 | 23 | initLevels () { 24 | for (const level of Object.keys(colors)) { 25 | this[level] = (title, message) => { 26 | if (!this.enabled) return 27 | 28 | if (message === undefined) { 29 | message = title 30 | this.log(message) 31 | return 32 | } 33 | 34 | title = formatTitle(level, title) 35 | message = formatText(level, message) 36 | if (process.env.NODE_ENV !== 'test') { 37 | message = this.appendTimestamp(title, message) 38 | } 39 | this.log(title, message) 40 | this.log() 41 | } 42 | } 43 | } 44 | 45 | appendTimestamp (title, message) { 46 | // Make timestamp appear at the end of the line 47 | const line = `${title} ${message}` 48 | const dateString = chalk.grey(new Date().toLocaleTimeString()) 49 | let logSpace = process.stdout.columns - stringWidth(line) - stringWidth(dateString) 50 | if (logSpace <= 0) { 51 | logSpace = 10 52 | } 53 | return `${message}${' '.repeat(logSpace)}${dateString}` 54 | } 55 | 56 | clearConsole () { 57 | if (this.enabled) { 58 | clearConsole() 59 | } 60 | } 61 | } 62 | 63 | module.exports = BaseReporter 64 | -------------------------------------------------------------------------------- /src/reporters/consola.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { colors, clearConsole } = require('../utils/log') 4 | const consola = require('consola') 5 | 6 | class ConsolaReporter { 7 | constructor () { 8 | this.enabled = true 9 | this.consola = consola.withTag('friendly-errors') 10 | this.initLevels() 11 | } 12 | 13 | enable () { 14 | this.enabled = true 15 | } 16 | 17 | log () { 18 | if (this.enabled) { 19 | this.consola.log.apply(this.consola, arguments) 20 | } 21 | } 22 | 23 | initLevels () { 24 | for (const level of Object.keys(colors)) { 25 | this[level] = (title, message) => { 26 | if (!this.enabled) return 27 | if (title === 'WAIT') return 28 | 29 | if (message === undefined) { 30 | this.consola.log(title) 31 | return 32 | } 33 | (this.consola[level] || this.consola.log)(message) 34 | } 35 | } 36 | } 37 | 38 | clearConsole () { 39 | if (this.enabled) { 40 | clearConsole() 41 | } 42 | } 43 | } 44 | 45 | module.exports = ConsolaReporter 46 | -------------------------------------------------------------------------------- /src/reporters/index.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | 3 | module.exports = { 4 | base: resolve(__dirname, 'base'), 5 | consola: resolve(__dirname, 'consola') 6 | } 7 | -------------------------------------------------------------------------------- /src/transformers/babelSyntax.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * This will be removed in next versions as it is not handled in the babel-loader 5 | * See: https://github.com/geowarin/friendly-errors-webpack-plugin/issues/2 6 | */ 7 | function cleanStackTrace (message) { 8 | return message 9 | .replace(/^\s*at\s.*:\d+:\d+[\s)]*\n/gm, '') // at ... ...:x:y 10 | } 11 | 12 | function cleanMessage (message) { 13 | return message 14 | // match until the last semicolon followed by a space 15 | // this should match 16 | // linux => "(SyntaxError: )Unexpected token (5:11)" 17 | // windows => "(SyntaxError: C:/projects/index.js: )Unexpected token (5:11)" 18 | .replace(/^Module build failed.*:(\sSyntaxError:(.*:)?)?\s/, 'Syntax Error: ') 19 | } 20 | 21 | function isBabelSyntaxError (e) { 22 | return e.name === 'ModuleBuildError' && 23 | e.message.indexOf('SyntaxError') >= 0 24 | } 25 | 26 | function transform (error) { 27 | if (isBabelSyntaxError(error)) { 28 | return Object.assign({}, error, { 29 | message: cleanStackTrace(cleanMessage(error.message) + '\n'), 30 | severity: 1000, 31 | name: 'Syntax Error' 32 | }) 33 | } 34 | 35 | return error 36 | } 37 | 38 | module.exports = transform 39 | -------------------------------------------------------------------------------- /src/transformers/esLintError.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function isEslintError (e) { 4 | return e.originalStack 5 | .some(stackframe => stackframe.fileName && stackframe.fileName.indexOf('eslint-loader') > 0) 6 | } 7 | 8 | function transform (error) { 9 | if (isEslintError(error)) { 10 | return Object.assign({}, error, { 11 | name: 'Lint error', 12 | type: 'lint-error' 13 | }) 14 | } 15 | 16 | return error 17 | } 18 | 19 | module.exports = transform 20 | -------------------------------------------------------------------------------- /src/transformers/moduleNotFound.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const TYPE = 'module-not-found' 4 | 5 | function isModuleNotFoundError (e) { 6 | const webpackError = e.webpackError || {} 7 | return webpackError.dependencies && 8 | webpackError.dependencies.length > 0 && 9 | e.name === 'ModuleNotFoundError' && 10 | e.message.indexOf('Module not found') === 0 11 | } 12 | 13 | function transform (error) { 14 | const webpackError = error.webpackError 15 | if (isModuleNotFoundError(error)) { 16 | const dependency = webpackError.dependencies[0] 17 | const module = dependency.userRequest || dependency.request 18 | return Object.assign({}, error, { 19 | message: `Module not found ${module}`, 20 | type: TYPE, 21 | severity: 900, 22 | module, 23 | name: 'Module not found' 24 | }) 25 | } 26 | 27 | return error 28 | } 29 | 30 | module.exports = transform 31 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Concat and flattens non-null values. 5 | * Ex: concat(1, undefined, 2, [3, 4]) = [1, 2, 3, 4] 6 | */ 7 | function concat () { 8 | const args = Array.from(arguments).filter(e => e != null) 9 | const baseArray = Array.isArray(args[0]) ? args[0] : [args[0]] 10 | return Array.prototype.concat.apply(baseArray, args.slice(1)) 11 | } 12 | 13 | /** 14 | * Dedupes array based on criterion returned from iteratee function. 15 | * Ex: uniqueBy( 16 | * [{ id: 1 }, { id: 1 }, { id: 2 }], 17 | * val => val.id 18 | * ) = [{ id: 1 }, { id: 2 }] 19 | */ 20 | function uniqueBy (arr, fun) { 21 | const seen = {} 22 | return arr.filter(el => { 23 | const e = fun(el) 24 | return !(e in seen) && (seen[e] = 1) 25 | }) 26 | } 27 | 28 | module.exports = { 29 | concat: concat, 30 | uniqueBy: uniqueBy 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/log.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const chalk = require('chalk') 4 | 5 | const colors = { 6 | success: 'green', 7 | info: 'blue', 8 | note: 'white', 9 | warn: 'yellow', 10 | error: 'red' 11 | } 12 | 13 | const titles = { 14 | success: 'success', 15 | info: 'info', 16 | note: 'note', 17 | warn: 'warning', 18 | error: 'error' 19 | } 20 | 21 | function bgColor (level) { 22 | const color = textColor(level) 23 | return 'bg' + capitalizeFirstLetter(color) 24 | } 25 | 26 | function textColor (level) { 27 | return colors[level.toLowerCase()] || 'red' 28 | } 29 | 30 | function capitalizeFirstLetter (string) { 31 | return string.charAt(0).toUpperCase() + string.slice(1) 32 | } 33 | 34 | function formatTitle (severity, title) { 35 | return chalk[bgColor(severity)].black('', title, '') 36 | } 37 | 38 | function formatText (severity, message) { 39 | return chalk[textColor(severity)](message) 40 | } 41 | 42 | function clearConsole () { 43 | process.stdout.write( 44 | process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H' 45 | ) 46 | } 47 | 48 | module.exports = { 49 | colors, 50 | titles, 51 | formatText, 52 | formatTitle, 53 | clearConsole 54 | } 55 | -------------------------------------------------------------------------------- /test/fixtures/.gitignore: -------------------------------------------------------------------------------- 1 | **/dist -------------------------------------------------------------------------------- /test/fixtures/babel-syntax/index.js: -------------------------------------------------------------------------------- 1 | 2 | class MyComponent extends React.Component { 3 | 4 | render() { 5 | return
6 | } 7 | } -------------------------------------------------------------------------------- /test/fixtures/babel-syntax/webpack.config.js: -------------------------------------------------------------------------------- 1 | const FriendlyErrorsWebpackPlugin = require('../../../index'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: __dirname + "/index.js", 6 | output: { 7 | path: __dirname + "/dist", 8 | filename: "bundle.js" 9 | }, 10 | plugins: [ 11 | new FriendlyErrorsWebpackPlugin() 12 | ], 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.jsx?$/, 17 | loader: 'babel-loader', 18 | exclude: /node_modules/ 19 | } 20 | ] 21 | } 22 | }; -------------------------------------------------------------------------------- /test/fixtures/eslint-warnings/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true, 6 | "commonjs": true, 7 | "es6": true, 8 | "jest": true, 9 | "node": true 10 | }, 11 | "parserOptions": { 12 | "ecmaVersion": 6, 13 | "sourceType": "module" 14 | }, 15 | "rules": { 16 | "no-unused-expressions": "warn", 17 | "no-unused-labels": "warn", 18 | "no-unused-vars": ["warn", { "vars": "local", "args": "none" }] 19 | } 20 | } -------------------------------------------------------------------------------- /test/fixtures/eslint-warnings/index.js: -------------------------------------------------------------------------------- 1 | require('./module'); 2 | 3 | const unused = 'I am unused'; 4 | const unused2 = 'I am unused too'; 5 | -------------------------------------------------------------------------------- /test/fixtures/eslint-warnings/module.js: -------------------------------------------------------------------------------- 1 | const unused = 'I am unused'; 2 | -------------------------------------------------------------------------------- /test/fixtures/eslint-warnings/webpack.config.js: -------------------------------------------------------------------------------- 1 | const FriendlyErrorsWebpackPlugin = require('../../../index'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: __dirname + "/index.js", 6 | output: { 7 | path: __dirname + "/dist", 8 | filename: "bundle.js" 9 | }, 10 | plugins: [ 11 | new FriendlyErrorsWebpackPlugin(), 12 | 13 | ], 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.js$/, 18 | loader: 'eslint-loader', 19 | enforce: 'pre', 20 | include: __dirname, 21 | options: { 22 | ignore: false 23 | } 24 | }, 25 | { 26 | test: /\.jsx?$/, 27 | loader: 'babel-loader', 28 | exclude: /node_modules/ 29 | } 30 | ] 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /test/fixtures/module-errors/index.js: -------------------------------------------------------------------------------- 1 | require('not-found'); 2 | require('./non-existing'); 3 | require('../non-existing'); 4 | -------------------------------------------------------------------------------- /test/fixtures/module-errors/webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | const FriendlyErrorsWebpackPlugin = require('../../../index'); 3 | 4 | module.exports = { 5 | mode: 'production', 6 | entry: __dirname + "/index.js", 7 | output: { 8 | path: __dirname + "/dist", 9 | filename: "bundle.js" 10 | }, 11 | plugins: [ 12 | new FriendlyErrorsWebpackPlugin() 13 | ] 14 | }; -------------------------------------------------------------------------------- /test/fixtures/multi-compiler-module-errors/index.js: -------------------------------------------------------------------------------- 1 | require('./non-existing'); 2 | -------------------------------------------------------------------------------- /test/fixtures/multi-compiler-module-errors/index2.js: -------------------------------------------------------------------------------- 1 | require('not-found'); 2 | -------------------------------------------------------------------------------- /test/fixtures/multi-compiler-module-errors/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | mode: 'production', 4 | entry: __dirname + "/index.js", 5 | output: { 6 | path: __dirname + "/dist", 7 | filename: "bundle.js" 8 | } 9 | }, 10 | { 11 | mode: 'production', 12 | entry: __dirname + "/index2.js", 13 | output: { 14 | path: __dirname + "/dist", 15 | filename: "bundle2.js" 16 | } 17 | } 18 | ]; 19 | -------------------------------------------------------------------------------- /test/fixtures/multi-compiler-success/index.js: -------------------------------------------------------------------------------- 1 | module.exports = 'I am an entry point'; 2 | -------------------------------------------------------------------------------- /test/fixtures/multi-compiler-success/index2.js: -------------------------------------------------------------------------------- 1 | module.exports = 'I am another entry point'; 2 | -------------------------------------------------------------------------------- /test/fixtures/multi-compiler-success/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | mode: 'production', 4 | entry: __dirname + "/index.js", 5 | output: { 6 | path: __dirname + "/dist", 7 | filename: "bundle.js" 8 | } 9 | }, 10 | { 11 | mode: 'production', 12 | entry: __dirname + "/index2.js", 13 | output: { 14 | path: __dirname + "/dist", 15 | filename: "bundle2.js" 16 | } 17 | } 18 | ]; 19 | -------------------------------------------------------------------------------- /test/fixtures/success/index.js: -------------------------------------------------------------------------------- 1 | module.exports = 'I am an entry point'; 2 | -------------------------------------------------------------------------------- /test/fixtures/success/webpack.config.js: -------------------------------------------------------------------------------- 1 | const FriendlyErrorsWebpackPlugin = require('../../../index'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: __dirname + "/index.js", 6 | output: { 7 | path: __dirname + "/dist", 8 | filename: "bundle.js" 9 | }, 10 | plugins: [ 11 | new FriendlyErrorsWebpackPlugin() 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /test/integration.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const webpack = require('webpack') 4 | const FriendlyErrorsWebpackPlugin = require('../src/friendly-errors-plugin') 5 | const MemoryFileSystem = require('memory-fs') 6 | const path = require('path') 7 | const { captureReports } = require('./utils') 8 | 9 | const webpackPromise = function (config, globalPlugins) { 10 | const compiler = webpack(config) 11 | compiler.outputFileSystem = new MemoryFileSystem() 12 | if (Array.isArray(globalPlugins)) { 13 | globalPlugins.forEach(p => p.apply(compiler)) 14 | } 15 | 16 | return new Promise((resolve, reject) => { 17 | compiler.run(err => { 18 | if (err) { 19 | reject(err) 20 | } 21 | resolve() 22 | }) 23 | }) 24 | } 25 | 26 | async function executeAndGetLogs (fixture, globalPlugins, reporter) { 27 | const config = require(fixture) 28 | reporter = (globalPlugins || config.plugins)[0].reporter 29 | return captureReports(reporter, () => webpackPromise(config, globalPlugins)) 30 | } 31 | 32 | it('integration : success', async () => { 33 | const logs = await executeAndGetLogs('./fixtures/success/webpack.config') 34 | 35 | expect(logs.join('\n')).toMatch(/DONE {2}Compiled successfully in (.\d*)ms/) 36 | }) 37 | 38 | it('integration : module-errors', async () => { 39 | const logs = await executeAndGetLogs('./fixtures/module-errors/webpack.config.js') 40 | 41 | expect(logs).toEqual([ 42 | 'ERROR Failed to compile with 3 errors', 43 | '', 44 | 'This dependency was not found:', 45 | '', 46 | '* not-found in ./test/fixtures/module-errors/index.js', 47 | '', 48 | 'To install it, you can run: npm install --save not-found', 49 | '', 50 | '', 51 | 'These relative modules were not found:', 52 | '', 53 | '* ../non-existing in ./test/fixtures/module-errors/index.js', 54 | '* ./non-existing in ./test/fixtures/module-errors/index.js' 55 | ]) 56 | }) 57 | 58 | function filename (filePath) { 59 | return path.join(__dirname, path.normalize(filePath)) 60 | } 61 | 62 | it('integration : should display eslint warnings', async () => { 63 | const logs = await executeAndGetLogs('./fixtures/eslint-warnings/webpack.config.js') 64 | 65 | expect(logs.join('\n')).toEqual( 66 | `WARN Compiled with 2 warnings 67 | 68 | Module Warning (from ./node_modules/eslint-loader/index.js): 69 | 70 | ${filename('fixtures/eslint-warnings/index.js')} 71 | 3:7 warning 'unused' is assigned a value but never used no-unused-vars 72 | 4:7 warning 'unused2' is assigned a value but never used no-unused-vars 73 | 74 | ✖ 2 problems (0 errors, 2 warnings) 75 | 76 | Module Warning (from ./node_modules/eslint-loader/index.js): 77 | 78 | ${filename('fixtures/eslint-warnings/module.js')} 79 | 1:7 warning 'unused' is assigned a value but never used no-unused-vars 80 | 81 | ✖ 1 problem (0 errors, 1 warning) 82 | 83 | You may use special comments to disable some warnings. 84 | Use // eslint-disable-next-line to ignore the next line. 85 | Use /* eslint-disable */ to ignore all warnings in a file.` 86 | ) 87 | }) 88 | 89 | it('integration : babel syntax error', async () => { 90 | const logs = await executeAndGetLogs('./fixtures/babel-syntax/webpack.config') 91 | 92 | expect(logs).toEqual([ 93 | 'ERROR Failed to compile with 1 errors', 94 | '', 95 | 'error in ./test/fixtures/babel-syntax/index.js', 96 | '', 97 | `Syntax Error: Unexpected token (5:11) 98 | 99 | 3 |${' '} 100 | 4 | render() { 101 | > 5 | return
102 | | ^ 103 | 6 | } 104 | 7 | }`, 105 | '' 106 | ]) 107 | }) 108 | 109 | it('integration : webpack multi compiler : success', async () => { 110 | // We apply the plugin directly to the compiler when targeting multi-compiler 111 | let globalPlugins = [new FriendlyErrorsWebpackPlugin()] 112 | const logs = await executeAndGetLogs('./fixtures/multi-compiler-success/webpack.config', globalPlugins) 113 | 114 | expect(logs.join('\n')).toMatch(/DONE {2}Compiled successfully in (.\d*)ms/) 115 | }) 116 | 117 | it('integration : webpack multi compiler : module-errors', async () => { 118 | // We apply the plugin directly to the compiler when targeting multi-compiler 119 | let globalPlugins = [new FriendlyErrorsWebpackPlugin()] 120 | const logs = await executeAndGetLogs('./fixtures/multi-compiler-module-errors/webpack.config', globalPlugins) 121 | 122 | expect(logs).toEqual([ 123 | 'ERROR Failed to compile with 2 errors', 124 | '', 125 | 'This dependency was not found:', 126 | '', 127 | '* not-found in ./test/fixtures/multi-compiler-module-errors/index2.js', 128 | '', 129 | 'To install it, you can run: npm install --save not-found', 130 | '', 131 | '', 132 | 'This relative module was not found:', 133 | '', 134 | '* ./non-existing in ./test/fixtures/multi-compiler-module-errors/index.js' 135 | ]) 136 | }) 137 | -------------------------------------------------------------------------------- /test/unit/formatErrors.spec.js: -------------------------------------------------------------------------------- 1 | const formatErrors = require('../../src/core/formatErrors') 2 | const expect = require('expect') 3 | 4 | const simple = (errors) => errors 5 | .filter(error => !error.type).map(e => e.message) 6 | 7 | const allCaps = (errors) => errors 8 | .filter(error => error.type === 'other').map(e => e.message.toUpperCase()) 9 | 10 | const notFound = (errors) => errors 11 | .filter(error => error.type === 'not-found').map(() => 'Not found') 12 | 13 | it('formats the error based on the matching formatters', () => { 14 | const errors = [ 15 | { message: 'Error 1', type: undefined }, 16 | { message: 'Error 2', type: 'other' }, 17 | { message: 'Error 3', type: 'not-found' } 18 | ] 19 | 20 | expect(formatErrors(errors, [simple, allCaps, notFound], 'Error')).toEqual([ 21 | 'Error 1', 22 | 'ERROR 2', 23 | 'Not found' 24 | ]) 25 | }) 26 | -------------------------------------------------------------------------------- /test/unit/formatters/defaultError.spec.js: -------------------------------------------------------------------------------- 1 | const defaultError = require('../../../src/formatters/defaultError') 2 | const expect = require('expect') 3 | const stripAnsi = require('strip-ansi') 4 | 5 | const noColor = (arr) => arr.map(stripAnsi) 6 | const error = { message: 'Error message', file: './src/index.js' } 7 | 8 | it('Formats errors with no type', () => { 9 | expect(noColor(defaultError([error], 'Warning'))).toEqual([ 10 | [ 'Warning', 'in ./src/index.js' ], 11 | 'Error message', 12 | '' 13 | ]) 14 | }) 15 | 16 | it('Does not format other errors', () => { 17 | const otherError = { type: 'other-error' } 18 | expect(noColor(defaultError([otherError], 'Error'))).toEqual([]) 19 | }) 20 | -------------------------------------------------------------------------------- /test/unit/formatters/moduleNotFound.spec.js: -------------------------------------------------------------------------------- 1 | const moduleNotFound = require('../../../src/formatters/moduleNotFound') 2 | const expect = require('expect') 3 | 4 | it('Formats module-not-found errors', () => { 5 | const error = { type: 'module-not-found', module: 'redux' } 6 | expect(moduleNotFound([error])).toEqual([ 7 | 'This dependency was not found:', 8 | '', 9 | '* redux', 10 | '', 11 | 'To install it, you can run: npm install --save redux' 12 | ]) 13 | }) 14 | 15 | it('Groups all module-not-found into one', () => { 16 | const reduxError = { type: 'module-not-found', module: 'redux' } 17 | const reactError = { type: 'module-not-found', module: 'react' } 18 | expect(moduleNotFound([reduxError, reactError])).toEqual([ 19 | 'These dependencies were not found:', 20 | '', 21 | '* redux', 22 | '* react', 23 | '', 24 | 'To install them, you can run: npm install --save redux react' 25 | ]) 26 | }) 27 | 28 | it('Groups same module in module-not-found with 2 files', () => { 29 | const reduxError = { type: 'module-not-found', module: 'redux' } 30 | const reactError1 = { type: 'module-not-found', module: 'react', file: './src/file1.js' } 31 | const reactError2 = { type: 'module-not-found', module: 'react', file: '../src/file2.js' } 32 | expect(moduleNotFound([reduxError, reactError1, reactError2])).toEqual([ 33 | 'These dependencies were not found:', 34 | '', 35 | '* redux', 36 | '* react in ./src/file1.js, ../src/file2.js', 37 | '', 38 | 'To install them, you can run: npm install --save redux react' 39 | ]) 40 | }) 41 | 42 | it('Groups same module in module-not-found with 3 files', () => { 43 | const reduxError = { type: 'module-not-found', module: 'redux' } 44 | const reactError1 = { type: 'module-not-found', module: 'react', file: './src/file1.js' } 45 | const reactError2 = { type: 'module-not-found', module: 'react', file: './src/file2.js' } 46 | const reactError3 = { type: 'module-not-found', module: 'react', file: './src/file3.js' } 47 | expect(moduleNotFound([reduxError, reactError1, reactError2, reactError3])).toEqual([ 48 | 'These dependencies were not found:', 49 | '', 50 | '* redux', 51 | '* react in ./src/file1.js, ./src/file2.js and 1 other', 52 | '', 53 | 'To install them, you can run: npm install --save redux react' 54 | ]) 55 | }) 56 | 57 | it('Groups same module in module-not-found with 4 files', () => { 58 | const reduxError = { type: 'module-not-found', module: 'redux' } 59 | const reactError1 = { type: 'module-not-found', module: 'react', file: './src/file1.js' } 60 | const reactError2 = { type: 'module-not-found', module: 'react', file: './src/file2.js' } 61 | const reactError3 = { type: 'module-not-found', module: 'react', file: './src/file3.js' } 62 | const reactError4 = { type: 'module-not-found', module: 'react', file: './src/file4.js' } 63 | expect(moduleNotFound([reduxError, reactError1, reactError2, reactError3, reactError4])).toEqual([ 64 | 'These dependencies were not found:', 65 | '', 66 | '* redux', 67 | '* react in ./src/file1.js, ./src/file2.js and 2 others', 68 | '', 69 | 'To install them, you can run: npm install --save redux react' 70 | ]) 71 | }) 72 | 73 | it('Does not format other errors', () => { 74 | const otherError = { type: 'other-error', module: 'foo' } 75 | expect(moduleNotFound([otherError])).toEqual([]) 76 | }) 77 | -------------------------------------------------------------------------------- /test/unit/plugin/friendlyErrors.spec.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect') 2 | const EventEmitter = require('events') 3 | const Stats = require('webpack/lib/Stats') 4 | const MultiStats = require('webpack/lib/MultiStats') 5 | const { captureReports } = require('../../utils') 6 | EventEmitter.prototype.plugin = EventEmitter.prototype.on 7 | 8 | const FriendlyErrorsPlugin = require('../../../index') 9 | 10 | let notifierPlugin = new FriendlyErrorsPlugin() 11 | let mockCompiler = new EventEmitter() 12 | let reporter = notifierPlugin.reporter 13 | 14 | beforeEach(() => { 15 | notifierPlugin = new FriendlyErrorsPlugin() 16 | mockCompiler = new EventEmitter() 17 | reporter = notifierPlugin.reporter 18 | 19 | mockCompiler.hooks = { 20 | done: { 21 | tap (name, handler) { 22 | mockCompiler.plugin('done', handler) 23 | } 24 | }, 25 | invalid: { 26 | tap (name, handler) { 27 | mockCompiler.plugin('invalid', handler) 28 | } 29 | } 30 | } 31 | notifierPlugin.apply(mockCompiler) 32 | }) 33 | 34 | it('friendlyErrors : capture invalid message', async () => { 35 | const logs = await captureReports(reporter, () => mockCompiler.emit('invalid')) 36 | expect(logs).toEqual([ 37 | 'WAIT Compiling...', 38 | '' 39 | ]) 40 | }) 41 | 42 | it('friendlyErrors : capture compilation without errors', async () => { 43 | const stats = successfulCompilationStats() 44 | const logs = await captureReports(reporter, () => mockCompiler.emit('done', stats)) 45 | expect(logs).toEqual([ 46 | 'DONE Compiled successfully in 100ms', 47 | '' 48 | ]) 49 | }) 50 | 51 | it('friendlyErrors : default clearConsole option', () => { 52 | const plugin = new FriendlyErrorsPlugin() 53 | expect(plugin.shouldClearConsole).toBeTruthy() 54 | }) 55 | 56 | it('friendlyErrors : clearConsole option', () => { 57 | const plugin = new FriendlyErrorsPlugin({ clearConsole: false }) 58 | expect(plugin.shouldClearConsole).toBeFalsy() 59 | }) 60 | 61 | describe('friendlyErrors : multicompiler', () => { 62 | it('supplies the correct max compile time across multiple stats', async () => { 63 | const stats1 = successfulCompilationStats({ startTime: 0, endTime: 1000 }) 64 | const stats2 = successfulCompilationStats({ startTime: 2, endTime: 2002 }) 65 | const multistats = new MultiStats([stats1, stats2]) 66 | 67 | const logs = await captureReports(reporter, () => { 68 | mockCompiler.emit('done', multistats) 69 | }) 70 | 71 | expect(logs).toEqual([ 72 | 'DONE Compiled successfully in 2000ms', 73 | '' 74 | ]) 75 | }) 76 | 77 | it('supplies the correct recompile time with rebuild', async () => { 78 | // Test that rebuilds do not use the prior "max" value when recompiling just one stats object. 79 | // This ensures the user does not get incorrect build times in watch mode. 80 | const stats1 = successfulCompilationStats({ startTime: 0, endTime: 1000 }) 81 | const stats2 = successfulCompilationStats({ startTime: 0, endTime: 500 }) 82 | const stats2Rebuild = successfulCompilationStats({ startTime: 1020, endTime: 1050 }) 83 | 84 | const multistats = new MultiStats([stats1, stats2]) 85 | const multistatsRecompile = new MultiStats([stats1, stats2Rebuild]) 86 | 87 | const logs = await captureReports(reporter, () => { 88 | mockCompiler.emit('done', multistats) 89 | mockCompiler.emit('done', multistatsRecompile) 90 | }) 91 | 92 | expect(logs).toEqual([ 93 | 'DONE Compiled successfully in 1000ms', 94 | '', 95 | 'DONE Compiled successfully in 30ms', 96 | '' 97 | ]) 98 | }) 99 | }) 100 | 101 | function successfulCompilationStats (opts) { 102 | const options = Object.assign({ startTime: 0, endTime: 100 }, opts) 103 | 104 | const compilation = { 105 | errors: [], 106 | warnings: [], 107 | children: [] 108 | } 109 | const stats = new Stats(compilation) 110 | stats.startTime = options.startTime 111 | stats.endTime = options.endTime 112 | return stats 113 | } 114 | -------------------------------------------------------------------------------- /test/unit/transformers/babelSyntax.spec.js: -------------------------------------------------------------------------------- 1 | const babelSyntax = require('../../../src/transformers/babelSyntax') 2 | const expect = require('expect') 3 | 4 | it('Sets severity to 1000', () => { 5 | const error = { name: 'ModuleBuildError', message: 'SyntaxError' } 6 | expect(babelSyntax(error).severity).toEqual(1000) 7 | }) 8 | 9 | it('Does not set type (it should be handle by the default formatter)', () => { 10 | const error = { name: 'ModuleBuildError', message: 'SyntaxError' } 11 | expect(babelSyntax(error).type).toEqual(undefined) 12 | }) 13 | 14 | it('Ignores other errors', () => { 15 | const error = { name: 'OtherError' } 16 | expect(babelSyntax(error)).toEqual(error) 17 | }) 18 | -------------------------------------------------------------------------------- /test/unit/transformers/moduleNotFound.spec.js: -------------------------------------------------------------------------------- 1 | const moduleNotFound = require('../../../src/transformers/moduleNotFound') 2 | const expect = require('expect') 3 | const RequireContextDependency = require('webpack/lib/dependencies/RequireContextDependency') 4 | 5 | const error = { 6 | name: 'ModuleNotFoundError', 7 | message: 'Module not found : redux', 8 | webpackError: { 9 | dependencies: [{ request: 'redux' }] 10 | } 11 | } 12 | 13 | it('Sets severity to 900', () => { 14 | expect(moduleNotFound(error).severity).toEqual(900) 15 | }) 16 | 17 | it('Sets module name', () => { 18 | expect(moduleNotFound(error).module).toEqual('redux') 19 | }) 20 | 21 | it('Sets the appropiate message', () => { 22 | const message = 'Module not found redux' 23 | expect(moduleNotFound(error).message).toEqual(message) 24 | }) 25 | 26 | it('Sets the appropiate type', () => { 27 | expect(moduleNotFound({ 28 | name: 'ModuleNotFoundError', 29 | message: 'Module not found', 30 | webpackError: error.webpackError 31 | }).type).toEqual('module-not-found') 32 | }) 33 | 34 | it('Ignores other errors', () => { 35 | const error = { name: 'OtherError' } 36 | expect(moduleNotFound(error)).toEqual(error) 37 | }) 38 | 39 | it('Sets the correct message with a RequireContextDependency', () => { 40 | const message = 'Module not found redux' 41 | 42 | expect(moduleNotFound({ 43 | name: 'ModuleNotFoundError', 44 | message: 'Module not found : redux', 45 | webpackError: { 46 | dependencies: [ new RequireContextDependency({ request: 'redux', regExp: {} }) ] 47 | } 48 | }).message).toEqual(message) 49 | }) 50 | -------------------------------------------------------------------------------- /test/unit/utils/utils.spec.js: -------------------------------------------------------------------------------- 1 | const utils = require('../../../src/utils') 2 | 3 | const concat = utils.concat 4 | const uniqueBy = utils.uniqueBy 5 | 6 | it('concat should concat removing undefined and null values', () => { 7 | const result = concat(1, undefined, '', null) 8 | expect(result).toEqual( 9 | [1, ''] 10 | ) 11 | }) 12 | 13 | it('concat should handle arrays', () => { 14 | const result = concat(1, [2, 3], null) 15 | expect(result).toEqual( 16 | [1, 2, 3] 17 | ) 18 | }) 19 | 20 | it('uniqueBy should dedupe based on criterion returned from iteratee function', () => { 21 | const result = uniqueBy([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 3 }], function (val) { 22 | return val.id 23 | }) 24 | expect(result).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]) 25 | }) 26 | -------------------------------------------------------------------------------- /test/utils/index.js: -------------------------------------------------------------------------------- 1 | const stripAnsi = require('strip-ansi') 2 | 3 | module.exports = { 4 | captureReports: async (output, callback) => { 5 | output.log = jest.fn() 6 | output.clearConsole = jest.fn() 7 | const logs = [] 8 | 9 | await callback() 10 | 11 | for (const args of output.log.mock.calls) { 12 | logs.push(stripAnsi(args.join(' ')).trim()) 13 | } 14 | return logs 15 | } 16 | } 17 | --------------------------------------------------------------------------------