$.mustache() is defined, but Mustache isn't de

2019-05-31 01:38发布

问题:

I am trying to use Mustache along with requireJS and jQuery, and it seems that it is being loaded into the browser since Chrome's console outputs correctly:

>$.mustache
<function (template, view, partials) {
    return Mustache.render(template, view, partials);
} 

But when I try to use the mustache function it gives the error ReferenceError: Mustache is not defined and points to line 588 in the mustache.js file itself:

$.mustache = function (template, view, partials) {
    return Mustache.render(template, view, partials);
};

Here is how I'm invoking it in the main.js file:

require(['mustache'], function(Mustache){
    var view = {test:'TEST'};
    var temp = '{{test}}!!!';
    $.mustache(temp,view);
});

回答1:

The Problem

Mustache is AMD-aware so when you just load the mustache.js file, it detects that it is running with an AMD-loader (RequireJS), calls define and does not leak anything into the global space. Yay! This is perfect behavior for an AMD-aware module. And if there is no AMD-loader, it does the typical thing: export a single symbol into the global space (Mustache).

However, when you use the jQuery plugin that is available with the Mustache codebase you get an eldritch abomination, a creature which is both AMD-aware and yet ignores AMD, which does not leak anything to the global space and yet depends on leaking things to the global space. Basically, what happens is that Mustache's core, which is AMD-aware is wrapped in code that adds the jQuery support but this code is not AMD-aware. Altogether, this code will work well only if it is not loaded in an environment where there is an AMD-loader present.

The error you get is because while Mustache does not leak anything to the global space, the jQuery plugin expects to find Mustache in the global space.

Options

There are a number of options open to you:

  1. Don't use the jQuery plugin bundled with Mustache.

  2. Use ugly RequireJS hacks to make it work. This includes deliberately leaking Mustache into the global space. (Yuck!) Declaring a shim for the mustache module even though it calls define. (When I tried it, it worked but I would not bet on this being stable at all, because shim is for code that does not call define.)

  3. Wrap the wrapper (ha!) into boilerplate that makes it behave.

Getting the Wrapper to Behave

I prefer option 3. This can be automated using Grunt or make, rake, what-have-you. The recipe is to concatenate 4 files in this order:

  1. Code to put before Mustache's jQuery wrapper:

    (function (factory) {
        // If in an AMD environment, define() our module, else use the
        // jQuery global.
        'use strict';
        if (typeof define === 'function' && define.amd)
            define(['jquery', 'mustache'], factory);
        else
            factory(jQuery, Mustache);
    }(function (jQuery, Mustache) {
        'use strict';
    
  2. The file mustache/wrappers/jquery/mustache.js.pre. This is bundled with Mustache.

  3. The file mustache/wrappers/jquery/mustache.js.post. This is bundled with Mustache.

  4. Code to put after Mustache's jQuery wrapper:

    }));
    

Once these 4 files are combined together in this order, you get a file that you can load with RequireJS.

Here's an example. In the following example, all third party components were installed with Bower. The js subdirectory contains the result of concatenating the four files as explained above and this result is named jquery.mustache.js.

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/xhtml; charset=utf-8"/>
    <script type="text/javascript" src="bower_components/requirejs/require.js"></script>
    <script id="template" type="text/html">
      <p>Hi, {{name}}</p>
    </script>
  </head>
  <body>
    <script>
      require.config({
        baseUrl: ".",
        paths: {
          jquery: "bower_components/jquery/dist/jquery",
          mustache: "bower_components/mustache/mustache",
          "jquery.mustache": "js/jquery.mustache"
        }
      });
      require(["jquery", "jquery.mustache"], function ($) {
        console.log($.mustache);
        $(document.body).append($("#template").mustache({name: "blah"}));
      });
    </script>
  </body>
</html>

I've run this in Chrome without any problems.



回答2:

The mustache module does not export a global Mustache object when it is invoked through the CommonJS interface (as require.js does) - it does export this object when the module is included via the tag, instead.

You can see this on line 23 of the jquery.mustache.js source: jquery.mustache.js (gist)

The jQuery plugin refers to this global Mustache object, which does not exist when jquery.mustache.js is imported by require.js.

You need to tell require.js to export the Mustache object - this is done through the require.config settings, using a shim property:

require.config({
  shim: {
    'mustache': {
        exports: 'Mustache'
    }
  }
});

Include this code before the call to require(), and the (global) Mustache object will be available to the $.mustache function (i.e. it will work).