How to solve a conflict between browserify (Backbo

2020-07-17 15:52发布

问题:

I have a backbone app up and running correctly. It's meant to be used as a widget in 3rd pages. Unfortunately, I just realised that one of these pages has already Backbone / underscore loaded.

I get errors such as this:

Uncaught TypeError: Cannot read property 'extend' of undefined 

Which usually appear when underscore has not been previously loaded.

My typical view is like this: (normal Backbone view)

./view1.js

var Backbone = require('backbone')
var _ = require('underscore')
var $ = require('jquery')
Backbone.$ = $

module.exports = Backbone.View.extend({

  events: {

  },

  initialize: function () {

  },

  render: function () {

  }
})

then I just have to call it:

var View1 = require('view1')
var view1Instance = new View1(...)

Thanks for any help :)

EDIT after investigation: When running through the debugger, it appears that the Backbone variable is an empty object instead of being Backbone. As if the require('backbone') just returned {}

EDIT2: It seems related to this issue: https://github.com/substack/node-browserify/issues/790

回答1:

The problem with Backbone and Requirejs (as indicated by thlorenz here)

  • backbone looks for define before looking for module.exports
  • requirejs works with globals which is not good practice and in this case causes problems since it affects everything that runs in the same browser tab

He advises to wrap everything in a scope and hide the define function. He has also a tool to do that.browserify-shim

However I did not use that but the postBundleCB option from Browserify: (thx to a coworker).

In my Gruntfile:

browserify: {
  dist: {
    src: ['src/app/app.js'],
    dest: 'www/app.js',
    options: {
      postBundleCB: function (err, src, next) {
        return next(null, '(function () { var define = undefined; '+src+' })();')
      }
    }
  }
},

This solved my problem :)

I didn't try browserify-shim, so I don't know much about it.



回答2:

In case someone is using gulp and the official browserify (so postBundleCB is not available) I used gulp-wrap to wrap the 'define' definition:

var wrap = require('gulp-wrap');

[...]

bundler.bundle()
  .on('error', swallowError)
  .pipe(source(file))
  .pipe(wrap('(function () { var define = undefined; <%=contents%> })();'))
  .pipe(rename(renameOptions))
  .pipe(gulp.dest(argv.output?argv.output:'./dist'));


回答3:

Another workaround would be using a custom prelude like this:

// modules are defined as an array
// [ module function, map of requireuires ]
//
// map of requireuires is short require name -> numeric require
//
// anything defined in a previous bundle is accessed via the
// orig method which is the requireuire for previous bundles

(function outer (modules, cache, entry) {
    // Save the require from previous bundle to this closure if any
    var previousRequire = typeof require == "function" && require;

    function newRequire(name, jumped){
        var oldDefine = window.define;
        window.define = undefined;
        if(!cache[name]) {
            if(!modules[name]) {
                // if we cannot find the the module within our internal map or
                // cache jump to the current global require ie. the last bundle
                // that was added to the page.
                var currentRequire = typeof require == "function" && require;
                if (!jumped && currentRequire) return currentRequire(name, true);

                // If there are other bundles on this page the require from the
                // previous one is saved to 'previousRequire'. Repeat this as
                // many times as there are bundles until the module is found or
                // we exhaust the require chain.
                if (previousRequire) return previousRequire(name, true);
                var err = new Error('Cannot find module \'' + name + '\'');
                err.code = 'MODULE_NOT_FOUND';
                throw err;
            }
            var m = cache[name] = {exports:{}};
            modules[name][0].call(m.exports, function(x){
                var id = modules[name][1][x];
                return newRequire(id ? id : x);
            },m,m.exports,outer,modules,cache,entry);
        }
        window.define = oldDefine;
        return cache[name].exports;
    }
    for(var i=0;i<entry.length;i++) newRequire(entry[i]);

    // Override the current require with this new one
    return newRequire;
})

and you can give this prelude for browserify like this:

browserify({
  prelude: fs.readFileSync(__dirname + '/src/js/prelude.js', 'utf-8'),
  ...
})


回答4:

this can be related to someone, who is using browserify in a site that has requirejs. A provisional solution can be, instead of :

`.pipe(wrap('(function () { var define = undefined; <%=contents%> })();'))`

for someone who is using the browserify API directly, can add this little piece of code before load our bundle:

(function() {
if (typeof define === "function" && define.amd) {
    define.amd= false; 
}})();

this will allow to continue with our execution, without break the code on the client side.

I hope that this be useful.