Question

By now I've had only one javascript file in my project that contains tons of functions only few of which are used by more than one web page..As the amount of code involved rises I can see the the file gets messy and way too long. I'm thinking of having a separate javascript file for each web page so that:

  1. I don't have to load the one giant javascript file even for those pages that make very little use of it.
  2. The file doesn't get even more messy

But I don't know if this would have any negative that I'm not aware of.

Was it helpful?

Solution

There are a number of options here, focused purely on the side of organizing your files it's worth considering using a library like require.js if what you're working on is fairly big. This however is meant for applications and even though you can easily use it for other things as well the idea is that you restructure your code into stand alone modules which might or might not be sensible in your case.

If however the javascript we're talking about is tightly coupled with the content of the page and thus in it's very setup not reusable for other pages it can be quite correct to use inline <script> tags rather than external javascript files.

And one last note, require.js or any setup with multiple javascript will give a slight overhead regarding http requests, but I would not worry about that too much. Require.js or similar libraries will even load this async for you, so all in all it will probably be a bit more efficient than one huge file which possibly isn't used for a big part for any given visitor (though that depends whether visitors will in the end visit all pages or not).

OTHER TIPS

Less files leads to less clean code which in turn means slower coding and harder to debug. More files means More HTTP Requests and a Slower website. So develop as many files as you want to organize your code and to keep development manageable.

Having multiple files would indeed help you have things organized, but at the same time it would require browser to make multiple requests to the server, which may impact site load time.

This is why many people use nowadays tools such as RequireJS.

In a nutshell, it'll let you define as many small modules as you wish (each in separate file), and then it will load them asynchronously.

If you marry it with some build tool such as Grunt, you can have a webpage which will load only two files (RequireJS library and your JS file, i.e. main.js), but main.js will be the result of concatenating many smaller modules.

This way you keep number of request to the minium and, at the same time, your code is organised and splitted in small modules The cost? You introduce an additional build step. This build step can be further automated, though, using tools like grunt-contrib-watch.


Here are a couple of snippets for a site which uses one main.js file and also depends on jQuery which will be loaded from Google CDN:

First, main.js: It's a RequireJS module (which is a AMD module) and looks like:

// Tell RequireJS that jQuery dependency should be loaded from the given URL.
// Without this, RequireJS would try to find jQuery.js file locally.
requirejs.config({
  paths: {
    'jQuery': '//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min',
  },
  shim: {
    'jQuery': {'exports': 'jQuery'},
  }
});

// You define that the module below depends on jQuery and two custom modules.
define(['jQuery', 'module1', 'module2'], function ($, Module1, Module2) {
  // Here you can access $, Module1 and Module2
});

Then use Grunt's grunt-contrib-requirejs plugin with the configuration similar to (Gruntfile.js):

requirejs: {
  compile: {
    options: {
      baseUrl: 'src/js',         // modules dir, which contains main.js, module1.js etc.
      out: 'static/js/main.js',  // output file
      name: 'main',
      paths: {
        'jQuery': 'http://lorem.ipsum1'  // jQuery will be loaded separately from CDN
      },
      optimize: 'uglify2'        // minify output file
    }
  }
}

Finally, load the RequireJS script and tell it to use main.js file as an entry point for your site (index.html):

<script src="js/require.min.js" data-main="js/main.js"></script>

Now days, I develop all of my javascript in separated files with a set of logical folders (Services, Controllers, Lib, etc).

However I do it with Gulp and Bower on Node.js. Note, I don't use Node.JS for my server side code, just my front end assets. Think of it as a distribution tool for client side assets. I use Gulp-Watch to monitor my SRC folder and auto transform the final javascript file as I work on the source.

So I get the benefit of having everything be separated out logically, but still get the performance of a single compressed JS file for Production.

Here is an example bower.json file:

{
  "name": "MyApp",
  "private": true,
  "dependencies": {
    "jquery": "2.2.4",
    "bootstrap": "v4.0.0-alpha.3",
    "angular": "^1.5.8",
    "font-awesome": "fontawesome#^4.6.3",
    "angular-ui-router": "^0.3.1",
    "angular-cookies": "^1.5.8",
    "angular-ui-select": "^0.19.4",
    "angular-ui-tree": "^2.17.0",
    "angular-toastr": "^2.0.0",
    "angular-animate": "^1.5.8",
    "angular-sanitize": "^1.5.8",
    "angular-chart": "^0.5.0",
    "angular-loading-bar": "^0.9.0",
    "angular-messages": "^1.5.8"
  },
  "devDependencies": {}
}

And here's the Gulp-File

/// <binding Clean='clean' />
"use strict";

var gulp = require("gulp");
var sass = require("gulp-sass"); //Syntactically Awesome Style Sheets
var sourcemaps = require('gulp-sourcemaps'); //Generate source maps for css and js
var autoprefixer = require('gulp-autoprefixer'); //auto prefix browser specific css 
var cleanCss = require('gulp-clean-css'); //minimize css
var rename = require('gulp-rename'); //rename file in gulp stream
var concat = require('gulp-concat'); //concat mutliple files in gulp stream into one
var runSequence = require('run-sequence'); //run mutliple gulp taxes in parrellel or sequence
var uglify = require('gulp-uglify'); //minimize js

var webroot = "./wwwroot/"; //the root folder where the css and js is going

var paths = {
    css: webroot + "css/", //path to my css
    scss: webroot + "scss/", //path to my scss src files
    destJs: webroot + "js/", //path to my js source files (also the destination)
    lib: webroot + "/lib/" //path to my lib folder for storing libraries
};

//build both the dev and production styles
gulp.task('build-styles', function (done) {
    return runSequence(["build-styles-dev", "build-styles-prod"]);
});



//sass transforms all my scss files individually (producing separate css files for each library I am using)
    gulp.task('build-styles-dev', function () {
        var sassOptions = {
            errLogToConsole: true,
            outputStyle: 'expanded',
            sourceMap: true
        };
        return gulp.src([ //include all my libraries css files that isn't sass based in the front of the stream
                paths.lib + '/angular-loading-bar/build/loading-bar.css',
                paths.lib + '/angular-toastr/dist/angular-toastr.css',
                paths.lib + '/angular-ui-select/dist/select.css',
                paths.lib + '/angular-ui-tree/dist/angular-ui-tree.css',
                paths.lib + '/tether/dist/css/tether.css',
                paths.lib + '/tether/dist/css/tether-theme-basic.css',
                paths.scss + '/site.scss' //my sass file (does imports etc in it)
        ])  
            .pipe(sass({ errLogToConsole: true, outputStyle: 'expanded', sourceMap: true }).on('error', sass.logError))
            .pipe(autoprefixer()) //autoprefix browser specific css
            .pipe(gulp.dest(paths.css)) //output all the sass and auto prefixed css files based on the source
            .resume();
    });

    //same as above task except this one doesn't output the individual files.  It concats them all together into one file, compresses it, and generates the source maps for it.
    gulp.task('build-styles-prod', function () {
        var sassOptions = {
            errLogToConsole: true,
            outputStyle: 'expanded',
            sourceMap: true
        };
        return gulp.src([
                paths.lib + '/angular-loading-bar/build/loading-bar.css',
                paths.lib + '/angular-toastr/dist/angular-toastr.css',
                paths.lib + '/angular-ui-select/dist/select.css',
                paths.lib + '/angular-ui-tree/dist/angular-ui-tree.css',
                paths.lib + '/tether/dist/css/tether.css',
                paths.lib + '/tether/dist/css/tether-theme-basic.css',
                paths.scss + '/site.scss'
        ])
            .pipe(sass({ errLogToConsole: true, outputStyle: 'expanded', sourceMap: true }).on('error', sass.logError))
            .pipe(autoprefixer())
            .pipe(concat("compiled.css"))
            .pipe(gulp.dest(paths.css))
            .pipe(sourcemaps.init({ loadMaps: true }))        
            .pipe(cleanCss())
            .pipe(rename({ suffix: '.min' }))        
            .pipe(sourcemaps.write('.'))        
            .pipe(gulp.dest(paths.css))
            .resume();
    });

    //grabs all the library scripts and compresses them into one js file
    gulp.task('build-scripts', function () {
        return gulp.src([
            paths.lib + 'jquery/dist/jquery.js',
            paths.lib + 'tether/dist/js/tether.js',
            paths.lib + 'angular/angular.js',
            paths.lib + 'angular-messages/angular-messages.js',
            paths.lib + 'angular-animate/angular-animate.js',
            paths.lib + 'angular-cookies/angular-cookies.js',
            paths.lib + 'angular-chart/angular-chart.js',
            paths.lib + 'angular-loading-bar/build/loading-bar.js',
            paths.lib + 'angular-toastr/dist/angular-toastr.js',
            paths.lib + 'angular-sanitize/angular-sanitize.js',
            paths.lib + 'angular-ui-router/release/angular-ui-router.js',
            paths.lib + 'angular-ui-select/dist/select.js',
            paths.lib + 'angular-ui-tree/dist/angular-ui-tree.js',
            paths.lib + 'bootstrap/dist/js/bootstrap.js',
            paths.js + 'app.js', //Load app.js first
            paths.js + 'services/**/*.js', //Load all angular services next
            paths.js + 'directives/**/*.js', //load directives after services
            paths.js + 'controllers/**/*.js', //load angular controllers last after services and directives
            paths.js + 'appLast.js', //A tail script I have load last for end of page logic, preventing the need for me to use document ready since this loads in the page footer.
        ])
            .pipe(sourcemaps.init())
            .pipe(gulp.dest(paths.destJs)) //output all the files individually (so file names need to be unique accross the whole system, generally not a problem)
            .pipe(concat('scripts.js')) //concat all the scripts together into one file called scripts.js
            .pipe(uglify()) //minimize all the scripts
            .pipe(rename({ suffix: '.min' })) //rename to scripts.min.js
            .pipe(sourcemaps.write('.')) //write the source maps
            .pipe(gulp.dest(paths.destJs)); //output scripts.min.js
    });

The missing part of my gulp file is the app scripts, which looks something like this (haven't finished it for this app yet).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top