3
votes

I am trying to use Gulp, Browserify, and Babelify to compile and transform multiple ES6 files into a single JavaScript library that can be shared with other developers.

I am trying to use multiple ES6 source files, each of which uses ES6 modules using export. I want them all to be wrapped up into a single class/function like a 'namespace'.

It seems like Browserify's --standalone option is designed to do this, but I can only get it to work when there is a single input file. When there are multiple source files with exports, I can't get them all to be included in the 'namespace' class, and I can't control which source file's exports ultimately gets picked to be in the 'namespace' class.

In this example, a.js and b.js are the source files, and I am expecting them to be bundled together in a 'namespace' class called TestModule.

a.js

export function fromA() {
    console.log('Hello from a.js');
}

b.js

export function fromB() {
    console.log('Hello from b.js');
}

gulpfile.js

const browserify = require('browserify');
const gulp = require('gulp');
const log = require('gulplog');
const plumber = require('gulp-plumber');
const source = require('vinyl-source-stream');

function minimalExample(done) {
    return browserify({
            entries: [
                './src/a.js',
                './src/b.js'
            ],
            standalone: 'TestModule' // output as a library under this namespace using a umd wrapper
        })
        .transform('babelify')
        .bundle()
            .on('error', log.error)
        .pipe(source('minimalExample.js'))
        .pipe(plumber())
        .pipe(gulp.dest('./dist'));
}

module.exports = {
    minimalExample
};

What I want

I want minimalExample.js to have an object named TestModule that has functions fromA() and fromB(), so that I can call both methods. I should be able to run either of these commands from the console:

TestModule.fromA()
TestModule.fromB()

What is actually happening

When I load minimalExample.js in a browser, open the console, and inspect the TestModule object, it exists, but it is missing the function from a.js. It only has the function from b.js:

TestModule has only the fromB() function

Am I missing a setting somewhere? Is there a way to get Browserify to include all the exports in the standalone 'namespace' class?


Update 1

Prompted by @Zydnar's discussion, I did the obvious thing and actually looked at the output file, minimalExample.js. I don't understand how the transforms are intended to work or what is going wrong yet; I'm still looking at that. But I do see both input files have been transformed and included in the output.

Here is the actual output, and the same thing but pretty-printed by Chrome.

1
I bet if you'll replace in entries './src/a.js', with './src/b.js', and vice versa, you'll have TestModule.fromA but not TestModule.fromB. I guess something after browserify replaces what actually should be appended.Zydnar
@Zydnar Oddly that is not what happens. Swapping them around still gives me the output from b.js. I don't seem to be able to pick which one is included (ETA: short of commenting all but one out). I thought of that too, because for my actual use case I could work around it if I could choose which files is included, but I can't seem to figure out how to do that.Katie Kilian
What do you need this line: .pipe(source('minimalExample.js')) for? And what is in this file? It looks like you override stream.Zydnar
Nothing is in that file, that is the output file. Up until that point, the objects being chained are not Vinyl objects. Browserify outputs a standard Node stream. So they must be converted from a Node stream to a Vinyl source stream for the rest of the gulp pipeline to work.Katie Kilian
@Zydnar This is interesting though; your question prompted me to do the obvious thing and examine the actual output in minimalExample.js. I still don't exactly understand what is going on, but I do see both of my source files transformed in the output. I'll post an update shortly.Katie Kilian

1 Answers

3
votes

Thanks to help on the browserify project on Github, I have an answer for this. Renée Kooi pointed me in the right direction. They said:

If you have multiple entry points, all of them are executed, but browserify doesn't merge modules, which could cause bad unexpected behaviour.

The solution is to have a single file that acts as an entry point for Browserify that exports everything you want exported. Use that single file as your input source file in the entries option. Browserify will walk your app's dependency tree and include the dependencies it requires. Anything exported by the entry point file will be included in the exported module as expected.


A complete example follows. main.js is the entry point file.

a.js

export function fromA() {
    console.log('Hello from a.js');
}

b.js

export function fromB() {
    console.log('Hello from b.js');
}

main.js (This one is new)

export * from './a.js';
export * from './b.js';

gulpfile.js

const browserify = require('browserify');
const gulp = require('gulp');
const log = require('gulplog');
const plumber = require('gulp-plumber');
const source = require('vinyl-source-stream');

function minimalExample(done) {
    return browserify({
            entries: [
                './src/main.js'  // THIS LINE HAS CHANGED FROM THE QUESTION
            ],
            standalone: 'TestModule'
        })
        .transform('babelify')
        .bundle()
            .on('error', log.error)
        .pipe(source('minimalExample.js'))
        .pipe(plumber())
        .pipe(gulp.dest('./dist'));
}

module.exports = {
    minimalExample
};

Now when you run the minimalExample task with gulp, the file generated will have both TestModule.fromA() and TestModule.fromB() functions.