Dependencymanagement 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 conﬂict Requirements conflict bet ween development and deployment: Devs want small discrete files since 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
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 testRequireJS
Deﬁne This 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.
Deﬁne A module that takes t wo 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
legacy JS files = large, spaghetti, global scopeIntroducing RequireJS with legacy JS ﬁles
Single JS ﬁle If you are using only one js file (or you have no cross-refs bet ween different stand-alone js files): you’re in luck.• One ﬁle = one module 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 ﬁlesThe problem here is the circular dependency bet ween 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 bet weenfile A and B.This issue doesn’t only occur if we have different functions calling each other. We could also have one file anda 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 ﬁlesin the t wo highlighted rows we export the cross-ref’d functions to the global namespace. These global functions are then called atlines 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 bet ween modules.
Thanks! RequireJS is Open Source, dev @ GitHub I’ll post this presentation + some samples at my• RequireJS.org blog.• Thomas Lundström, Softhouse• firstname.lastname@example.org• @thomaslundstrom• http://blog.thomaslundstrom.com