Upload
thomas-lundstroem
View
4.541
Download
1
Embed Size (px)
DESCRIPTION
Slides and notes from my lightning talk at Scandinavian Developer Conference, April 2012 in Gothenburg.
Citation preview
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
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.
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
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
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
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.
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.)
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.
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.
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!
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
Introducing RequireJS with legacy JS files
legacy JS files = large, spaghetti, global scope
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.
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.)
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.
Thanks!
• RequireJS.org
• Thomas Lundström, Softhouse
• @thomaslundstrom
• http://blog.thomaslundstrom.com
RequireJS is Open Source, dev @ GitHub
I’ll post this presentation + some samples at my blog.