How do I professionally structure my module-pattern Javascript projects? [closed]
Asked Answered
E

2

13

I've read about the Revealing Module Pattern and I love it. But what about large projects where the 'master-object' has tens of sub-objects and maybe hundreds of functions. I wouldn't want to be the one to place all that code in one anonymous function closure.

So how are large module pattern projects managed?

Epizootic answered 22/5, 2011 at 1:59 Comment(0)
C
12

You can use augmentation to separate your modules out in to different files. Once you are ready for a production release, you can concatenate those files into one or several files.

File1 defines module M.n

var M = M || {};
M.n = M.n || {};

(function (self) {
    self.doSomething = function () {
        console.log("something");
    };
})(M.n);

File2 defines module M.n.p

var M = M || {};
M.n = M.n || {};
M.n.p = M.n.p || {};

(function (self) {
    self.doSomethingElse = function () {
       console.log("something else");
    };
})(M.n.p);

Now in your "main" script you can freely use the members of these modules.

M.n.doSomething();
M.n.p.doSomethingElse();

Defining the modules can be a little tedious, but you should be able to whip something up to automate it. In the past, I've used this little script to help make it easier, but feel free to make your own. You may even be able to bake in dependency management with consistent file naming.

 var namespace = function(path, context, args) {
  var finalLink = namespace._generateChain(path, window);
  context.apply(finalLink, [finalLink].concat(args));
 };

 namespace._generateChain = function(path, root) {
  var segments = path.split('.'),
      cursor = root,
      segment;

  for (var i = 0; i < segments.length; ++i) {
   segment = segments[i];
   cursor = cursor[segment] = cursor[segment] || {};
  }

  return cursor;
 };

To use:

namespace("M.n.p", function (self) {
   self.doSomethingElse = function () {
      console.log("something else");
   };
});

If for some reason you want to include a variable under a different alias, you can pass it to the namespace function and it will be passed to the function as an argument.

namespace("M.n.p", function (self, $) {
   self.doSomethingElse = function () {
      $("p").text("something else");
   };
}, jQuery);
Counterchange answered 22/5, 2011 at 2:17 Comment(4)
How exactly would concatenation remove the many closures around each module?Spiracle
@Drew: It wouldn't. Concatenation would used to reduce the number of HTTP requests (from script loading) in the production environment. Likewise, you would pass it through a minifier to reduce the code size.Counterchange
Sorry I thought I read that in your explanationSpiracle
just sayin' - there is an error in the third line of your second code block: M.p should be M.n.pMagnetic
S
3

Use RequireJS to organize things. For shared logic, the shared methods must be stored on a globally accessible namespace, or accessed via require(). I didn't like having to make many require() calls for the application code, so I include modules in chunks and each attach to a particular namespace via define inclusion.

//Core.js
define(function(){
  return {
    ns: 'global namespace'
  };
});

//newMethod.js
define(['core'], function( ns ){
  ns.newMethod = function(){ console.log( 'my new method ' ); }
});

//Application code
require(['newMethod'], function( namespace ) {
   console.log( namespace.ns ); //global namespace
   namespace.newMethod(); //'my new method'
});
Spiracle answered 22/5, 2011 at 3:1 Comment(2)
Would I still need to create a script tag for every module I want to load?Epizootic
You will need to read how RequireJS works, but you will be creating one script to configure RequireJS. After this, the other scripts are loaded as they are needed. These are asynchronously loaded when they are needed via your require() and define() calls.Spiracle

© 2022 - 2024 — McMap. All rights reserved.