16
RequireJS for modular JavaScript code Thomas Lundström, Softhouse @thomaslundstrom April 16, 2012 Scandinavian Developer Conference this presentation is about Require JS. * why use it * How it works * How it’s used * How to start using it Let’s start with some of the problems I’ve seen (and been guilty of) in web apps that use JavaScript during my tenure as a consultant

Using RequireJS for Modular JavaScript Code

Embed Size (px)

DESCRIPTION

Slides and notes from my lightning talk at Scandinavian Developer Conference, April 2012 in Gothenburg.

Citation preview

Page 1: Using RequireJS for Modular JavaScript Code

RequireJS for modular JavaScript code

Thomas Lundström, Softhouse@thomaslundstrom

April 16, 2012Scandinavian Developer Conference

this presentation is about Require JS.

* why use it* How it works* How it’s used* How to start using it

Let’s start with some of the problems I’ve seen (and been guilty of) in web apps that use JavaScript during my tenure as a consultant

Page 2: Using RequireJS for Modular JavaScript Code

JS is big

• Web-apps today contain large amounts of JavaScript

Even if you don’t run a JS framework, just “some HTML and JS”, you often have rather large JS files - quite often spaghetti-like with a large number of functions calling each other.

New JS-heavy apps often use one or more of the JS Frameworks that exist today, e.g. Backbone, Knockout, Ember, SproutCore etc, making it more and more important to think about componentizing and structuring your code.

Page 3: Using RequireJS for Modular JavaScript Code

Dependency management is hard

Top-level functions in the global namespace often lead to circular dependencies (which aren’t easily spotted - you basically need to wade through the JS code to find all dependencies).

2 Files - 5 different functions calling each other

Page 4: Using RequireJS for Modular JavaScript Code

Dev/deploy conflictRequirements conflict between development and deployment:

Devs want small discrete filessince it’s easier to debug and test small units.Additionally, using large files with a script tag doesn’t scale in larger apps with large number of developers: e.g. merge hell in version control

Deployment should be done on large files since we don’t want the browser to load lots of files - the latency gives us slowly loaded pages

Page 5: Using RequireJS for Modular JavaScript Code

RequireJS

Require tries to solve the problems with growing JS code bases by introducing the “module” abstraction.

In its core, modules exposes an interface and has dependencies. You ask RequireJS for a module, and RequireJS traces and instantiates its dependencies (including transitive dependencies) before instantiating the module you wish to use.

Why modules?Modules are (or, should be) small => easy to understand => easy to change + easy to test

Page 6: Using RequireJS for Modular JavaScript Code

DefineThis is a quite standard logger that every site needs. This one works with IE (where console.log throws an error if the development tools aren’t enabled).

You use the define function to define a module. The API is called AMD - asyncronous module definition.

The file name (modules/logger) is the name of the module.

Dependencies are stated in the array in the beginning of the function call. This one has no dependencies - the array is empty

The function @ param2 is a factory function that’s executed when we instantiate this module

The returned object is fed as an argument to the module requiring this module as a dependency. Here, the factory returns an object containing only one function: log(msg).

Note: we don’t clobber the global scope since everything in this module is hidden by the scope of the anonymous function.

Page 7: Using RequireJS for Modular JavaScript Code

Define A module that takes two other modules as dependencies.The names of the dependencies are the module names, e.g. “modules/logger” from the previous slide

The returned objects from the dependencies are injected into our factory function. (Logger returns an object containing the log function.)

Page 8: Using RequireJS for Modular JavaScript Code

Require

The require() call is the main entry point to the modules defined by the define() call.

The first argument is an array containing all module names we wish to load.

The anonymous function is a callback that’s called after the required modules and their dependencies have been loaded. The args to the function is the required modules.

Page 9: Using RequireJS for Modular JavaScript Code

End result The different modules in the diagram have defined dependencies, a clean interface (i.e. the returned object) and are easy to unit-test.

Page 10: Using RequireJS for Modular JavaScript Code

Firebug

An image from firebug with the modules from the previous slide.

Current problem: we have too many files, leading to slow loading times.

Require JS optimizer to the rescue!

Page 11: Using RequireJS for Modular JavaScript Code

Firebug (optimized)

The RequireJS optimizer compiles a top-level module (in our case, “module1.js”) and its dependencies (recursively) together into one file.

The file is also uglified (see example below).

-> One file per top-level module with minimal footprint

Page 12: Using RequireJS for Modular JavaScript Code

Introducing RequireJS with legacy JS files

legacy JS files = large, spaghetti, global scope

Page 13: Using RequireJS for Modular JavaScript Code

Single JS file

• One file = one module

If you are using only one js file (or you have no cross-refs between different stand-alone js files): you’re in luck.

This requires that no-one accesses the JS functions that are defined in the single JS file (e.g. literal click handlers on buttons/links etc).

It’s rather uncommon, sadly.

BUT: If we have this we can rather easily introduce RequireJS in the application.

Let the entire file be your module as a start.Then, refactor small pieces of functionality into sub-modules as you go along.

Page 14: Using RequireJS for Modular JavaScript Code

Interconnected files

The problem here is the circular dependency between file A and B at: file_a_2() -> file_b_2() -> file_a_1().

This state is rather common. There is no super-simple solution if we have these circular dependencies between file A and B.

This issue doesn’t only occur if we have different functions calling each other. We could also have one file and a literal click handler on a button/a-link with the function name stated in HTML.

To start solving this issue, we can modularize and export only the globally used functions. (In next slide.)

Page 15: Using RequireJS for Modular JavaScript Code

Interconnected files

in the two highlighted rows we export the cross-ref’d functions to the global namespace. These global functions are then called at lines 9 and 13 in file A and B respectively.

The next step is to refactor out file_a_1 and file_b_2 into separate modules. This is left as an excercise to the reader. The reason for extracting this is that we don’t want to use circular references between modules.

Page 16: Using RequireJS for Modular JavaScript Code

Thanks!

• RequireJS.org

• Thomas Lundström, Softhouse

[email protected]

• @thomaslundstrom

• http://blog.thomaslundstrom.com

RequireJS is Open Source, dev @ GitHub

I’ll post this presentation + some samples at my blog.