4

In complex client side projects, the number of Javascript files can get very large. However, for performance reasons it's good to concatenate these files, and compress the resulting file for sending over the wire. I am having problems in concatenating these as the dependencies are included after they are needed in some cases.

For instance, there are 2 files:

/modules/Module.js <requires Core.js>
/modules/core/Core.js

The directories are recursively traversed, and Module.js gets included before Core.js, which causes errors. This is just a simple example where dependencies could span across directories, and there could be other complex cases. There are no circular dependencies though.

The Javascript structure I follow is similar to Java packages, where each file defines a single Object (I'm using MooTools, but that's irrelevant). The structure of each javascript file and the dependencies is always consistent:

Module.js

var Module = new Class({
    Implements: Core,

    ...
});

Core.js

var Core = new Class({
    ...
});

What practices do you usually follow to handle dependencies in projects where the number of Javascript files is huge, and there are inter-file dependencies?

Anurag
  • 140,337
  • 36
  • 221
  • 257

9 Answers9

2

Using directories is clever, however, I think you might run into problems when you have multiple dependencies. I found that I had to create my own solution to handle this. So, I created a dependency management tool that is worth checking out. (Pyramid Dependency Manager documentation)

It does some important things other javascript dependency managers don't do, mainly

  1. Handles other files (including inserting html for views...yes, you can separate your views during development)
  2. Combines the files for you in javascript when you are ready for release (no need to install external tools)
  3. Has a generic include for all html pages. You only have to update one file when a dependency gets added, removed, renamed, etc

Some sample code to show how it works during development.

File: dependencyLoader.js

//Set up file dependencies
Pyramid.newDependency({
    name: 'standard',
    files: [
    'standardResources/jquery.1.6.1.min.js'
    ]
});

Pyramid.newDependency({
name:'lookAndFeel',
files: [
    'styles.css',
    'customStyles.css'
    ]
});

Pyramid.newDependency({
name:'main',
files: [
    'createNamespace.js',
    'views/buttonView.view', //contains just html code for a jquery.tmpl template
    'models/person.js',
    'init.js'
    ],
    dependencies: ['standard','lookAndFeel']
});

Html Files

<head>
    <script src="standardResources/pyramid-1.0.1.js"></script>
    <script src="dependencyLoader.js"></script>
    <script type="text/javascript">
        Pyramid.load('main');
    </script>
</head>
i8abug
  • 1,692
  • 3
  • 19
  • 31
1

This may be crude, but what I do is keep my separate script fragments in separate files. My project is such that I'm willing to have all my Javascript available for every page (because, after all, it'll be cached, and I'm not noticing performance problems from the parse step). Therefore, at build time, my Ant script runs Freemarker via a little custom Ant task. That tasks roots around the source tree and gathers up all the separate Javascript source files into a group of Maps. There are a few different kinds of sources (jQuery extensions, some page-load operations, so general utilities, and so on), so the task groups those different kinds together (getting its hints as to what's what from the script source directory structure.

Once it's built the Maps, it feeds those into Freemarker. There's a single global template, and via Freemarker all the script fragments are packed into that one file. Then that goes through YUI compressor, and bingo! each page just grabs that one script, and once it's cached there's no more script fetchery over my entire site.

Dependencies, you ask? Well, that Ant task orders my source files by name as it builds those maps, so where I need to ensure definition-use ordering I just prefix the files with numeric codes. (At some point I'm going to spiff it up so that the source files can keep their ordering info, or maybe even explicitly declared dependencies, inside the source in comment blocks or something. I'm not too motivated because though it's a little ugly it really doesn't bother anybody that much.)

Pointy
  • 405,095
  • 59
  • 585
  • 614
  • i like the idea of using directory names and file extensions to group things together. funny you say that about the dependencies, because i was doing just that (prefixing underscores) till today when it really started annoying me after things stopped working whenever few js files were added to the project. – Anurag Mar 07 '10 at 01:18
1

There is a very crude dependency finder that I've written based on which I am doing the concatenation. Turns out the fact that its using MooTools is not so irrelevant after all. The solution works great because it does not require maintaining dependency information separately, since it's available within the javascript files itself meaning I can be super lazy.

Since the class and file naming was consistent, class Something will always have the filename Something.js. To find the external dependencies, I'm looking for three things:

  1. does it Implement any other classes
  2. does it Extend any other classes
  3. does it instantiate other classes using the new keyword

A search for the above three patterns in each javascript file gives its dependent classes. After finding the dependent classes, all Javascript files residing in any folder are searched and matched with this class name to figure out where that class is defined. Once the dependencies are found, I build a dependency graph and use the topological sort algorithm to generate the order in which files should be included.

Anurag
  • 140,337
  • 36
  • 221
  • 257
0

I say just copy and paste this files to a one file in an ordered way. Each file will have a starting and ending comment to distinguish each particular code.

Each time you updated one of the files, you'll need to updated this file. So, this file need to contain only finish libraries, that not going to changes in the near time.

Fitzchak Yitzchaki
  • 9,095
  • 12
  • 56
  • 96
  • lol, i have over 100 js files and this is just the early stages.. could create a new full time job just for this purpose :P – Anurag Mar 07 '10 at 00:52
  • a slightly modified solution to yours could work easily by creating another file `dependencies.list` that just lists all js files (with full paths) in the order that should be included. now a build script can create a `temp` file, go through the contents of each file listed in `dependencies.list` and keep appending to `temp`. that way only one file has to be changed and the ordering is maintained. this too is going to get complicated with just two people working on this. – Anurag Mar 07 '10 at 01:00
0

Your directory structure is inverted...

Core dependencies should be in the root and modules are in subdirs.

scripts/core.js
scripts/modules/module1.js

and your problem is solved.

Any further dependency issues will be indicative of defective 'class'/dependency design.

Sky Sanders
  • 36,396
  • 8
  • 69
  • 90
  • that was just an example, maybe the names i chose were not very suitable. but the problem is basically about dependencies. has nothing to do with core components. – Anurag Mar 07 '10 at 01:11
  • @Anurag - answer is the same regardless. If you are loading scripts from a recursive directory scan, place the dependencies closer to the root than the dependents. simple. – Sky Sanders Mar 07 '10 at 01:27
  • Our structure is like packages (in Java or whatever). If the package structure is well designed, there is no restriction to what should be closer to the root. Moreover, there could be dependencies within the same package (which we do have), and renaming the files to appear first in some listing is hacking it away anyways (which we were doing, till now). – Anurag Mar 08 '10 at 06:00
  • @Anurag - you said 'The directories are recursively traversed', this is the problem I saw with your directory structure in the context of your loading strategy. And JS is not Java. There is no intrinsic linker. If you want that sort of capability you already know the options, but that is not what you described. just my .2 pesos – Sky Sanders Mar 08 '10 at 07:20
  • I included my current solution in phrasing the question which is not always the best thing to do, especially when we're heading south with our answer. Anyways, I have written a hacky, messy automated dependency finder that seems to be doing the job for now. – Anurag Mar 08 '10 at 10:06
0

Similar to Mendy, but I create combined files on server-side. The created files will also be minified, and will have a unique name to omit cache issues after an update.

Of course, this practice only makes sense in a whole application or in a framework.

Frunsi
  • 7,099
  • 5
  • 36
  • 42
  • do you define the dependencies somewhere for the server side script to consider while concatenating? – Anurag Mar 07 '10 at 01:21
  • The server side script defines the list of files that are required. And there is not a single js file, but something like `array('media.js'=>array('a.js','b.js',...),'mmgt.js'=>...)`. So, a single server-side script may require media.js, another may require media.js and mmgt.js and so on. – Frunsi Mar 07 '10 at 02:19
0

I think your best bet if at all possible, would be to redesign to not have a huge number of javascript files with interfile dependencies. Javascript just wasn't intended to go there.

dkretz
  • 37,399
  • 13
  • 80
  • 138
  • it's for an RIA where all views and all logic is built from the ground up using javascript. and it talks to a remote service only when data is needed. actually most of the files are really short, maybe just 6-10 lines long, but it helps a lot in managing complexity. – Anurag Mar 07 '10 at 01:07
0

This is probably too obvious but have you looked at the mootools Core Depender: http://mootools.net/docs/more/Core/Depender

Dimitar Christoff
  • 26,147
  • 8
  • 50
  • 69
  • Core Depender looks really good, but my current project is in Rails, and I don't think there was a rails component for Dependent.Server. Plus, as long as I stick to certain conventions, my approach should work without manually specifying dependencies. – Anurag Mar 08 '10 at 19:57
  • 1.3 coming out soon with a LOT of goodies for server side js, including CommonJS support, rails stuff and so forth. wait :) – Dimitar Christoff Mar 09 '10 at 09:02
0

One way to break the parse-time or load-time dependencies is with Self-Defining Objects (a variation on Self-Defining Functions).

Let's say you have something like this:

var obj = new Obj();

Where this line is in someFile.js and Obj is defined in Obj.js. In order for this to parse successfully you must load or concatenate Obj.js before someFile.js.

But if you define obj like this:

var obj = {
   init: function() {
        obj = new Obj();
    }
};

Then at parse or load time it doesn't matter what order you load the two files in as long as Obj is visible at run-time. You will have to call obj.init() in order to get your object into the state you want it, but that's a small price to pay for breaking the dependency.

Just to make it clearer how this works here is some code you can cut and paste into a browser console:

var Obj = function() {
    this.func1 = function ( ) {
        console.log("func1 in constructor function");
    };
    this.init = function () {
        console.log("init in constructor function");
    }
};

var obj = {
    init: function() {
        console.log("init in original object");
        obj = new Obj();
        obj.init();
    }
};

obj.init();
obj.func1();

And you could also try a module loader like RequireJS.

grahamesd
  • 4,773
  • 1
  • 27
  • 27