├── .gitignore ├── .npmrc ├── example └── files │ ├── two.js │ ├── main.js │ └── one.js ├── test ├── zzz.js ├── api.js ├── api_implicit_cache.js ├── api_brfs.js ├── bin.js ├── bin_standalone.js ├── errors.js ├── bin_pipe.js ├── bin_plugins_pipelining_multiple_errors.js ├── bin_brfs.js ├── api_ignore_watch.js ├── api_ignore_watch_default.js ├── api_ignore_watch_multiple.js ├── expose.js ├── bin_ignore_watch_default.js ├── bin_ignore_watch.js ├── bin_ignore_watch_multiple.js ├── errors_transform.js ├── many_immediate.js └── many.js ├── CHANGELOG.md ├── bin ├── args.js └── cmd.js ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── package.json ├── index.js └── readme.markdown /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /example/files/two.js: -------------------------------------------------------------------------------- 1 | module.exports = function (n) { return n * 11 }; 2 | -------------------------------------------------------------------------------- /example/files/main.js: -------------------------------------------------------------------------------- 1 | var one = require('./one'); 2 | console.log(one(5)); 3 | -------------------------------------------------------------------------------- /example/files/one.js: -------------------------------------------------------------------------------- 1 | var two = require('./two'); 2 | 3 | module.exports = function (x) { return x * two(x + 5) }; 4 | -------------------------------------------------------------------------------- /test/zzz.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | 3 | test('__END__', function (t) { 4 | t.on('end', function () { 5 | setTimeout(function () { 6 | process.exit(0); 7 | }, 100) 8 | }); 9 | t.end(); 10 | }); 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # watchify Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## 4.0.0 6 | * Update dependencies. 7 | 8 | This release drops support for Node.js versions older than 8.10, following the upgrade to `chokidar` 3.x. 9 | -------------------------------------------------------------------------------- /bin/args.js: -------------------------------------------------------------------------------- 1 | var fromArgs = require('browserify/bin/args'); 2 | var watchify = require('../'); 3 | var defined = require('defined'); 4 | var xtend = require('xtend'); 5 | 6 | module.exports = function (args) { 7 | var b = fromArgs(args, watchify.args); 8 | 9 | var opts = {}; 10 | var ignoreWatch = defined(b.argv['ignore-watch'], b.argv.iw); 11 | if (ignoreWatch) { 12 | opts.ignoreWatch = ignoreWatch; 13 | } 14 | 15 | return watchify(b, xtend(opts, b.argv)); 16 | }; 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Tests 8 | strategy: 9 | matrix: 10 | node: [8.x, 10.x, 12.x, 14.x, 15.x] 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout sources 14 | uses: actions/checkout@v2 15 | - name: Install Node.js ${{matrix.node}} 16 | uses: actions/setup-node@v2-beta 17 | with: 18 | node-version: ${{matrix.node}} 19 | - name: Install dependencies 20 | run: npm install 21 | - name: npm test 22 | run: npm test 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is released under the MIT license: 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "watchify", 3 | "version": "4.0.0", 4 | "description": "watch mode for browserify builds", 5 | "main": "index.js", 6 | "bin": "bin/cmd.js", 7 | "engines": { 8 | "node": ">= 8.10.0" 9 | }, 10 | "dependencies": { 11 | "anymatch": "^3.1.0", 12 | "browserify": "^17.0.0", 13 | "chokidar": "^3.4.0", 14 | "defined": "^1.0.0", 15 | "outpipe": "^1.1.0", 16 | "through2": "^4.0.2", 17 | "xtend": "^4.0.2" 18 | }, 19 | "devDependencies": { 20 | "brfs": "^2.0.1", 21 | "mkdirp": "^0.5.5", 22 | "split": "^1.0.0", 23 | "tape": "^5.1.1", 24 | "uglify-js": "^2.5.0", 25 | "win-spawn": "^2.0.0" 26 | }, 27 | "scripts": { 28 | "test": "tape test/*.js" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git://github.com/browserify/watchify.git" 33 | }, 34 | "homepage": "https://github.com/browserify/watchify", 35 | "keywords": [ 36 | "browserify", 37 | "browserify-tool", 38 | "watch", 39 | "bundle", 40 | "build", 41 | "browser" 42 | ], 43 | "author": { 44 | "name": "James Halliday", 45 | "email": "mail@substack.net", 46 | "url": "http://substack.net" 47 | }, 48 | "license": "MIT" 49 | } 50 | -------------------------------------------------------------------------------- /test/api.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var watchify = require('../'); 3 | var browserify = require('browserify'); 4 | var vm = require('vm'); 5 | 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var mkdirp = require('mkdirp'); 9 | 10 | var os = require('os'); 11 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 12 | 13 | var file = path.join(tmpdir, 'main.js'); 14 | 15 | mkdirp.sync(tmpdir); 16 | fs.writeFileSync(file, 'console.log(555)'); 17 | 18 | test('api', function (t) { 19 | t.plan(5); 20 | var w = watchify(browserify(file, watchify.args)); 21 | w.on('update', function () { 22 | w.bundle(function (err, src) { 23 | t.ifError(err); 24 | t.equal(run(src), '333\n'); 25 | w.close(); 26 | }); 27 | }); 28 | w.bundle(function (err, src) { 29 | t.ifError(err); 30 | t.equal(run(src), '555\n'); 31 | setTimeout(function () { 32 | fs.writeFile(file, 'console.log(333)', function (err) { 33 | t.ifError(err); 34 | }); 35 | }, 1000); 36 | }); 37 | }); 38 | 39 | function run (src) { 40 | var output = ''; 41 | function log (msg) { output += msg + '\n' } 42 | vm.runInNewContext(src, { console: { log: log } }); 43 | return output; 44 | } 45 | -------------------------------------------------------------------------------- /test/api_implicit_cache.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var watchify = require('../'); 3 | var browserify = require('browserify'); 4 | var vm = require('vm'); 5 | 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var mkdirp = require('mkdirp'); 9 | 10 | var os = require('os'); 11 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 12 | 13 | var file = path.join(tmpdir, 'main.js'); 14 | 15 | mkdirp.sync(tmpdir); 16 | fs.writeFileSync(file, 'console.log(555)'); 17 | 18 | test('api implicit cache', function (t) { 19 | t.plan(5); 20 | var w = watchify(browserify(file)); 21 | w.on('update', function () { 22 | w.bundle(function (err, src) { 23 | t.ifError(err); 24 | t.equal(run(src), '333\n'); 25 | w.close(); 26 | }); 27 | }); 28 | w.bundle(function (err, src) { 29 | t.ifError(err); 30 | t.equal(run(src), '555\n'); 31 | setTimeout(function () { 32 | fs.writeFile(file, 'console.log(333)', function (err) { 33 | t.ifError(err); 34 | }); 35 | }, 1000); 36 | }); 37 | }); 38 | 39 | function run (src) { 40 | var output = ''; 41 | function log (msg) { output += msg + '\n' } 42 | vm.runInNewContext(src, { console: { log: log } }); 43 | return output; 44 | } 45 | -------------------------------------------------------------------------------- /test/api_brfs.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var watchify = require('../'); 3 | var browserify = require('browserify'); 4 | var vm = require('vm'); 5 | 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var mkdirp = require('mkdirp'); 9 | 10 | var os = require('os'); 11 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 12 | 13 | var files = { 14 | main: path.join(tmpdir, 'main.js'), 15 | lines: path.join(tmpdir, 'lines.txt') 16 | }; 17 | 18 | mkdirp.sync(tmpdir); 19 | fs.writeFileSync(files.main, [ 20 | 'var fs = require("fs");', 21 | 'var src = fs.readFileSync(__dirname + "/lines.txt", "utf8");', 22 | 'console.log(src.toUpperCase());' 23 | ].join('\n')); 24 | fs.writeFileSync(files.lines, 'beep\nboop'); 25 | 26 | test('api with brfs', function (t) { 27 | t.plan(5); 28 | var w = watchify(browserify(files.main, watchify.args)); 29 | w.transform('brfs'); 30 | w.on('update', function () { 31 | w.bundle(function (err, src) { 32 | t.ifError(err); 33 | t.equal(run(src), 'ROBO-BOOGIE\n'); 34 | w.close(); 35 | }); 36 | }); 37 | w.bundle(function (err, src) { 38 | t.ifError(err); 39 | t.equal(run(src), 'BEEP\nBOOP\n'); 40 | setTimeout(function () { 41 | fs.writeFile(files.lines, 'rObO-bOOgie', function (err) { 42 | t.ifError(err); 43 | }); 44 | }, 1000); 45 | }); 46 | }); 47 | 48 | function run (src) { 49 | var output = ''; 50 | function log (msg) { output += msg + '\n' } 51 | vm.runInNewContext(src, { console: { log: log } }); 52 | return output; 53 | } 54 | -------------------------------------------------------------------------------- /test/bin.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var mkdirp = require('mkdirp'); 5 | var spawn = require('win-spawn'); 6 | var split = require('split'); 7 | 8 | var cmd = path.resolve(__dirname, '../bin/cmd.js'); 9 | var os = require('os'); 10 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 11 | 12 | var files = { 13 | main: path.join(tmpdir, 'main.js'), 14 | bundle: path.join(tmpdir, 'bundle.js') 15 | }; 16 | 17 | mkdirp.sync(tmpdir); 18 | fs.writeFileSync(files.main, 'console.log(555)'); 19 | 20 | test('bin', function (t) { 21 | t.plan(5); 22 | var ps = spawn(cmd, [ files.main, '-o', files.bundle, '-v' ]); 23 | var lineNum = 0; 24 | ps.stderr.pipe(split()).on('data', function (line) { 25 | lineNum ++; 26 | if (lineNum === 1) { 27 | run(files.bundle, function (err, output) { 28 | t.ifError(err); 29 | t.equal(output, '555\n'); 30 | fs.writeFile(files.main, 'console.log(333)', t.ifError); 31 | }) 32 | } 33 | else if (lineNum === 2) { 34 | run(files.bundle, function (err, output) { 35 | t.ifError(err); 36 | t.equal(output, '333\n'); 37 | ps.kill(); 38 | }); 39 | } 40 | }); 41 | }); 42 | 43 | function run (file, cb) { 44 | var ps = spawn(process.execPath, [ file ]); 45 | var data = []; 46 | ps.stdout.on('data', function (buf) { data.push(buf) }); 47 | ps.stdout.on('end', function () { 48 | cb(null, Buffer.concat(data).toString('utf8')); 49 | }); 50 | ps.on('error', cb); 51 | return ps; 52 | } 53 | -------------------------------------------------------------------------------- /test/bin_standalone.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var mkdirp = require('mkdirp'); 5 | var spawn = require('win-spawn'); 6 | var split = require('split'); 7 | 8 | var cmd = path.resolve(__dirname, '../bin/cmd.js'); 9 | var os = require('os'); 10 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 11 | 12 | var files = { 13 | main: path.join(tmpdir, 'main.js'), 14 | bundle: path.join(tmpdir, 'bundle.js') 15 | }; 16 | 17 | mkdirp.sync(tmpdir); 18 | fs.writeFileSync(files.main, 'console.log(555)'); 19 | 20 | test('bin with standalone', function (t) { 21 | t.plan(5); 22 | var ps = spawn(cmd, [ files.main, '-o', files.bundle, '-v', '-s', 'XXX' ]); 23 | var lineNum = 0; 24 | ps.stderr.pipe(split()).on('data', function (line) { 25 | lineNum ++; 26 | if (lineNum === 1) { 27 | run(files.bundle, function (err, output) { 28 | t.ifError(err); 29 | t.equal(output, '555\n'); 30 | fs.writeFile(files.main, 'console.log(333)', t.ifError); 31 | }) 32 | } 33 | else if (lineNum === 2) { 34 | run(files.bundle, function (err, output) { 35 | t.ifError(err); 36 | t.equal(output, '333\n'); 37 | ps.kill(); 38 | }); 39 | } 40 | }); 41 | }); 42 | 43 | function run (file, cb) { 44 | var ps = spawn(process.execPath, [ file ]); 45 | var data = []; 46 | ps.stdout.on('data', function (buf) { data.push(buf) }); 47 | ps.stdout.on('end', function () { 48 | cb(null, Buffer.concat(data).toString('utf8')); 49 | }); 50 | ps.on('error', cb); 51 | return ps; 52 | } 53 | -------------------------------------------------------------------------------- /test/errors.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var watchify = require('../'); 3 | var browserify = require('browserify'); 4 | var vm = require('vm'); 5 | 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var mkdirp = require('mkdirp'); 9 | 10 | var os = require('os'); 11 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 12 | 13 | var file = path.join(tmpdir, 'main.js'); 14 | 15 | mkdirp.sync(tmpdir); 16 | fs.writeFileSync(file, 'console.log(555)'); 17 | 18 | test('errors', function (t) { 19 | t.plan(5); 20 | var w = watchify(browserify(file, watchify.args)); 21 | w.bundle(function (err, src) { 22 | t.ifError(err); 23 | t.equal(run(src), '555\n'); 24 | breakTheBuild(); 25 | }); 26 | function breakTheBuild() { 27 | setTimeout(function() { 28 | fs.writeFileSync(file, 'console.log('); 29 | }, 1000); 30 | w.once('update', function () { 31 | w.bundle(function (err, src) { 32 | t.ok(err instanceof Error, 'should be error'); 33 | fixTheBuild(); 34 | }); 35 | }); 36 | } 37 | function fixTheBuild() { 38 | setTimeout(function() { 39 | fs.writeFileSync(file, 'console.log(333)'); 40 | }, 1000); 41 | w.once('update', function () { 42 | w.bundle(function (err, src) { 43 | t.ifError(err); 44 | t.equal(run(src), '333\n'); 45 | w.close(); 46 | }); 47 | }); 48 | } 49 | }); 50 | 51 | function run (src) { 52 | var output = ''; 53 | function log (msg) { output += msg + '\n' } 54 | vm.runInNewContext(src, { console: { log: log } }); 55 | return output; 56 | } 57 | -------------------------------------------------------------------------------- /test/bin_pipe.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var mkdirp = require('mkdirp'); 5 | var spawn = require('win-spawn'); 6 | var split = require('split'); 7 | 8 | var cmd = path.resolve(__dirname, '../bin/cmd.js'); 9 | var os = require('os'); 10 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 11 | 12 | var files = { 13 | main: path.join(tmpdir, 'main.js'), 14 | bundle: path.join(tmpdir, 'bundle.js') 15 | }; 16 | 17 | mkdirp.sync(tmpdir); 18 | fs.writeFileSync(files.main, 'console.log(num * 2)'); 19 | 20 | test('bin with pipe', function (t) { 21 | t.plan(5); 22 | var ps = spawn(cmd, [ 23 | files.main, 24 | '-o', 'uglifyjs - --enclose 11:num > ' + files.bundle, 25 | '-v' 26 | ]); 27 | var lineNum = 0; 28 | ps.stderr.pipe(split()).on('data', function (line) { 29 | lineNum ++; 30 | if (lineNum === 1) { 31 | run(files.bundle, function (err, output) { 32 | t.ifError(err); 33 | t.equal(output, '22\n'); 34 | fs.writeFile(files.main, 'console.log(num * 3)', t.ifError); 35 | }); 36 | } 37 | else if (lineNum === 2) { 38 | run(files.bundle, function (err, output) { 39 | t.ifError(err); 40 | t.equal(output, '33\n'); 41 | ps.kill(); 42 | }); 43 | } 44 | }); 45 | }); 46 | 47 | function run (file, cb) { 48 | var ps = spawn(process.execPath, [ file ]); 49 | var data = []; 50 | ps.stdout.on('data', function (buf) { data.push(buf) }); 51 | ps.stdout.on('end', function () { 52 | cb(null, Buffer.concat(data).toString('utf8')); 53 | }); 54 | ps.on('error', cb); 55 | return ps; 56 | } 57 | -------------------------------------------------------------------------------- /test/bin_plugins_pipelining_multiple_errors.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var mkdirp = require('mkdirp'); 5 | var spawn = require('win-spawn'); 6 | var split = require('split'); 7 | 8 | var cmd = path.resolve(__dirname, '../bin/cmd.js'); 9 | var os = require('os'); 10 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 11 | 12 | var files = { 13 | main: path.join(tmpdir, 'main.js'), 14 | plugin: path.join(tmpdir, 'plugin.js'), 15 | bundle: path.join(tmpdir, 'bundle.js') 16 | }; 17 | 18 | mkdirp.sync(tmpdir); 19 | fs.writeFileSync(files.plugin, [ 20 | 'module.exports = function(b, opts) {', 21 | ' b.on("file", function (file, id) {', 22 | ' b.pipeline.emit("error", "bad boop");', 23 | ' b.pipeline.emit("error", "bad boop");', 24 | ' });', 25 | '};', 26 | ].join('\n')); 27 | fs.writeFileSync(files.main, 'boop\nbeep'); 28 | 29 | test('bin plugins pipelining multiple errors', function (t) { 30 | t.plan(4); 31 | var ps = spawn(cmd, [ 32 | files.main, 33 | '-p', files.plugin, '-v', 34 | '-o', files.bundle 35 | ]); 36 | var lineNum = 0; 37 | ps.stderr.pipe(split()).on('data', function (line) { 38 | lineNum ++; 39 | if (lineNum === 1) { 40 | t.equal(line, 'bad boop'); 41 | } 42 | if (lineNum === 2) { 43 | t.equal(line, 'bad boop'); 44 | setTimeout(function() { 45 | fs.writeFileSync(files.main, 'beep\nboop'); 46 | }, 1000); 47 | } 48 | if (lineNum === 3) { 49 | t.equal(line, 'bad boop'); 50 | } 51 | if (lineNum === 4) { 52 | t.equal(line, 'bad boop'); 53 | ps.kill(); 54 | } 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/bin_brfs.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var mkdirp = require('mkdirp'); 5 | var spawn = require('win-spawn'); 6 | var split = require('split'); 7 | 8 | var cmd = path.resolve(__dirname, '../bin/cmd.js'); 9 | var os = require('os'); 10 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 11 | 12 | var files = { 13 | main: path.join(tmpdir, 'main.js'), 14 | lines: path.join(tmpdir, 'lines.txt'), 15 | bundle: path.join(tmpdir, 'bundle.js') 16 | }; 17 | 18 | mkdirp.sync(tmpdir); 19 | fs.writeFileSync(files.main, [ 20 | 'var fs = require("fs");', 21 | 'var src = fs.readFileSync(__dirname + "/lines.txt", "utf8");', 22 | 'console.log(src.toUpperCase());' 23 | ].join('\n')); 24 | fs.writeFileSync(files.lines, 'beep\nboop'); 25 | 26 | test('bin brfs', function (t) { 27 | t.plan(5); 28 | var ps = spawn(cmd, [ 29 | files.main, 30 | '-t', require.resolve('brfs'), '-v', 31 | '-o', files.bundle 32 | ]); 33 | var lineNum = 0; 34 | ps.stderr.pipe(split()).on('data', function (line) { 35 | lineNum ++; 36 | if (lineNum === 1) { 37 | run(files.bundle, function (err, output) { 38 | t.ifError(err); 39 | t.equal(output, 'BEEP\nBOOP\n'); 40 | fs.writeFile(files.lines, 'robo-bOOgie', t.ifError); 41 | }) 42 | } 43 | else if (lineNum === 2) { 44 | run(files.bundle, function (err, output) { 45 | t.ifError(err); 46 | t.equal(output, 'ROBO-BOOGIE\n'); 47 | ps.kill(); 48 | }); 49 | } 50 | }); 51 | }); 52 | 53 | function run (file, cb) { 54 | var ps = spawn(process.execPath, [ file ]); 55 | var data = []; 56 | ps.stdout.on('data', function (buf) { data.push(buf) }); 57 | ps.stdout.on('end', function () { 58 | cb(null, Buffer.concat(data).toString('utf8')); 59 | }); 60 | ps.on('error', cb); 61 | return ps; 62 | } 63 | -------------------------------------------------------------------------------- /test/api_ignore_watch.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var watchify = require('../'); 3 | var browserify = require('browserify'); 4 | var vm = require('vm'); 5 | 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var mkdirp = require('mkdirp'); 9 | 10 | var os = require('os'); 11 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 12 | 13 | var files = { 14 | main: path.join(tmpdir, 'main.js'), 15 | beep: path.join(tmpdir, 'beep.js'), 16 | boop: path.join(tmpdir, 'boop.js'), 17 | robot: path.join(tmpdir, 'node_modules', 'robot', 'index.js') 18 | }; 19 | 20 | mkdirp.sync(tmpdir); 21 | mkdirp.sync(path.dirname(files.robot)); 22 | fs.writeFileSync(files.main, [ 23 | 'var beep = require("./beep");', 24 | 'var boop = require("./boop");', 25 | 'var robot = require("robot");', 26 | 'console.log(beep + " " + boop + " " + robot);' 27 | ].join('\n')); 28 | fs.writeFileSync(files.beep, 'module.exports = "beep";'); 29 | fs.writeFileSync(files.boop, 'module.exports = "boop";'); 30 | fs.writeFileSync(files.robot, 'module.exports = "robot";'); 31 | 32 | test('api ignore watch', function (t) { 33 | t.plan(4); 34 | var w = watchify(browserify(files.main, watchify.args), { 35 | ignoreWatch: '**/be*.js' 36 | }); 37 | w.on('update', function () { 38 | w.bundle(function (err, src) { 39 | t.ifError(err); 40 | t.equal(run(src), 'beep BOOP ROBOT\n'); 41 | w.close(); 42 | }); 43 | }); 44 | w.bundle(function (err, src) { 45 | t.ifError(err); 46 | t.equal(run(src), 'beep boop robot\n'); 47 | setTimeout(function () { 48 | fs.writeFileSync(files.beep, 'module.exports = "BEEP";'); 49 | fs.writeFileSync(files.boop, 'module.exports = "BOOP";'); 50 | fs.writeFileSync(files.robot, 'module.exports = "ROBOT";'); 51 | }, 1000); 52 | }); 53 | }); 54 | 55 | function run (src) { 56 | var output = ''; 57 | function log (msg) { output += msg + '\n' } 58 | vm.runInNewContext(src, { console: { log: log } }); 59 | return output; 60 | } 61 | -------------------------------------------------------------------------------- /test/api_ignore_watch_default.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var watchify = require('../'); 3 | var browserify = require('browserify'); 4 | var vm = require('vm'); 5 | 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var mkdirp = require('mkdirp'); 9 | 10 | var os = require('os'); 11 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 12 | 13 | var files = { 14 | main: path.join(tmpdir, 'main.js'), 15 | beep: path.join(tmpdir, 'beep.js'), 16 | boop: path.join(tmpdir, 'boop.js'), 17 | robot: path.join(tmpdir, 'node_modules', 'robot', 'index.js') 18 | }; 19 | 20 | mkdirp.sync(tmpdir); 21 | mkdirp.sync(path.dirname(files.robot)); 22 | fs.writeFileSync(files.main, [ 23 | 'var beep = require("./beep");', 24 | 'var boop = require("./boop");', 25 | 'var robot = require("robot");', 26 | 'console.log(beep + " " + boop + " " + robot);' 27 | ].join('\n')); 28 | fs.writeFileSync(files.beep, 'module.exports = "beep";'); 29 | fs.writeFileSync(files.boop, 'module.exports = "boop";'); 30 | fs.writeFileSync(files.robot, 'module.exports = "robot";'); 31 | 32 | test('api ignore watch default', function (t) { 33 | t.plan(4); 34 | var w = watchify(browserify(files.main, watchify.args), { 35 | ignoreWatch: true 36 | }); 37 | w.on('update', function () { 38 | w.bundle(function (err, src) { 39 | t.ifError(err); 40 | t.equal(run(src), 'BEEP BOOP robot\n'); 41 | w.close(); 42 | }); 43 | }); 44 | w.bundle(function (err, src) { 45 | t.ifError(err); 46 | t.equal(run(src), 'beep boop robot\n'); 47 | setTimeout(function () { 48 | fs.writeFileSync(files.beep, 'module.exports = "BEEP";'); 49 | fs.writeFileSync(files.boop, 'module.exports = "BOOP";'); 50 | fs.writeFileSync(files.robot, 'module.exports = "ROBOT";'); 51 | }, 1000); 52 | }); 53 | }); 54 | 55 | function run (src) { 56 | var output = ''; 57 | function log (msg) { output += msg + '\n' } 58 | vm.runInNewContext(src, { console: { log: log } }); 59 | return output; 60 | } 61 | -------------------------------------------------------------------------------- /bin/cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var outpipe = require('outpipe'); 5 | var through = require('through2'); 6 | 7 | var fromArgs = require('./args.js'); 8 | var w = fromArgs(process.argv.slice(2)); 9 | 10 | var outfile = w.argv.o || w.argv.outfile; 11 | var verbose = w.argv.v || w.argv.verbose; 12 | 13 | if (w.argv.version) { 14 | console.error('watchify v' + require('../package.json').version + 15 | ' (in ' + path.resolve(__dirname, '..') + ')' 16 | ); 17 | console.error('browserify v' + require('browserify/package.json').version + 18 | ' (in ' + path.dirname(require.resolve('browserify')) + ')' 19 | ); 20 | return; 21 | } 22 | 23 | if (!outfile) { 24 | console.error('You MUST specify an outfile with -o.'); 25 | process.exit(1); 26 | } 27 | 28 | var bytes, time; 29 | w.on('bytes', function (b) { bytes = b }); 30 | w.on('time', function (t) { time = t }); 31 | 32 | w.on('update', bundle); 33 | bundle(); 34 | 35 | function bundle () { 36 | var didError = false; 37 | var writer = through(); 38 | var wb = w.bundle(); 39 | 40 | w.pipeline.get('pack').once('readable', function() { 41 | if (!didError) { 42 | wb.pipe(writer); 43 | } 44 | }); 45 | 46 | wb.on('error', function (err) { 47 | console.error(String(err)); 48 | if (!didError) { 49 | didError = true; 50 | writer.end('console.error(' + JSON.stringify(String(err)) + ');'); 51 | } 52 | }); 53 | 54 | writer.once('readable', function() { 55 | var outStream = outpipe(outfile); 56 | outStream.on('error', function (err) { 57 | console.error(err); 58 | }); 59 | outStream.on('exit', function () { 60 | if (verbose && !didError) { 61 | console.error(bytes + ' bytes written to ' + outfile 62 | + ' (' + (time / 1000).toFixed(2) + ' seconds) at ' 63 | + new Date().toLocaleTimeString() 64 | ); 65 | } 66 | }); 67 | writer.pipe(outStream); 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /test/api_ignore_watch_multiple.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var watchify = require('../'); 3 | var browserify = require('browserify'); 4 | var vm = require('vm'); 5 | 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var mkdirp = require('mkdirp'); 9 | 10 | var os = require('os'); 11 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 12 | 13 | var files = { 14 | main: path.join(tmpdir, 'main.js'), 15 | beep: path.join(tmpdir, 'beep.js'), 16 | boop: path.join(tmpdir, 'boop.js'), 17 | robot: path.join(tmpdir, 'node_modules', 'robot', 'index.js') 18 | }; 19 | 20 | mkdirp.sync(tmpdir); 21 | mkdirp.sync(path.dirname(files.robot)); 22 | fs.writeFileSync(files.main, [ 23 | 'var beep = require("./beep");', 24 | 'var boop = require("./boop");', 25 | 'var robot = require("robot");', 26 | 'console.log(beep + " " + boop + " " + robot);' 27 | ].join('\n')); 28 | fs.writeFileSync(files.beep, 'module.exports = "beep";'); 29 | fs.writeFileSync(files.boop, 'module.exports = "boop";'); 30 | fs.writeFileSync(files.robot, 'module.exports = "robot";'); 31 | 32 | test('api ignore watch multiple paths', function (t) { 33 | t.plan(4); 34 | var w = watchify(browserify(files.main, watchify.args), { 35 | ignoreWatch: ['**/be*.js', '**/robot/*.js'] 36 | }); 37 | w.on('update', function () { 38 | w.bundle(function (err, src) { 39 | t.ifError(err); 40 | t.equal(run(src), 'beep BOOP robot\n'); 41 | w.close(); 42 | }); 43 | }); 44 | w.bundle(function (err, src) { 45 | t.ifError(err); 46 | t.equal(run(src), 'beep boop robot\n'); 47 | setTimeout(function () { 48 | fs.writeFileSync(files.beep, 'module.exports = "BEEP";'); 49 | fs.writeFileSync(files.boop, 'module.exports = "BOOP";'); 50 | fs.writeFileSync(files.robot, 'module.exports = "ROBOT";'); 51 | }, 1000); 52 | }); 53 | }); 54 | 55 | function run (src) { 56 | var output = ''; 57 | function log (msg) { output += msg + '\n' } 58 | vm.runInNewContext(src, { console: { log: log } }); 59 | return output; 60 | } 61 | -------------------------------------------------------------------------------- /test/expose.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var watchify = require('../'); 3 | var browserify = require('browserify'); 4 | var vm = require('vm'); 5 | 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var mkdirp = require('mkdirp'); 9 | 10 | var os = require('os'); 11 | var tmpbase = fs.realpathSync((os.tmpdir || os.tmpDir)()); 12 | var tmpdir = path.join(tmpbase, 'watchify-' + Math.random()); 13 | 14 | var files = { 15 | main: path.join(tmpdir, 'main.js'), 16 | beep: path.join(tmpdir, 'beep.js'), 17 | boop: path.join(tmpdir, 'boop.js'), 18 | abc: path.join(tmpdir, 'lib', 'abc.js'), 19 | xyz: path.join(tmpdir, 'lib', 'xyz.js') 20 | }; 21 | 22 | mkdirp.sync(tmpdir); 23 | mkdirp.sync(path.join(tmpdir, 'lib')); 24 | 25 | fs.writeFileSync(files.main, [ 26 | 'var abc = require("abc");', 27 | 'var xyz = require("xyz");', 28 | 'var beep = require("./beep");', 29 | 'console.log(abc + " " + xyz + " " + beep);' 30 | ].join('\n')); 31 | fs.writeFileSync(files.beep, 'module.exports = require("./boop");'); 32 | fs.writeFileSync(files.boop, 'module.exports = require("xyz");'); 33 | fs.writeFileSync(files.abc, 'module.exports = "abc";'); 34 | fs.writeFileSync(files.xyz, 'module.exports = "xyz";'); 35 | 36 | test('properly caches exposed files', function (t) { 37 | t.plan(4); 38 | var cache = {}; 39 | var w = watchify(browserify({ 40 | entries: [files.main], 41 | basedir: tmpdir, 42 | cache: cache, 43 | packageCache: {} 44 | })); 45 | 46 | w.require('./lib/abc', {expose: 'abc'}); 47 | w.require('./lib/xyz', {expose: 'xyz'}); 48 | w.on('update', function () { 49 | w.bundle(function (err, src) { 50 | t.ifError(err); 51 | t.equal(run(src), 'ABC XYZ XYZ\n'); 52 | w.close(); 53 | }); 54 | }); 55 | w.bundle(function (err, src) { 56 | t.ifError(err); 57 | t.equal(run(src), 'abc xyz xyz\n'); 58 | setTimeout(function () { 59 | // If we're incorrectly caching exposed files, 60 | // then "files.abc" would be re-read from disk. 61 | cache[files.abc].source = 'module.exports = "ABC";'; 62 | fs.writeFileSync(files.xyz, 'module.exports = "XYZ";'); 63 | }, 1000); 64 | }); 65 | }); 66 | 67 | function run (src) { 68 | var output = ''; 69 | function log (msg) { output += msg + '\n' } 70 | vm.runInNewContext(src, { console: { log: log } }); 71 | return output; 72 | } 73 | -------------------------------------------------------------------------------- /test/bin_ignore_watch_default.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var mkdirp = require('mkdirp'); 5 | var spawn = require('win-spawn'); 6 | var split = require('split'); 7 | 8 | var cmd = path.resolve(__dirname, '../bin/cmd.js'); 9 | var os = require('os'); 10 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 11 | 12 | var files = { 13 | main: path.join(tmpdir, 'main.js'), 14 | beep: path.join(tmpdir, 'beep.js'), 15 | boop: path.join(tmpdir, 'boop.js'), 16 | robot: path.join(tmpdir, 'node_modules', 'robot', 'index.js'), 17 | bundle: path.join(tmpdir, 'bundle.js') 18 | }; 19 | 20 | mkdirp.sync(tmpdir); 21 | mkdirp.sync(path.dirname(files.robot)); 22 | fs.writeFileSync(files.main, [ 23 | 'var beep = require("./beep");', 24 | 'var boop = require("./boop");', 25 | 'var robot = require("robot");', 26 | 'console.log(beep + " " + boop + " " + robot);' 27 | ].join('\n')); 28 | fs.writeFileSync(files.beep, 'module.exports = "beep";'); 29 | fs.writeFileSync(files.boop, 'module.exports = "boop";'); 30 | fs.writeFileSync(files.robot, 'module.exports = "robot";'); 31 | 32 | test('api ignore watch', function (t) { 33 | t.plan(4); 34 | var ps = spawn(cmd, [ 35 | files.main, 36 | '--ignore-watch', 37 | '-o', files.bundle, 38 | '-v' 39 | ]); 40 | var lineNum = 0; 41 | ps.stderr.pipe(split()).on('data', function (line) { 42 | lineNum ++; 43 | if (lineNum === 1) { 44 | run(files.bundle, function (err, output) { 45 | t.ifError(err); 46 | t.equal(output, 'beep boop robot\n'); 47 | fs.writeFileSync(files.beep, 'module.exports = "BEEP";'); 48 | fs.writeFileSync(files.boop, 'module.exports = "BOOP";'); 49 | fs.writeFileSync(files.robot, 'module.exports = "ROBOT";'); 50 | }); 51 | } 52 | else if (lineNum === 2) { 53 | run(files.bundle, function (err, output) { 54 | t.ifError(err); 55 | t.equal(output, 'BEEP BOOP robot\n'); 56 | ps.kill(); 57 | }); 58 | } 59 | }); 60 | }); 61 | 62 | function run (file, cb) { 63 | var ps = spawn(process.execPath, [ file ]); 64 | var data = []; 65 | ps.stdout.on('data', function (buf) { data.push(buf) }); 66 | ps.stdout.on('end', function () { 67 | cb(null, Buffer.concat(data).toString('utf8')); 68 | }); 69 | ps.on('error', cb); 70 | return ps; 71 | } 72 | -------------------------------------------------------------------------------- /test/bin_ignore_watch.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var mkdirp = require('mkdirp'); 5 | var spawn = require('win-spawn'); 6 | var split = require('split'); 7 | 8 | var cmd = path.resolve(__dirname, '../bin/cmd.js'); 9 | var os = require('os'); 10 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 11 | 12 | var files = { 13 | main: path.join(tmpdir, 'main.js'), 14 | beep: path.join(tmpdir, 'beep.js'), 15 | boop: path.join(tmpdir, 'boop.js'), 16 | robot: path.join(tmpdir, 'node_modules', 'robot', 'index.js'), 17 | bundle: path.join(tmpdir, 'bundle.js') 18 | }; 19 | 20 | mkdirp.sync(tmpdir); 21 | mkdirp.sync(path.dirname(files.robot)); 22 | fs.writeFileSync(files.main, [ 23 | 'var beep = require("./beep");', 24 | 'var boop = require("./boop");', 25 | 'var robot = require("robot");', 26 | 'console.log(beep + " " + boop + " " + robot);' 27 | ].join('\n')); 28 | fs.writeFileSync(files.beep, 'module.exports = "beep";'); 29 | fs.writeFileSync(files.boop, 'module.exports = "boop";'); 30 | fs.writeFileSync(files.robot, 'module.exports = "robot";'); 31 | 32 | test('api ignore watch', function (t) { 33 | t.plan(4); 34 | var ps = spawn(cmd, [ 35 | files.main, 36 | '--ignore-watch', '**/be*.js', 37 | '-o', files.bundle, 38 | '-v' 39 | ]); 40 | var lineNum = 0; 41 | ps.stderr.pipe(split()).on('data', function (line) { 42 | lineNum ++; 43 | if (lineNum === 1) { 44 | run(files.bundle, function (err, output) { 45 | t.ifError(err); 46 | t.equal(output, 'beep boop robot\n'); 47 | fs.writeFileSync(files.beep, 'module.exports = "BEEP";'); 48 | fs.writeFileSync(files.boop, 'module.exports = "BOOP";'); 49 | fs.writeFileSync(files.robot, 'module.exports = "ROBOT";'); 50 | }); 51 | } 52 | else if (lineNum === 2) { 53 | run(files.bundle, function (err, output) { 54 | t.ifError(err); 55 | t.equal(output, 'beep BOOP ROBOT\n'); 56 | ps.kill(); 57 | }); 58 | } 59 | }); 60 | }); 61 | 62 | function run (file, cb) { 63 | var ps = spawn(process.execPath, [ file ]); 64 | var data = []; 65 | ps.stdout.on('data', function (buf) { data.push(buf) }); 66 | ps.stdout.on('end', function () { 67 | cb(null, Buffer.concat(data).toString('utf8')); 68 | }); 69 | ps.on('error', cb); 70 | return ps; 71 | } 72 | -------------------------------------------------------------------------------- /test/bin_ignore_watch_multiple.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var mkdirp = require('mkdirp'); 5 | var spawn = require('win-spawn'); 6 | var split = require('split'); 7 | 8 | var cmd = path.resolve(__dirname, '../bin/cmd.js'); 9 | var os = require('os'); 10 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 11 | 12 | var files = { 13 | main: path.join(tmpdir, 'main.js'), 14 | beep: path.join(tmpdir, 'beep.js'), 15 | boop: path.join(tmpdir, 'boop.js'), 16 | robot: path.join(tmpdir, 'node_modules', 'robot', 'index.js'), 17 | bundle: path.join(tmpdir, 'bundle.js') 18 | }; 19 | 20 | mkdirp.sync(tmpdir); 21 | mkdirp.sync(path.dirname(files.robot)); 22 | fs.writeFileSync(files.main, [ 23 | 'var beep = require("./beep");', 24 | 'var boop = require("./boop");', 25 | 'var robot = require("robot");', 26 | 'console.log(beep + " " + boop + " " + robot);' 27 | ].join('\n')); 28 | fs.writeFileSync(files.beep, 'module.exports = "beep";'); 29 | fs.writeFileSync(files.boop, 'module.exports = "boop";'); 30 | fs.writeFileSync(files.robot, 'module.exports = "robot";'); 31 | 32 | test('api ignore watch multiple paths', function (t) { 33 | t.plan(4); 34 | var ps = spawn(cmd, [ 35 | files.main, 36 | '--ignore-watch', '**/be*.js', 37 | '--ignore-watch', '**/robot/*.js', 38 | '-o', files.bundle, 39 | '-v' 40 | ]); 41 | var lineNum = 0; 42 | ps.stderr.pipe(split()).on('data', function (line) { 43 | lineNum ++; 44 | if (lineNum === 1) { 45 | run(files.bundle, function (err, output) { 46 | t.ifError(err); 47 | t.equal(output, 'beep boop robot\n'); 48 | fs.writeFileSync(files.beep, 'module.exports = "BEEP";'); 49 | fs.writeFileSync(files.boop, 'module.exports = "BOOP";'); 50 | fs.writeFileSync(files.robot, 'module.exports = "ROBOT";'); 51 | }); 52 | } 53 | else if (lineNum === 2) { 54 | run(files.bundle, function (err, output) { 55 | t.ifError(err); 56 | t.equal(output, 'beep BOOP robot\n'); 57 | ps.kill(); 58 | }); 59 | } 60 | }); 61 | }); 62 | 63 | function run (file, cb) { 64 | var ps = spawn(process.execPath, [ file ]); 65 | var data = []; 66 | ps.stdout.on('data', function (buf) { data.push(buf) }); 67 | ps.stdout.on('end', function () { 68 | cb(null, Buffer.concat(data).toString('utf8')); 69 | }); 70 | ps.on('error', cb); 71 | return ps; 72 | } 73 | -------------------------------------------------------------------------------- /test/errors_transform.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var watchify = require('../'); 3 | var browserify = require('browserify'); 4 | var vm = require('vm'); 5 | 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var mkdirp = require('mkdirp'); 9 | var through = require('through2'); 10 | 11 | var os = require('os'); 12 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 13 | 14 | var main = path.join(tmpdir, 'main.js'); 15 | var file = path.join(tmpdir, 'dep.jsnum'); 16 | 17 | mkdirp.sync(tmpdir); 18 | fs.writeFileSync(main, 'require("./dep.jsnum")'); 19 | fs.writeFileSync(file, 'console.log(555)'); 20 | 21 | function someTransform(file) { 22 | if (!/\.jsnum$/.test(file)) { 23 | return through(); 24 | } 25 | function write (chunk, enc, next) { 26 | if (/\d/.test(chunk)) { 27 | this.push(chunk); 28 | } else { 29 | this.emit('error', new Error('No number in this chunk')); 30 | } 31 | next(); 32 | } 33 | return through(write); 34 | } 35 | 36 | test('errors in transform', function (t) { 37 | t.plan(6); 38 | var b = browserify(main, watchify.args); 39 | b.transform(someTransform); 40 | var w = watchify(b); 41 | w.bundle(function (err, src) { 42 | t.ifError(err); 43 | t.equal(run(src), '555\n'); 44 | breakTheBuild(); 45 | }); 46 | function breakTheBuild() { 47 | setTimeout(function() { 48 | fs.writeFileSync(file, 'console.log()'); 49 | }, 1000); 50 | w.once('update', function () { 51 | w.bundle(function (err, src) { 52 | t.ok(err instanceof Error, 'should be error'); 53 | t.ok(/^No number in this chunk/.test(err.message)); 54 | fixTheBuild(); 55 | }); 56 | }); 57 | } 58 | function fixTheBuild() { 59 | setTimeout(function() { 60 | fs.writeFileSync(file, 'console.log(333)'); 61 | }, 1000); 62 | var safety = setTimeout(function() { 63 | t.fail("gave up waiting"); 64 | w.close(); 65 | t.end(); 66 | }, 5000); 67 | w.once('update', function () { 68 | clearTimeout(safety); 69 | w.bundle(function (err, src) { 70 | t.ifError(err); 71 | t.equal(run(src), '333\n'); 72 | w.close(); 73 | }); 74 | }); 75 | } 76 | }); 77 | 78 | function run (src) { 79 | var output = ''; 80 | function log (msg) { output += msg + '\n' } 81 | vm.runInNewContext(src, { console: { log: log } }); 82 | return output; 83 | } 84 | -------------------------------------------------------------------------------- /test/many_immediate.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var mkdirp = require('mkdirp'); 5 | var spawn = require('win-spawn'); 6 | var split = require('split'); 7 | 8 | var cmd = path.resolve(__dirname, '../bin/cmd.js'); 9 | var os = require('os'); 10 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 11 | 12 | var files = { 13 | main: path.join(tmpdir, 'main.js'), 14 | robot: path.join(tmpdir, 'robot.js'), 15 | lines: path.join(tmpdir, 'lines.txt'), 16 | bundle: path.join(tmpdir, 'bundle.js') 17 | }; 18 | 19 | var edits = [ 20 | { file: 'lines', source: 'robo-boogie' }, 21 | { file: 'lines', source: 'dinosaurus rex' }, 22 | { 23 | file: 'robot', 24 | source: 'module.exports = function (n) { return n * 111 }', 25 | next: true 26 | }, 27 | { file: 'main', source: [ 28 | 'var fs = require("fs");', 29 | 'var robot = require("./robot.js");', 30 | 'var src = fs.readFileSync(__dirname + "/lines.txt", "utf8");', 31 | 'console.log(src.toUpperCase() + " " + robot(src.length));' 32 | ].join('\n') }, 33 | { file: 'lines', source: 't-rex' }, 34 | { 35 | file: 'robot', 36 | source: 'module.exports = function (n) { return n * 100 }', 37 | } 38 | ]; 39 | 40 | var expected = [ 41 | 'BEEP\nBOOP\n', 42 | 'ROBO-BOOGIE\n', 43 | 'DINOSAURUS REX\n', 44 | 'DINOSAURUS REX 1554\n', 45 | 'T-REX 555\n', 46 | 'T-REX 500\n' 47 | ]; 48 | 49 | mkdirp.sync(tmpdir); 50 | fs.writeFileSync(files.main, [ 51 | 'var fs = require("fs");', 52 | 'var src = fs.readFileSync(__dirname + "/lines.txt", "utf8");', 53 | 'console.log(src.toUpperCase());' 54 | ].join('\n')); 55 | fs.writeFileSync(files.lines, 'beep\nboop'); 56 | 57 | test('many immediate', function (t) { 58 | t.plan(expected.length * 2 + edits.length); 59 | var ps = spawn(cmd, [ 60 | files.main, 61 | '-t', require.resolve('brfs'), '-v', 62 | '-o', files.bundle 63 | ]); 64 | ps.stdout.pipe(process.stdout); 65 | ps.stderr.pipe(process.stdout); 66 | var lineNum = 0; 67 | ps.stderr.pipe(split()).on('data', function (line) { 68 | if (line.length === 0) return; 69 | 70 | run(files.bundle, function (err, output) { 71 | t.ifError(err); 72 | t.equal(output, expected.shift()); 73 | 74 | (function next () { 75 | if (edits.length === 0) return; 76 | var edit = edits.shift(); 77 | fs.writeFile(files[edit.file], edit.source, function (err) { 78 | t.ifError(err); 79 | if (edit.next) next(); 80 | }); 81 | })(); 82 | }) 83 | }); 84 | 85 | t.on('end', function () { 86 | ps.kill(); 87 | }); 88 | }); 89 | 90 | function run (file, cb) { 91 | var ps = spawn(process.execPath, [ file ]); 92 | var data = []; 93 | ps.stdout.on('data', function (buf) { data.push(buf) }); 94 | ps.stdout.on('end', function () { 95 | cb(null, Buffer.concat(data).toString('utf8')); 96 | }); 97 | ps.on('error', cb); 98 | return ps; 99 | } 100 | -------------------------------------------------------------------------------- /test/many.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var mkdirp = require('mkdirp'); 5 | var spawn = require('win-spawn'); 6 | var split = require('split'); 7 | 8 | var cmd = path.resolve(__dirname, '../bin/cmd.js'); 9 | var os = require('os'); 10 | var tmpdir = path.join((os.tmpdir || os.tmpDir)(), 'watchify-' + Math.random()); 11 | 12 | var files = { 13 | main: path.join(tmpdir, 'main.js'), 14 | robot: path.join(tmpdir, 'robot.js'), 15 | lines: path.join(tmpdir, 'lines.txt'), 16 | bundle: path.join(tmpdir, 'bundle.js') 17 | }; 18 | 19 | var edits = [ 20 | { file: 'lines', source: 'robo-boogie' }, 21 | { file: 'lines', source: 'dinosaurus rex' }, 22 | { 23 | file: 'robot', 24 | source: 'module.exports = function (n) { return n * 111 }', 25 | next: true 26 | }, 27 | { file: 'main', source: [ 28 | 'var fs = require("fs");', 29 | 'var robot = require("./robot.js");', 30 | 'var src = fs.readFileSync(__dirname + "/lines.txt", "utf8");', 31 | 'console.log(src.toUpperCase() + " " + robot(src.length));' 32 | ].join('\n') }, 33 | { file: 'lines', source: 't-rex' }, 34 | { 35 | file: 'robot', 36 | source: 'module.exports = function (n) { return n * 100 }', 37 | } 38 | ]; 39 | 40 | var expected = [ 41 | 'BEEP\nBOOP\n', 42 | 'ROBO-BOOGIE\n', 43 | 'DINOSAURUS REX\n', 44 | 'DINOSAURUS REX 1554\n', 45 | 'T-REX 555\n', 46 | 'T-REX 500\n' 47 | ]; 48 | 49 | mkdirp.sync(tmpdir); 50 | fs.writeFileSync(files.main, [ 51 | 'var fs = require("fs");', 52 | 'var src = fs.readFileSync(__dirname + "/lines.txt", "utf8");', 53 | 'console.log(src.toUpperCase());' 54 | ].join('\n')); 55 | fs.writeFileSync(files.lines, 'beep\nboop'); 56 | 57 | test('many edits', function (t) { 58 | t.plan(expected.length * 2 + edits.length); 59 | var ps = spawn(cmd, [ 60 | files.main, 61 | '-t', require.resolve('brfs'), '-v', 62 | '-o', files.bundle 63 | ]); 64 | ps.stdout.pipe(process.stdout); 65 | ps.stderr.pipe(process.stdout); 66 | var lineNum = 0; 67 | ps.stderr.pipe(split()).on('data', function (line) { 68 | if (line.length === 0) return; 69 | 70 | run(files.bundle, function (err, output) { 71 | t.ifError(err); 72 | t.equal(output, expected.shift()); 73 | 74 | (function next () { 75 | if (edits.length === 0) return; 76 | var edit = edits.shift(); 77 | setTimeout(function () { 78 | fs.writeFile(files[edit.file], edit.source, function (err) { 79 | t.ifError(err); 80 | if (edit.next) next(); 81 | }); 82 | }, 25); 83 | })(); 84 | }) 85 | }); 86 | 87 | t.on('end', function () { 88 | ps.kill(); 89 | }); 90 | }); 91 | 92 | function run (file, cb) { 93 | var ps = spawn(process.execPath, [ file ]); 94 | var data = []; 95 | ps.stdout.on('data', function (buf) { data.push(buf) }); 96 | ps.stdout.on('end', function () { 97 | cb(null, Buffer.concat(data).toString('utf8')); 98 | }); 99 | ps.on('error', cb); 100 | return ps; 101 | } 102 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var through = require('through2'); 2 | var path = require('path'); 3 | var chokidar = require('chokidar'); 4 | var xtend = require('xtend'); 5 | var anymatch = require('anymatch'); 6 | 7 | module.exports = watchify; 8 | module.exports.args = { 9 | cache: {}, packageCache: {} 10 | }; 11 | 12 | function watchify (b, opts) { 13 | if (!opts) opts = {}; 14 | var cache = b._options.cache; 15 | var pkgcache = b._options.packageCache; 16 | var delay = typeof opts.delay === 'number' ? opts.delay : 100; 17 | var changingDeps = {}; 18 | var pending = false; 19 | var updating = false; 20 | 21 | var wopts = {persistent: true}; 22 | if (opts.ignoreWatch) { 23 | var ignored = opts.ignoreWatch !== true 24 | ? opts.ignoreWatch 25 | : '**/node_modules/**'; 26 | } 27 | if (opts.poll || typeof opts.poll === 'number') { 28 | wopts.usePolling = true; 29 | wopts.interval = opts.poll !== true 30 | ? opts.poll 31 | : undefined; 32 | } 33 | 34 | if (cache) { 35 | b.on('reset', collect); 36 | collect(); 37 | } 38 | 39 | function collect () { 40 | b.pipeline.get('deps').push(through.obj(function(row, enc, next) { 41 | var file = row.expose ? b._expose[row.id] : row.file; 42 | cache[file] = { 43 | source: row.source, 44 | deps: xtend(row.deps) 45 | }; 46 | this.push(row); 47 | next(); 48 | })); 49 | } 50 | 51 | b.on('file', function (file) { 52 | watchFile(file); 53 | }); 54 | 55 | b.on('package', function (pkg) { 56 | var file = path.join(pkg.__dirname, 'package.json'); 57 | watchFile(file); 58 | if (pkgcache) pkgcache[file] = pkg; 59 | }); 60 | 61 | b.on('reset', reset); 62 | reset(); 63 | 64 | function reset () { 65 | var time = null; 66 | var bytes = 0; 67 | b.pipeline.get('record').on('end', function () { 68 | time = Date.now(); 69 | }); 70 | 71 | b.pipeline.get('wrap').push(through(write, end)); 72 | function write (buf, enc, next) { 73 | bytes += buf.length; 74 | this.push(buf); 75 | next(); 76 | } 77 | function end () { 78 | var delta = Date.now() - time; 79 | b.emit('time', delta); 80 | b.emit('bytes', bytes); 81 | b.emit('log', bytes + ' bytes written (' 82 | + (delta / 1000).toFixed(2) + ' seconds)' 83 | ); 84 | this.push(null); 85 | } 86 | } 87 | 88 | var fwatchers = {}; 89 | var fwatcherFiles = {}; 90 | var ignoredFiles = {}; 91 | 92 | b.on('transform', function (tr, mfile) { 93 | tr.on('file', function (dep) { 94 | watchFile(mfile, dep); 95 | }); 96 | }); 97 | b.on('bundle', function (bundle) { 98 | updating = true; 99 | bundle.on('error', onend); 100 | bundle.on('end', onend); 101 | function onend () { updating = false } 102 | }); 103 | 104 | function watchFile (file, dep) { 105 | dep = dep || file; 106 | if (ignored) { 107 | if (!ignoredFiles.hasOwnProperty(file)) { 108 | ignoredFiles[file] = anymatch(ignored, file); 109 | } 110 | if (ignoredFiles[file]) return; 111 | } 112 | if (!fwatchers[file]) fwatchers[file] = []; 113 | if (!fwatcherFiles[file]) fwatcherFiles[file] = []; 114 | if (fwatcherFiles[file].indexOf(dep) >= 0) return; 115 | 116 | var w = b._watcher(dep, wopts); 117 | w.setMaxListeners(0); 118 | w.on('error', b.emit.bind(b, 'error')); 119 | w.on('change', function () { 120 | invalidate(file); 121 | }); 122 | fwatchers[file].push(w); 123 | fwatcherFiles[file].push(dep); 124 | } 125 | 126 | function invalidate (id) { 127 | if (cache) delete cache[id]; 128 | if (pkgcache) delete pkgcache[id]; 129 | changingDeps[id] = true; 130 | 131 | if (!updating && fwatchers[id]) { 132 | fwatchers[id].forEach(function (w) { 133 | w.close(); 134 | }); 135 | delete fwatchers[id]; 136 | delete fwatcherFiles[id]; 137 | } 138 | 139 | // wait for the disk/editor to quiet down first: 140 | if (pending) clearTimeout(pending); 141 | pending = setTimeout(notify, delay); 142 | } 143 | 144 | function notify () { 145 | if (updating) { 146 | pending = setTimeout(notify, delay); 147 | } else { 148 | pending = false; 149 | b.emit('update', Object.keys(changingDeps)); 150 | changingDeps = {}; 151 | } 152 | } 153 | 154 | b.close = function () { 155 | Object.keys(fwatchers).forEach(function (id) { 156 | fwatchers[id].forEach(function (w) { w.close() }); 157 | }); 158 | }; 159 | 160 | b._watcher = function (file, opts) { 161 | return chokidar.watch(file, opts); 162 | }; 163 | 164 | return b; 165 | } 166 | -------------------------------------------------------------------------------- /readme.markdown: -------------------------------------------------------------------------------- 1 | # watchify 2 | 3 | watch mode for [browserify](https://github.com/substack/node-browserify) builds 4 | 5 | [![build status](https://secure.travis-ci.org/browserify/watchify.svg?branch=master)](http://travis-ci.org/browserify/watchify) 6 | 7 | Update any source file and your browserify bundle will be recompiled on the 8 | spot. 9 | 10 | # example 11 | 12 | ``` 13 | $ watchify main.js -o static/bundle.js 14 | ``` 15 | 16 | Now as you update files, `static/bundle.js` will be automatically 17 | incrementally rebuilt on the fly. 18 | 19 | The `-o` option can be a file or a shell command (not available on Windows) 20 | that receives piped input: 21 | 22 | ``` sh 23 | watchify main.js -o 'exorcist static/bundle.js.map > static/bundle.js' -d 24 | ``` 25 | 26 | ``` sh 27 | watchify main.js -o 'uglifyjs -cm > static/bundle.min.js' 28 | ``` 29 | 30 | You can use `-v` to get more verbose output to show when a file was written and how long the bundling took (in seconds): 31 | 32 | ``` 33 | $ watchify browser.js -d -o static/bundle.js -v 34 | 610598 bytes written to static/bundle.js (0.23 seconds) at 8:31:25 PM 35 | 610606 bytes written to static/bundle.js (0.10 seconds) at 8:45:59 PM 36 | 610597 bytes written to static/bundle.js (0.14 seconds) at 8:46:02 PM 37 | 610606 bytes written to static/bundle.js (0.08 seconds) at 8:50:13 PM 38 | 610597 bytes written to static/bundle.js (0.08 seconds) at 8:58:16 PM 39 | 610597 bytes written to static/bundle.js (0.19 seconds) at 9:10:45 PM 40 | ``` 41 | 42 | # usage 43 | 44 | Use `watchify` with all the same options as `browserify` except that `-o` (or 45 | `--outfile`) is mandatory. Additionally, there are also: 46 | 47 | ``` 48 | Standard Options: 49 | 50 | --outfile=FILE, -o FILE 51 | 52 | This option is required. Write the browserify bundle to this file. If 53 | the file contains the operators `|` or `>`, it will be treated as a 54 | shell command, and the output will be piped to it. 55 | 56 | --verbose, -v [default: false] 57 | 58 | Show when a file was written and how long the bundling took (in 59 | seconds). 60 | 61 | --version 62 | 63 | Show the watchify and browserify versions with their module paths. 64 | ``` 65 | 66 | ``` 67 | Advanced Options: 68 | 69 | --delay [default: 100] 70 | 71 | Amount of time in milliseconds to wait before emitting an "update" 72 | event after a change. 73 | 74 | --ignore-watch=GLOB, --iw GLOB [default: false] 75 | 76 | Ignore monitoring files for changes that match the pattern. Omitting 77 | the pattern will default to "**/node_modules/**". 78 | 79 | --poll=INTERVAL [default: false] 80 | 81 | Use polling to monitor for changes. Omitting the interval will default 82 | to 100ms. This option is useful if you're watching an NFS volume. 83 | ``` 84 | 85 | # methods 86 | 87 | ``` js 88 | var watchify = require('watchify'); 89 | ``` 90 | 91 | ## watchify(b, opts) 92 | 93 | watchify is a browserify [plugin](https://github.com/browserify/browserify#bpluginplugin-opts), so it can be applied like any other plugin. 94 | However, when creating the browserify instance `b`, **you MUST set the `cache` 95 | and `packageCache` properties**: 96 | 97 | ``` js 98 | var b = browserify({ cache: {}, packageCache: {} }); 99 | b.plugin(watchify); 100 | ``` 101 | 102 | ```js 103 | var b = browserify({ 104 | cache: {}, 105 | packageCache: {}, 106 | plugin: [watchify] 107 | }); 108 | ``` 109 | 110 | **By default, watchify doesn't display any output, see [events](https://github.com/browserify/watchify#events) for more info.** 111 | 112 | `b` continues to behave like a browserify instance except that it caches file 113 | contents and emits an `'update'` event when a file changes. You should call 114 | `b.bundle()` after the `'update'` event fires to generate a new bundle. 115 | Calling `b.bundle()` extra times past the first time will be much faster due 116 | to caching. 117 | 118 | **Important:** Watchify will not emit `'update'` events until you've called 119 | `b.bundle()` once and completely drained the stream it returns. 120 | 121 | ```js 122 | var fs = require('fs'); 123 | var browserify = require('browserify'); 124 | var watchify = require('watchify'); 125 | 126 | var b = browserify({ 127 | entries: ['path/to/entry.js'], 128 | cache: {}, 129 | packageCache: {}, 130 | plugin: [watchify] 131 | }); 132 | 133 | b.on('update', bundle); 134 | bundle(); 135 | 136 | function bundle() { 137 | b.bundle() 138 | .on('error', console.error) 139 | .pipe(fs.createWriteStream('output.js')) 140 | ; 141 | } 142 | ``` 143 | 144 | ### options 145 | 146 | You can to pass an additional options object as a second parameter of 147 | watchify. Its properties are: 148 | 149 | `opts.delay` is the amount of time in milliseconds to wait before emitting 150 | an "update" event after a change. Defaults to `100`. 151 | 152 | `opts.ignoreWatch` ignores monitoring files for changes. If set to `true`, 153 | then `**/node_modules/**` will be ignored. For other possible values see 154 | Chokidar's [documentation](https://github.com/paulmillr/chokidar#path-filtering) on "ignored". 155 | 156 | `opts.poll` enables polling to monitor for changes. If set to `true`, then 157 | a polling interval of 100ms is used. If set to a number, then that amount of 158 | milliseconds will be the polling interval. For more info see Chokidar's 159 | [documentation](https://github.com/paulmillr/chokidar#performance) on 160 | "usePolling" and "interval". 161 | **This option is useful if you're watching an NFS volume.** 162 | 163 | ```js 164 | var b = browserify({ cache: {}, packageCache: {} }); 165 | // watchify defaults: 166 | b.plugin(watchify, { 167 | delay: 100, 168 | ignoreWatch: ['**/node_modules/**'], 169 | poll: false 170 | }); 171 | ``` 172 | 173 | ## b.close() 174 | 175 | Close all the open watch handles. 176 | 177 | # events 178 | 179 | ## b.on('update', function (ids) {}) 180 | 181 | When the bundle changes, emit the array of bundle `ids` that changed. 182 | 183 | ## b.on('bytes', function (bytes) {}) 184 | 185 | When a bundle is generated, this event fires with the number of bytes. 186 | 187 | ## b.on('time', function (time) {}) 188 | 189 | When a bundle is generated, this event fires with the time it took to create the 190 | bundle in milliseconds. 191 | 192 | ## b.on('log', function (msg) {}) 193 | 194 | This event fires after a bundle was created with messages of the form: 195 | 196 | ``` 197 | X bytes written (Y seconds) 198 | ``` 199 | 200 | with the number of bytes in the bundle X and the time in seconds Y. 201 | 202 | # working with browserify transforms 203 | 204 | If your custom transform for browserify adds new files to the bundle in a non-standard way without requiring. 205 | You can inform Watchify about these files by emiting a 'file' event. 206 | 207 | ``` 208 | module.exports = function(file) { 209 | return through( 210 | function(buf, enc, next) { 211 | /* 212 | manipulating file content 213 | */ 214 | 215 | this.emit("file", absolutePathToFileThatHasToBeWatched); 216 | 217 | next(); 218 | } 219 | ); 220 | }; 221 | ``` 222 | 223 | # install 224 | 225 | With [npm](https://npmjs.org) do: 226 | 227 | ``` 228 | $ npm install -g watchify 229 | ``` 230 | 231 | to get the watchify command and: 232 | 233 | ``` 234 | $ npm install watchify 235 | ``` 236 | 237 | to get just the library. 238 | 239 | # troubleshooting 240 | 241 | ## rebuilds on OS X never trigger 242 | 243 | It may be related to a bug in `fsevents` (see [#250](https://github.com/browserify/watchify/issues/205#issuecomment-98672850) 244 | and [stackoverflow](http://stackoverflow.com/questions/26708205/webpack-watch-isnt-compiling-changed-files/28610124#28610124)). 245 | Try the `--poll` flag 246 | and/or renaming the project's directory - that might help. 247 | 248 | ## watchify swallows errors 249 | 250 | To ensure errors are reported you have to add a event listener to your bundle stream. For more information see ([browserify/browserify#1487 (comment)](https://github.com/browserify/browserify/issues/1487#issuecomment-173357516) and [stackoverflow](https://stackoverflow.com/a/22389498/1423220)) 251 | 252 | **Example:** 253 | ``` 254 | var b = browserify(); 255 | b.bundle() 256 | .on('error', console.error) 257 | ... 258 | ; 259 | ``` 260 | 261 | # see also 262 | 263 | - [budo](https://www.npmjs.com/package/budo) – a simple development server built on watchify 264 | - [errorify](https://www.npmjs.com/package/errorify) – a plugin to add error handling to watchify development 265 | - [watchify-request](https://www.npmjs.com/package/watchify-request) – wraps a `watchify` instance to avoid stale bundles in HTTP requests 266 | - [watchify-middleware](https://www.npmjs.com/package/watchify-middleware) – similar to `watchify-request`, but includes some higher-level features 267 | 268 | # license 269 | 270 | MIT 271 | --------------------------------------------------------------------------------