Search code examples
javascriptnode.jsbrowserifycommonjs

Is there a way to allow browserify modules access to the global scope (window object)?


The Scenario

I'm using Browserify (or NodeJS/CommonJS like imports) to require some JavaScript libraries. However, I'm having trouble getting them to play nice since apparently Browserify by default denies global scope access to all modules. For example, doing this doesn't work;

file1.js

require('moment');

file2.js

moment(new Date()); // throws moment is undefined

But this works by changing the contents of file1.js to the following;

window.moment = require('moment');

The Problem

This has worked well enough so far, however now I'm having trouble loading the moment timezone library (an extension of MomentJS). Moment Timezone's docs imply both scripts should run on the window global scope by adding them as script tags like so;

<script src="moment.js"></script>
<script src="moment-timezone-with-data.js"></script>

So they can be used like this;

moment().tz("America/Los_Angeles").format();

This however, seems very hard to achieve, since if I try the following;

window.moment = require('moment');
require('./../../node_modules/moment-timezone/builds/moment-timezone-with-data-2010-2020'); // the location of my moment-timezone library

I get a runtime error saying that moment.tz is undefined (meaning that the second library wasn't run with the global scope). And if I try to add the window.moment to the second line of code, I'll be rewriting the first instance of the full library.

So, in short;

Is there a way to allow certain Browserify imports to have global scope access, or to run with the window object as their scope?

I'm aware of the security implications this has, but selectively used, this would be immensely useful while requiring things like JavaScript libraries that need global access to set up themselves correctly. Any help would be greatly appreciated.


Solution

  • Put tiny libraries into vendor.js, include that on your page with <script>, make them known to browserify with browserify-shim and use them with require('libname') in your modules.

    Larger libraries may be included from, say, vendor CDNs, and also be made known to browserify with browserify-shim.

    index.html

    <html>
        <head>
            <script src="vendor.js"></script>
            <script src="bundle.js"></script>
        </head>
        <body>
            Open devtools and inspect the output
        </body>
    </html>
    

    package.json

    {
      "scripts": {
        "build": "cat vendor1.js vendor2.js > dist/vendor.js && cp index.html dist/index.html && browserify index.js -o dist/bundle.js"
      },
      "browserify-shim" : {
        "vendor1" : "global:vendor1",
        "vendor2" : "global:vendor2"
      },
      "browserify" : {
        "transform" : [ "browserify-shim" ]
      },
      "devDependencies": {
        "browserify": "^14.1.0",
        "browserify-shim": "^3.8.14"
      },
      "dependencies": {
        "moment": "^2.18.1",
        "moment-timezone": "^0.5.11"
      }
    }
    

    A couple of things to note about the above package.json:

    • build: it builds 'application' in dist folder, creating the following structure:
      • dist
        • index.html
        • bundle.js
        • vendor.js
    • "browserify" entry: it just adds browserify-shim
    • "browserify-shim" entry: it tells browserify that vendor1 and vendor2 are available as properties on window (global) object. Browserify won't attempt to bundle them, and require() will just return those properties. You need to make sure they are actually available (so index.html above includes them with <script src="vendor.js">

    index.js

    var momentfromnpm = require('moment-timezone');
    var vendor1 = require('vendor1');
    var vendor2 = require('vendor2');
    console.log("Hello");
    console.log("Using module from npm", momentfromnpm().tz("Europe/London").format());
    console.log("using a function from vendor1.js (bundled into vendor.js)", vendor1());
    console.log("using a 'module' from vendor2.js (bundled to vendor.js)", vendor2.doWork());
    

    vendor1.js

    window.vendor1 = function() { // could also be just `function vendor1()...`
        return "I'm a simple function, defined on a window";
    };
    

    vendor2.js

    var vendor2 = { // could also be window.vendor2
        doWork: function() {
            console.log("vendor2 doing work");
        }
    };