Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a plugin, that modifies the original module only when required?

I have a plugin extending an original module.
It should only modify the module, when explicitly required.

Problem:
As soon as it is required once, the original module is modified forever, also for cases where the plugin is not a dependency.
The order doesn't matter here, it's enough to require the plugin once.

Example:

define("main", [], function() {
    return {opt: "A"};
});
define("plugin", ["main"], function(obj) {
    obj.opt = "B";
});
require(["main", "plugin"], function(obj) {
    console.log(obj.opt); // should log B
});
require(["main"], function(obj) {
    console.log(obj.opt); // should log A but logs B
});

I guess the way to go is to somehow tell require to always reload main from source instead of using the cached version.
I have no idea how, though.
Or maybe there's an even more elegant way?

Please enlighten me, guys.

Fiddle: http://jsfiddle.net/r75e446f

UPDATE: Some might find it important to know that I need this for my karma unit test environment to test a module with and without the plugin.

UPDATE2: Look below for my own solution.

like image 740
Jan Paepke Avatar asked Dec 17 '14 19:12

Jan Paepke


2 Answers

RequireJS modules are singletons. If you load main once, twice, 10 times, you are always going to get the same module. And so if you modify its state, it is modified for all modules that use it. It is possible to tell RequireJS to undefine the module but I do not recommend it as it will just make your code obscure.

If I wanted to do what you are trying to do I'd design my code something like this:

<script>
  define("main", [], function() {
      function Main (opt) {
          this.opt = opt;
      }
      return Main;
  });
  define("plugin1", [], function() {
      return {
          install: function (main) {
              main.opt += " plugin1";
          }
      };
  });
  define("plugin2", [], function() {
      return {
          install: function (main) {
              main.opt += " plugin2";
          }
      };
  });
  // Case 1: no plugins
  require(["main"], function(Main) {
      var main = new Main("A");
      console.log(main.opt);
  });
  // Case 2: only plugin1
  require(["plugin1", "main"], function (plugin1, Main) {
      var main = new Main("A");
      plugin1.install(main);
      console.log(main.opt);
  });
  // Case 3: only plugin2
  require(["plugin2", "main"], function (plugin2, Main) {
      var main = new Main("A");
      plugin2.install(main);
      console.log(main.opt);
  });
  // Case 4: plugin1 and plugin2
  require(["plugin1", "plugin2", "main"], function (plugin1, plugin2,
                                                    Main) {
      var main = new Main("A");
      plugin1.install(main);
      plugin2.install(main);
      console.log(main.opt);
  });

Basically, make what is common to all cases a Main class which can be initialized at construction and which can be modified by plugins. Then each plugin can install itself on Main. The code above is a minimal illustration of how it could be done. In a real project, the final solution would have to be designed to take into account the specific needs of the project.

like image 111
Louis Avatar answered Oct 25 '22 05:10

Louis


If you don't want the original module to be modified for all modules which use it, then your plugin should not modify the original module. Instead, have the plugin return a modified copy of the original module instead.

define("main", [], function() {
  return {opt: "A"};
});
define("plugin", ["main"], function(obj) {
  var decorator = {}
  for (var key in obj) { decorator[key] = obj[key];}
  decorator.opt = "B";
  return decorator
});
require(["main", "plugin"], function(obj, plugin) {
  console.log(plugin.opt); // should log B
});
require(["main"], function(obj) {
  console.log(obj.opt); // should log A but logs B
});

This will work without any complications if your original object is a simple struct-like object without any functions. If there are functions, or if your original object was constructed using the Module Pattern, then there is a strong possibility of subtle errors in the copy, depending on how the methods were defined.

EDIT 2015-01-13: The OP clarified his question that he would like a way for his tests to be able to run both modified and unmodified original module without having to reload the page. In that case, I would recommend using require.undef to unload the main module and then reload it without having to reload the entire page.

like image 35
I-Lin Kuo Avatar answered Oct 25 '22 06:10

I-Lin Kuo