8
\$\begingroup\$

I'm writing my first JavaScript application (no 3rd party libraries used) and looking for some high-level guidance on how to better organize the code into separate classes & files.

Here is a simplified/contrived example to illustrate my current organizational layout/problems:

game.js:

/* Lots of constants, enums, etc defined here.
   Can't define them inside Game function as
   they are used by Graphics and ui_player classes too.
*/

function Game(ui) {

    /* Lots of things defined here which are exclusive to Game class. */

    var graphics = new Graphics();
    graphics.load();
    ui.setCanvas(graphics.canvas);

    this.start = function() {
        mainLoop();
    };

    function mainLoop() {
        /* ... */
        ui.player.updateAbc(abc);
        /* ... */
        paint();
    }

    function paint() {
        graphics.renderAll()
    }

    /* ... */
}

graphics.js:

function Graphics() {

    /* Some constants defined here that are exclusive to Graphics class. */

    var canvas = this.canvas = ...;
    var ctx = canvas.getContext("2d");

    /* ... */

    this.renderAll = function() {
        ctx.drawImage(...);
    };

    /* ... */
}

ui.js:

/* Lots of constants related to UI classes here. */

function ui() {
    var div = this.div = ...;
    var canvas;            

    /* ... */

    var player = this.player = new ui_player();
    div.appendChild(player.div);

    this.setCanvas(c) {
        if (canvas)
            div.removeChild(canvas);
        canvas = c;
        div.appendChild(canvas);
    };

    /* ... */
}

ui_player.js:

 function ui_player() {
     var div = this.div = ...;

     /* ... */

     this.updateAbc = function(abc) {
         div.innerHTML = abc;
     };
 }

main.js:

 ui = new ui();
 game = new Game(ui);
 game.start();

What I dislike about this:

  • I feel that my scopes are clumsy.
  • I dislike the fact that I have constants in global scope because they are shared across multiple classes.
  • I dislike the fact that I have to pass ui to Game and then to Graphics, but I'm not sure if this is bad or if allowing every class to access others would be worse.

How can I improve my code structure? How can/should I put it all into a namespace hierarchy where classes can access classes above (and maybe even below) them?

P.S. - I'm compiling with Google Closure Compiler (advanced) if that makes any difference. No 3rd party libraries are used.

\$\endgroup\$
5
\$\begingroup\$

Using a closure

You can avoid putting constants in the global scope by wrapping everything in another closure, i.e. the code produced after the compiling process should look something like this:

(function() {
/* all your definitions here */
function ui() {
    // code here
}

function Game(ui) {
    // code here
}

function Graphics(ui) {
    // code here
}
}).call(this);

Thereby all the functions have access to your constants without cluttering the global namespace.

Supporting RequireJS/AMD

RequireJS is a library that aims to give you the possibility to import modules, something JavaScript lacks by default. It does so using 'dependency injection', i.e. passing all the things code requires to execute as parameters to a wrapping function. (You're already doing this in your code - kudos! Even though you seem not to like it, definitely stick to it!) This is considered a best practice in many programming languages - including JavaScript, duh - as it allows for nice modularization of code and great testability by replacing other modules that you don't want to test by mock implementations that might always return the same input, thereby you are able to test just parts of your code and locate errors more quickly. This is called unit testing - something you will never want to miss again.

By supporting RequireJS natively you will save your users from a lot headaches figuring out how exactly to get references to your code.
Doing so is quite easy, you check if the define function exists and use it or release everything to the global scope if it doesn't:

var lib = {
    ui: ui,
    Game: Game,
    Graphics: Graphics
}

if (typeof define === "function") {
    define("mylib", [], function() { return lib; });
} else {
    window.mylib = lib;
}

EDIT: When I wrote this I thought you wrote that you were creating your first library - not application. That being the case you can ignore the last paragraph, the first in this section still applies, though.

Building your library as a set of AMD modules

You can leverage the power of RequireJS/AMD for your own application. Just use any directory structure you like and use dependency injection to seperate them into files nicely. The RequireJS optimizer can then put them in a single file, you would still need RequireJS, though. Meet almond! It is only about 1kb minified+gzipped and defines a subset of the functionality of RequireJS that suffices to still use AMD modules inside a single file, which is ultimately what you want: a single file you can include in their HTML or load using Require. Now, unfortunately, this introduces another step in going from your source to a distributable JS file.

Using a sophisticated build tool

Because nobody wants to do 10 steps manually each time you change your code and want a new build, sophisticated build tools were created that invoke the compiler(s), do code linting, etc. I personally recommend Grunt. It is IMHO the best and most flexible option out there and big libraries, e.g. jQuery, use it as well.

Don't reinvent the wheel

There already are great libraries that can do some of the heavy lifting for you. While for a first application it is a good idea to do things yourself to learn how they work internally, once you start working on production code you want to rely on existing libraries, because

  1. that leaves you more time to work on your app and make it better
  2. they make your code more error proof:
    • many libraries have been around for quite a long time and hardly have bugs
    • bugs that are found will most likely be fixed quickly (and by other people, see (1))
  3. many libraries will make some design decisions for you. While you might think that cuts your freedom as a programmer (it certainly does), it also has a great advantage: Other people have spent a great deal on working out a good concept that will probably make your app easier to maintain in the end. So just choose one you like and go with it...
  4. let's face it: people who dedicate their whole time to working on a library probably have better implementations of stuff than you, so that might make your app faster

Additional Points

I will add further points here with edits, but wanted to give you access to existing sections asap ;)

\$\endgroup\$
4
  • 3
    \$\begingroup\$ Thanks in advance, I look forward to what you might add. \$\endgroup\$ – user35358 Jan 21 '14 at 21:23
  • \$\begingroup\$ I started writing this when the question was still on StackOverflow... I just forgot to delete that first sentence :D \$\endgroup\$ – Cu3PO42 Jan 21 '14 at 21:32
  • 1
    \$\begingroup\$ I thought Closure Compiler already did this (can't remember if by default or with an option)? \$\endgroup\$ – Dagg Jan 21 '14 at 21:39
  • \$\begingroup\$ I am honestly not sure if it does that by default, though it probably can with a switch... \$\endgroup\$ – Cu3PO42 Jan 21 '14 at 21:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.