Why `init` of shim in my require.js configuration

2019-02-26 02:03发布

问题:

Update:

I was writing a small module to handle this csrf token problem in backbone until I got push notification of @Louis's answer.

His answer is quite elegant and seems nice, but I'll leave a link to my backbone.csrf module github repo just for anyone who needs it.

====================================================================

I'm using Backbone as my frontend framework along with my Django backend.

I had to configure my Backbone.sync to set CSRF request header for every single AJAX requests before it sends it, for compatibility with Django's CSRF protection system.

Since I was using require.js for modular javascript development, I tried to configure this in shim.init of require.config so that this overriding will be fired as soon as the Backbone is loaded on browser:

<script>
    var require = {
        ...

        shim: {
            'jquery': {'exports': 'jQuery'},
            'backbone': {
                'deps': ['underscore', 'jquery'],
                'exports': 'Backbone',
                'init': function(_, $) {
                     alert('NOT EVEN CALLED');
                     var originalSync = this.Backbone.sync;
                     this.Backbone.sync = function(method, model, options) {
                         options.beforeSend = function(xhr) {
                             xhr.setRequestHeader('X-CSRFToken', window.csrf_token);
                         }
                         return originalSync(method, model, options);
                     }
                 }
            }
        }
    }
</script>
// Load require.js
<script src="require.js"></script>

Although the Backbone has been successfully loaded, the 'init' of the require configuration doesn't get called.

What is the problem?

回答1:

Looking at the annotated source, I see that Backbone calls define when it detects it is running with an AMD loader. Using a shim with a module that calls define results in undefined behavior because shim is for modules that do not call define.

You could achieve what you want with a fake backbone module like this which you'd save in a file named backbone-glue.js:

define(['backbone'], function (Backbone) {
    var originalSync = Backbone.sync;
    Backbone.sync = function(method, model, options) {
        options.beforeSend = function(xhr) {
            xhr.setRequestHeader('X-CSRFToken', window.csrf_token);
        }
        return originalSync(method, model, options);
    };

    return Backbone;
});

And then you should have a map like this in your configuration for RequireJS:

map: {
     '*': {
          backbone: 'backbone-glue'
     },
     'backbone-glue': {
          backbone: 'backbone'
     }
}

What this does is that everywhere (*) when the module backbone is required RequireJS loads backbone-glue instead. However, in backbone-glue, when backbone is required, then backbone is loaded. This allows backbone-glue to load the original Backbone.