├── .gitignore ├── test ├── fixtures │ ├── good │ │ ├── dep1.js │ │ ├── entry.js │ │ └── dep2.js │ ├── bad-syntax-entry.js │ ├── bad-syntax-dep │ │ ├── dep1.js │ │ └── entry.js │ ├── missing-dep-dep │ │ ├── entry.js │ │ └── dep1.js │ └── missing-dep-entry.js └── errorify-test.js ├── .travis.yml ├── package.json ├── README.md └── errorify.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules 3 | -------------------------------------------------------------------------------- /test/fixtures/good/dep1.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dep2: require('./dep2') 3 | }; 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - "iojs" 6 | -------------------------------------------------------------------------------- /test/fixtures/bad-syntax-entry.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | dep1: require('./dep1') 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/bad-syntax-dep/dep1.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | dep2: require('./dep2') 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/bad-syntax-dep/entry.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dep1: require('./dep1') 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/missing-dep-dep/entry.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dep1: require('./dep1') 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/missing-dep-dep/dep1.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dep2: require('./a/b/c/d/e') 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/missing-dep-entry.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dep: require('./a/b/c/d/e/f') 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/good/entry.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dep1: require('./dep1'), 3 | dep2: require('./dep2') 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/good/dep2.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | identity: function(thing) { 3 | return thing; 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "errorify", 3 | "version": "0.3.1", 4 | "description": "Browserify plugin to write failed build error messages to the output file", 5 | "keywords": [ 6 | "build", 7 | "browserify", 8 | "browserify-plugin" 9 | ], 10 | "homepage": "https://github.com/zertosh/errorify", 11 | "license": "MIT", 12 | "author": "Andres Suarez ", 13 | "files": [ 14 | "README.md", 15 | "errorify.js" 16 | ], 17 | "main": "errorify.js", 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/zertosh/errorify.git" 21 | }, 22 | "scripts": { 23 | "test": "tap test/*.js" 24 | }, 25 | "devDependencies": { 26 | "browserify": "^11.0.1", 27 | "concat-stream": "^1.4.7", 28 | "tap": "^1.3.2", 29 | "through2": "^0.6.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # errorify 2 | 3 | A [browserify](https://github.com/substack/node-browserify) plugin that writes the error message of a failed build to the output file, rendering it in the browser. 4 | 5 | [![Build Status](https://travis-ci.org/zertosh/errorify.svg?branch=master&style=flat)](https://travis-ci.org/zertosh/errorify) 6 | 7 | ## Example 8 | 9 | ```sh 10 | watchify index.js -o bundle.js -p errorify 11 | ``` 12 | 13 | After adding the plugin to your `browserify` instance, `errorify` prevents `bundle()` from emitting `error`'s. All errors are trapped, including: invalid syntax in the source, a missing dependency, a failed transform, etc. When the error message is written to the output file, it is written to the DOM in a `
` tag (or `console.error` if we are not in a browser environment). 
14 | 
15 | During development, it might look like this: 
16 | 
17 | ![es6](http://i.imgur.com/Pen6bYu.png)
18 | 
19 | Only the `bundle()` stream is rewritten. If you pass in a callback, it'll get the expected `err` and `body` arguments.
20 | 
21 | `errorify` is meant to be used with something like [watchify](https://github.com/substack/watchify). It saves you a trip to the terminal to see why a build failed.
22 | 
23 | Keep in mind that since errors are no longer emitted, all builds appear "successful". Careful not to deploy broken code.
24 | 
25 | _Note: Only tested with Browserify 9+_
26 | 
27 | ## Usage
28 | 
29 | ### API
30 | 
31 | ```js
32 | var browserify = require('browserify');
33 | var errorify = require('errorify');
34 | var b = browserify({ /*...*/ });
35 | b.plugin(errorify, /* errorify options */);
36 | ```
37 | 
38 | #### Options
39 | 
40 | * `replacer` _(optional)_ is a function that takes an error as its first argument, and returns a string that will be used as the output bundle.
41 | 
42 | ### CLI
43 | 
44 | After installing `errorify` as a local devDependency, you can use the `--plugin` or `-p` option like so:
45 | 
46 | ```sh
47 | watchify index.js -o bundle.js -p errorify
48 | ```
49 | 
50 | ### CSS Customization
51 | 
52 | The added `
` tag has the class name `errorify`, so you can customize errors in your page like so:
53 | 
54 | ```css
55 | body > .errorify {
56 |   color: red;
57 |   font-family: 'Consolas', monospace;
58 |   padding: 5px 10px;
59 | }
60 | ```
61 | 
62 | ## License
63 | 
64 | MIT.
65 | 


--------------------------------------------------------------------------------
/errorify.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var stream = require('stream');
 4 | 
 5 | module.exports = function errorify(b, opts) {
 6 |   var bundle = b.bundle;
 7 |   var replacer = opts && opts.replacer || defaultReplacer;
 8 |   b.bundle = function(cb) {
 9 |     var output = new stream.Transform();
10 |     output._transform = function(chunk, enc, callback) {
11 |       callback(null, chunk);
12 |     };
13 |     var pipeline = bundle.call(b, cb);
14 |     pipeline.on('error', function(err) {
15 |       // module-deps likes to emit each error
16 |       console.error('errorify: %s', err);
17 |     });
18 |     pipeline.once('error', function(err) {
19 |       output.push(replacer(err));
20 |       output.push(null);
21 |       pipeline.unpipe(output);
22 |     });
23 |     pipeline.pipe(output);
24 |     return output;
25 |   };
26 | };
27 | 
28 | // https://github.com/sindresorhus/ansi-regex/blob/11423e1/index.js
29 | var ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
30 | 
31 | var template = function(error) {
32 |   console.error(error);
33 |   if (typeof document === 'undefined') {
34 |     return;
35 |   } else if (!document.body) {
36 |     document.addEventListener('DOMContentLoaded', print);
37 |   } else {
38 |     print();
39 |   }
40 |   function print() {
41 |     var pre = document.createElement('pre');
42 |     pre.className = 'errorify';
43 |     pre.textContent = error.message || error;
44 |     if (document.body.firstChild) {
45 |       document.body.insertBefore(pre, document.body.firstChild);
46 |     } else {
47 |       document.body.appendChild(pre);
48 |     }
49 |   }
50 | }.toString();
51 | 
52 | function normalizeError(err) {
53 |   var result = {};
54 |   [
55 |     'message',
56 |     'line',
57 |     'lineNumber',
58 |     'column',
59 |     'columnNumber',
60 |     'name',
61 |     'stack',
62 |     'fileName'
63 |   ].forEach(function(key) {
64 |     var val;
65 |     if (key === 'message' && err.codeFrame) {
66 |       val = err.message + '\n\n' + err.codeFrame; //babelify@6.x
67 |     } else if (key === 'message') {
68 |       val = err.annotated || err.message; //babelify@5.x and browserify
69 |     } else {
70 |       val = err[key];
71 |     }
72 | 
73 |     if (typeof val === 'number') {
74 |       result[key] = val;
75 |     } else if (typeof val !== 'undefined') {
76 |       result[key] = String(val).replace(ansiRegex, '');
77 |     }
78 |   });
79 | 
80 |   return result;
81 | }
82 | 
83 | function defaultReplacer(err) {
84 |   return '!' + template + '(' + JSON.stringify(normalizeError(err)) + ')';
85 | }
86 | 


--------------------------------------------------------------------------------
/test/errorify-test.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | var browserify = require('browserify');
  4 | var concat = require('concat-stream');
  5 | var test = require('tap').test;
  6 | var through = require('through2');
  7 | var vm = require('vm');
  8 | 
  9 | // Types of Browserify errors: Bad syntax, missing dependency, or a transform error
 10 | // Error can happen in the Entry file, or in Dependency
 11 | 
 12 | test('errorify', function(t) {
 13 | 
 14 |   var errorify = require('../');
 15 | 
 16 |   var ERROR_PRELUDE_RE = /pre\.textContent = error\.message \|\| error;/;
 17 | 
 18 |   t.test('exports', function(t) {
 19 |     t.type(errorify, 'function', 'should export a function');
 20 |     t.end();
 21 |   });
 22 | 
 23 |   t.test('successful build with repeat "bundle" calls', function(t) {
 24 |     t.plan(2);
 25 |     var b = browserify();
 26 |     b.require('./test/fixtures/good/entry.js', {expose: 'entry'});
 27 |     b.plugin(errorify);
 28 |     b.bundle().pipe(concat(function(src) {
 29 |       var c = {};
 30 |       vm.runInNewContext(src, c);
 31 |       t.type(c.require('entry').dep1.dep2.identity, 'function', 'should work with pipe');
 32 |     }));
 33 |     b.bundle().pipe(concat(function(src) {
 34 |       t.notMatch(
 35 |         src.toString(),
 36 |         ERROR_PRELUDE_RE,
 37 |         'should not have error message with pipe'
 38 |       );
 39 |     }));
 40 |   });
 41 | 
 42 |   t.test('bad syntax in entry file', function(t) {
 43 |     t.plan(1);
 44 |     var b = browserify('./test/fixtures/bad-syntax-entry.js');
 45 |     b.plugin(errorify);
 46 |     b.bundle().pipe(concat(function(src) {
 47 |       t.match(
 48 |         src.toString(),
 49 |         ERROR_PRELUDE_RE,
 50 |         'should have error message when there is a syntax error in the entry'
 51 |       );
 52 |     }));
 53 |   });
 54 | 
 55 |   t.test('bad syntax in dep file', function(t) {
 56 |     t.plan(1);
 57 |     var b = browserify('./test/fixtures/bad-syntax-dep/entry.js');
 58 |     b.plugin(errorify);
 59 |     b.bundle().pipe(concat(function(src) {
 60 |       t.match(
 61 |         src.toString(),
 62 |         ERROR_PRELUDE_RE,
 63 |         'should have error message when there is a syntax error in dep'
 64 |       );
 65 |     }));
 66 |   });
 67 | 
 68 |   t.test('missing dependency in entry file', function(t) {
 69 |     t.plan(1);
 70 |     var b = browserify('./test/fixtures/missing-dep-entry.js');
 71 |     b.plugin(errorify);
 72 |     b.bundle().pipe(concat(function(src) {
 73 |       t.match(
 74 |         src.toString(),
 75 |         ERROR_PRELUDE_RE,
 76 |         'should have error message when entry is missing a dep'
 77 |       );
 78 |     }));
 79 |   });
 80 | 
 81 |   t.test('missing dependency in dependency file', function(t) {
 82 |     t.plan(1);
 83 |     var b = browserify('./test/fixtures/missing-dep-dep/entry.js');
 84 |     b.plugin(errorify);
 85 |     b.bundle().pipe(concat(function(src) {
 86 |       t.match(
 87 |         src.toString(),
 88 |         ERROR_PRELUDE_RE,
 89 |         'should have error message when dep is missing a dep'
 90 |       );
 91 |     }));
 92 |   });
 93 | 
 94 |   t.test('error in transform', function(t) {
 95 |     t.plan(1);
 96 |     var b = browserify('./test/fixtures/good/entry.js');
 97 |     b.transform(function() {
 98 |       return through(function(chunk, enc, cb) {
 99 |         this.emit('error', new Error());
100 |         cb();
101 |       });
102 |     });
103 |     b.plugin(errorify);
104 |     b.bundle().pipe(concat(function(src) {
105 |       t.match(
106 |         src.toString(),
107 |         ERROR_PRELUDE_RE,
108 |         'should have error message when transform fails on entry'
109 |       );
110 |     }));
111 |   });
112 | 
113 |   t.test('error in multiple transforms', function(t) {
114 |     t.plan(4);
115 |     var b = browserify('./test/fixtures/good/entry.js');
116 |     b.transform(function(file) {
117 |       var str = '';
118 |       return through(function(chunk, enc, cb) {
119 |         str += chunk;
120 |         cb();
121 |       }, function(cb) {
122 |         if (file.indexOf('good/entry.js') !== -1) {
123 |           this.push(str);
124 |         } else {
125 |           this.emit('error', new Error());
126 |         }
127 |         t.ok(file);
128 |         cb();
129 |       });
130 |     });
131 |     b.plugin(errorify);
132 |     b.bundle().pipe(concat(function(src) {
133 |       t.match(
134 |         src.toString(),
135 |         ERROR_PRELUDE_RE,
136 |         'should have error message when multiple transforms fails'
137 |       );
138 |     }));
139 |   });
140 | 
141 |   t.test('custom replacer', function(t) {
142 |     t.plan(2);
143 |     var b = browserify('./test/fixtures/bad-syntax-entry.js');
144 |     b.plugin(errorify, {
145 |       replacer: function(err) {
146 |         t.type(err, 'Error');
147 |         return 'custom text';
148 |       }
149 |     });
150 |     b.bundle().pipe(concat(function(src) {
151 |       t.match(
152 |         src.toString(),
153 |         /custom text/,
154 |         'should have used custom replacer'
155 |       );
156 |     }));
157 |   });
158 | 
159 |   t.test('error object', function(t) {
160 |     t.plan(1);
161 |     var b = browserify('./test/fixtures/good/entry.js');
162 |     b.transform(function() {
163 |       return through(function(chunk, enc, cb) {
164 |         this.emit('error', new Error('custom text'));
165 |         cb();
166 |       });
167 |     });
168 |     b.plugin(errorify);
169 |     b.bundle().pipe(concat(function(src) {
170 |       t.match(
171 |         src.toString(),
172 |         /custom text/,
173 |         'should have error message'
174 |       );
175 |     }));
176 |   });
177 | 
178 |   t.end();
179 | });
180 | 


--------------------------------------------------------------------------------