Search code examples
javascriptecmascript-6gulpbabeljsbrowserify

Browserify --standalone with ES6 modules and multiple source files and exports


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.


Solution

  • 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.