简体   繁体   中英

How to structure JavaScript applications in development and production

I'm building my first (non-spaghetti) large JavaScript application. While the introduction of RequireJS and other dependency management frameworks for JavaScript makes it easier to split up files, I'm unclear as to how push a large code base to production . What I would like is a way to aggregate and minify/uglify my JavaScripts for production, using something like Ready.js and UglifyJS . Or some other approach, if it makes sense.

How do developers with large JavaScript apps in production handle their structure in development and in production?

I could, for example, use RequireJS in development and then use Ready/Uglify to aggregate/minify. But then my code would have pointless require()'s scattered throughout. I'm sure there's a better approach.

I'm also confused about including jQuery within these files. Should I be wrapping each and every separate jQuery file (for example Backbone views that use jQuery) within their own separate $(document).ready(function(){...}) ? That seems very un-DRY.

You can use the RequireJS optimizer . The requires are not pointless, even in the compressed application, because you always have to get a reference to the module. The optimizer docs also say, that it will not include a module that has been loaded with a variable like

var mods = someCondition ? ['a', 'b'], ['c', 'd'];
require(mods);

I think that RequireJS should wait till the DOM is ready and all modules have been loaded, so you don't need to wrap every file.

That said, my favourite package manager is still StealJS . It can kick out unnecessary calls in a production build and a module is always encapsulated in a closure that gets the jQuery object passed and waits till the DOM is ready and all scripts have been loaded. Unfortunately it is not yet compatible with the CommonJS module specifications.

I've found YUI Builder works well for me. I'm not sure how useful it is if you're not using YUI 3, but there's a good chance you could adapt it to your needs.

On the other hand, have you taken a look at RequireJS Optimizer ?

Regarding document.ready handling; I think it's good practice to not let code in modules do anything until they're initialized or called. So I would have a single $(document).ready() in a <script> tag at the bottom of the page itself, which "glues together" the modules that are needed on that page.

Anti-spaghetti way

To effectively develop and easily maintain a JavaScript application, as opposed to a number of ad hoc scripts or subpar non-transparent automation, you can use native Qooxdoo application . It's impossible to cover Qooxdoo without writing too much, but in case of native app (don't confuse the term with C or Java, Qooxdoo is pure JavaScript) it is described as:

For applications using custom HTML/CSS-based GUIs instead of qooxdoo's widget layer.

Thus such application doesn't use any Qooxdoo UI layers, but just code structure facilities and build tools. Code in Qooxdoo organised in classes, one per file like in Java. I may look like this:

/**
 * @use(website.library.MosaicFlow)
 */
qx.Class.define('website.controller.Gallery', {

  extend : website.controller.Abstract,

  members : {

    _baseUrl : 'https://picasaweb.google.com/data/feed/api',


    _render : function(photos)
    {
      q('.preloader').remove();

      q.template.get('gallery-template', {'photos': photos}).appendTo('#gallery-container');
      var gallery = window.jQuery('#gallery-container .gallery').mosaicflow({
        'minItemWidth'    : 256,
        'itemSelector'    : '.photo',
        'autoCalculation' : false
      });
      gallery.trigger('resize');
    },

    _convert : function(item, index)
    {
      try
      {
        return {
          'url'     : item.content.src,
          'summary' : item.summary.$t,
          'thumb'   : item.media$group.media$thumbnail[0]
        };
      }
      catch(ex)
      {
        this.debug('failed to convert', index, item);
        return null;
      }
    },

    _onLoadSuccess : function(event)
    {
      var request  = event.getTarget();
      var response = request.getResponse();
      if(!qx.lang.Type.isObject(response) || !('feed' in response))
      {
        request.debug('Malformed response received');
      }
      else
      {
        this._render(response.feed.entry.map(this._convert, this).filter(function(item)
        {
          return !!item;
        }));
      }
    },

    _onLoadFail : function()
    {
      this.debug('Picasa search failed');
    },

    main : function()
    {
      var query   = /^\/gallery\/(\w+)$/.exec(window.location.pathname);
      var request = new qx.io.request.Jsonp(qx.lang.String.format('%1/all', [this._baseUrl]));
      request.setRequestData({
        'q'           : query[1],
        'thumbsize'   : 300,
        'max-results' : 20,
        'alt'         : 'json'
      });
      request.setTimeout(16000);
      request.setCache(false);
      request.addListener('fail',    this._onLoadFail,    this);
      request.addListener('success', this._onLoadSuccess, this);
      request.send();
    }

  }

});

Qooxdoo object model takes advantage of both worlds. In has qualities of mature platforms like Java, at the same time it's modern and dynamic, providing classes, inheritance, interfaces, mixins, events, properties, data-binding and more. Because every class has a defined name and located in a namespace tree, the Qooxdoo generator can take advantage of it. It parses your classes and builds their syntax trees. Then it resolves dependencies. Ie when you refer to another class, like website.controller.Abstract . This leads to the dependency graph, which is used to load scripts in proper order. Note, that it all is automatic and transparent to a developer, and files are loaded as is. There's no propcessing like in case of CommonJS, there's no ugly boilerplate to wrap you code in like with AMD.

As you can see in example above it is possible to deal with external non-qooxdoo libraries. You can just need to write a dummy wrapper for a library to include it in build process.

Development and production environments

You develop building your app (build is needed only when new dependency is introduced in code) with so called source target. Your application files are loaded in dependency order, one by one. Framework files can be loaded one by one, or which is better option, are built in several big chunks. In production environment your application code is built with build target. You have an option to produce single output file, or have partial build, where code is chunked in big files (you can control their size). Partial build may look like this (optimised/gzipped):

├── [127/64kB]  website.f6ffa57fc541.js
├── [100/33kB]  website.f86294b58d1a.js
└── [361/110kB] website.js

Note that parts are loaded on-demand on the pages that require them.

http://example.com/
└── website.js
http://example.com/article
└── website.js
http://example.com/form
└── website.js
    └── website.f86294b58d1a.js
http://example.com/gallery
└── website.js
    └── website.f6ffa57fc541.js
http://example.com/geo
└── website.js

Because Qooxdoo doesn't target full-blown website builds yet, but is only providing a platform of native application type, you need to code the entry to the application and some basics, like bootstrapping, URL routing, etc. I tried to address this with qooxdoo-website-skeleton , which examples above belong to. You're free to use it, or write your own.

Finally note that it may be not as easy to start as with average JavaScript library, but the complexity is proportional to the eventual benefit.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM