browserify / watchify

watch mode for browserify builds
Other
1.79k stars 181 forks source link

The need to drain initial bundle() should be clearly documented #254

Closed baileyparker closed 9 years ago

baileyparker commented 9 years ago

When using watchify programmatically, the README (and pretty much any documentation available online...I spent nearly 3 hours looking so trust me when I any) does not mention that you must call bundle() and then drain that stream before watching will actually begin.

Everything I've seen says you need this:

var browserify = require('browserify'),
    watchify = require('watchify');

var b = watchify(browserify({
    entries: ['./file.js'],
    cache: {},
    packageCache: {},
    fullPaths: true,
}));

b.on('update', function() {

    // b.bundle() ... and then save
});

But you actually need:

var browserify = require('browserify'),
    watchify = require('watchify');

var b = watchify(browserify({
    entries: ['./file.js'],
    cache: {},
    packageCache: {},
    fullPaths: true,
}));

b.on('update', function() {

    // b.bundle() ... and then save
});

// this is REQUIRED
b.bundle().on('data', function() {});

And b.bundle() alone will not suffice (although it lulls you into complacency because if you put a console log into the _watcher function in watchify it seems that chokidar is called for the file). You need to consume this bundle's stream completely before any file events will arrive.

The explanation to why this is is beyond me (does node queue events until that particular stream is finished? if so, why? isn't the whole point of the streams API that it's async?), but I think this should be clearly documented somewhere. I've spent nearly 4 hours trying to resolve this, so hopefully this will save someone else 4 hours in the future.

ghost commented 9 years ago

The 'data' listener isn't required if you write to a file:

function writer () { b.bundle().pipe(fs.createWriteStream('bundle.js'))
b.on('update', writer)
writer()

I don't quite get the use-case where you wouldn't write to a file or otherwise consume the output. Do you more mean that it's not obvious that listening for 'update' only kicks in after the first bundle? I'm not seeing why you would want to throw the first bundle away like that.

baileyparker commented 9 years ago

After spending so much time on this, it makes sense that watchify could only start firing updates after the first bundle (because it needs to collect dependencies) - but why not say this explicitly? You don't have to understand the internals of browserify to use it. It wouldn't be clear without knowing them that calling b.require() or b.add() doesn't do anything but append the file to an array (at the least, I assumed it would fire the 'file' event and watchify would catch this and start listening for fs events).

But now that I know that, why you have to do something with that initial bundle's stream is really non-obvious to me.

Maybe this use case isn't very common, but I think it's an easy mistake to make. I can see someone (as I did) taking the following steps:

  1. To use watchify the docs say I need an instance of watchify and browserify - Initialize browserify and watchify
  2. Now that I have the instance, I follow the common Node pattern of listening for an event - Listen for 'update' event

And then if they figure out that they must bundle it first, what intuition would tell them that they must do something with that initial bundle? Wouldn't your first instinct be to just add a w.bundle()?

The fact that this silently fails really compounds this problem. You can't see why (or event what's failing) just adding a w.bundle() doesn't work by adding console.log()s or using a debugger.