JavaScript dependencies & modules
Introduction to Browserify and how to use CommonJS/Node.js modules in the browser. We'll start with a brief look at the current landscape of handling dependencies and modules.
Talk at sthlm.js 2014-01-07, http://www.meetup.com/sthlm-js/events/156429912/
Demos & code examples; https://github.com/johannilsson/sthlmjs-1401
3. Wednesday, January 8, 14
I’ve been writing JS for quite a while, then I was off doing mostly backend & Android for while.
This is Theo, I felt like him here towards JS for a while I feel that the tools around for JS has increased and grown in complexity quite fast. It also feels like it is
changing quite rapid. This is also what attracts me to JS, for me it feels like this is where a lot of creativity and innovation is happening.
But picking the right tools is a/has been challenge.
4. Web Sites vs Web Apps
What’s the difference?
Wednesday, January 8, 14
I find this quite interesting. And is there a difference and why do we make a difference?
5. We’re all building sites that people visit, do
something, and leave. Differentiating websites vs. web
apps is no good to anyone.
– Jack Franklin
https://speakerdeck.com/jackfranklin/port80-practical-javascripting
Wednesday, January 8, 14
6. Why do you want to make that distinction?
What benefit do you gain by arbitrarily dividing
the entire web into two classes?
– Jeremy Keith
http://adactio.com/journal/6246/
Wednesday, January 8, 14
7. A lot of people ignore new JavaScript tools, methods
or approaches because those are just for “web apps.”
– Jack Franklin
https://speakerdeck.com/jackfranklin/port80-practical-javascripting
Wednesday, January 8, 14
I’ve done this for too long.
8. Dev Environment
•
Module System: A way to programmatically load scripts when they’re
needed.
•
Package Manager: Fetch scripts from online sources like GitHub, taking
dependencies into account.
•
Build Tool: Combine scripts and other assets together into something
usable by browsers.
http://dailyjs.com/2013/01/28/components/
Wednesday, January 8, 14
And to handle modules and dependencies properly we need to use proper tools to cope with a modern development environment
9. Modules
A way to programmatically load scripts when they’re needed
Wednesday, January 8, 14
Let’s start with modules.
What is a module? Keep it simple, it’s basically just a script that we can load whenever we need it.
11. SRC_DIR = src
BUILD_DIR = build
JS_SERVER_FILES = $(SRC_DIR)/js/namespace.js
! $(SRC_DIR)/js/vendor/buffer-loader.js
! $(SRC_DIR)/js/vendor/requestanimframe.js
! $(SRC_DIR)/js/vendor/stats.min.js
! $(SRC_DIR)/js/vendor/minpubsub.js
! $(SRC_DIR)/js/app.js
! $(SRC_DIR)/js/utils.js
! $(SRC_DIR)/js/sound.js
! $(SRC_DIR)/js/shape.js
! $(SRC_DIR)/js/main.js
JS_BUNDLE = $(BUILD_DIR)/app.js
BUILD_BUNDLE = $(BUILD_DIR)/app-min.js
.DEFAULT_GOAL := all
all: bundle
! java -jar $(GCC_COMPRESSOR) $(GCC_OPTION) --js=$(JS_BUNDLE) -js_output_file=$(BUILD_BUNDLE)
debug: bundle
! cat $(JS_FILES) >> $(JS_BUNDLE)
Wednesday, January 8, 14
To assemble this for production we would need a build script. This is a Makefile from an old project of mine. Sure we can have grunt but it is still configuration
clean:
we need to add.
! rm -Rf $(BUILD_DIR)/*
For me this isn’t really prototyping friendly.
12. <% if (production) { %>
<script type="text/javascript"
<% } else { %>
<script type="text/javascript"
<script type="text/javascript"
<script type="text/javascript"
<script type="text/javascript"
<script type="text/javascript"
<script type="text/javascript"
<script type="text/javascript"
<script type="text/javascript"
<script type="text/javascript"
<script type="text/javascript"
<% } %>
src="/js/app-min.js"></script>
src="/js/namespace.js"></script>
src="/js/vendor/buffer-loader.js"></script>
src="/js/vendor/requestanimframe.js"></script>
src="/js/vendor/stats.min.js"></script>
src="/js/vendor/minpubsub.js"></script>
src="/js/app.js"></script>
src="/js/utils.js"></script>
src="/js/sound.js"></script>
src="/js/shape.js"></script>
src="/js/main.js"></script>
Wednesday, January 8, 14
And we add a conditional to check if we are in production. Personally I don’t like this since it means that we’re during development will most likely run a different
version of the site than we will in production.
13. The Problem
•
•
•
•
Code complexity grows as the site gets bigger
Assembly gets harder
Developer wants discrete JS files/modules
Deployment wants optimized code in just one or a few HTTP calls
http://requirejs.org/docs/why.html#1
Wednesday, January 8, 14
Why do we want modules. Require.JS outlines a few arguments on there website.
We basically want it because we need better structure in our code.
14. Current Landscape
•
•
•
•
•
•
Globals
Globals + Namespace
AMD (Asynchronous Module Definition)
CommonJS
UMD (Universal Module Definition)
(ES6)
Wednesday, January 8, 14
Alternatives for modules, not taking framework specific ones like Angular into account.
15. var isEven = function(i) {
return i % 2 === 0;
};
Wednesday, January 8, 14
I like small bits of code that can be separated and re-used. Here’s a quite simple re-usable bit of code but is short enough to fit a slide.
Simple re-usable code
28. //Allow for anonymous modules
if (typeof name !== 'string') {
//Adjust args appropriately
callback = deps;
deps = name;
name = null;
}
//This module may not have dependencies
if (!isArray(deps)) {
callback = deps;
deps = null;
}
require.js
//If no name, and callback is a function, then figure out if it a
//CommonJS thing with dependencies.
if (!deps && isFunction(callback)) {
deps = [];
//Remove comments from the callback string,
//look for require calls, and pull them into the dependencies,
//but only if there are function args.
if (callback.length) {
callback
.toString()
.replace(commentRegExp, '')
.replace(cjsRequireRegExp, function (match, dep) {
deps.push(dep);
});
//May be a CommonJS thing even without require calls, but still
//could use exports, and module. Avoid doing exports and module
//work though if it just needs require.
//REQUIRES the function to expect the CommonJS variables in the
//order listed below.
deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps);
}
}
//If in IE 6-8 and hit an anonymous define() call, do the interactive
//work.
if (useInteractive) {
node = currentlyAddingScript || getInteractiveScript();
if (node) {
if (!name) {
name = node.getAttribute('data-requiremodule');
}
context = contexts[node.getAttribute('data-requirecontext')];
}
}
//Always save off evaluating the def call until the script onload handler.
//This allows multiple modules to be in a file without prematurely
//tracing dependencies, and allows for anonymous module support,
//where the module name is not known until the script onload event
//occurs. If no context, use the global queue, and get it processed
//in the onscript load callback.
(context ? context.defQueue : globalDefQueue).push([name, deps, callback]);
};
define.amd = {
jQuery: true
};
Wednesday, January 8, 14
/**
* Executes the text. Normally just uses eval, but can be modified
* to use a better, environment-specific call. Only used for transpiling
* loader plugins, not for plain JS modules.
* @param {String} text the text to execute/evaluate.
*/
req.exec = function (text) {
/*jslint evil: true */
return eval(text);
};
Quite a massive file that is required //Set load these files. Not sure exactly what goes on here, but it works.
to up with config info.
req(cfg);
}(this));
29. Makefile / Build step
build:
! node r.js -o name=app out=js/app-built.js baseUrl=./js
build-even:
! node r.js -o name=even out=js/even-built.js baseUrl=./js
Wednesday, January 8, 14
For production you might want to bundle your files into one bundle. You still depend on require.js though. And you will have one setup for development and another one
for production.
34. var shim = {};
if (typeof(exports) === 'undefined') {
if(typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
// we are in amd.
shim.exports = {};
define(function() {
return shim.exports;
});
} else {
// we are in a browser, define its namespaces in global
shim.exports = typeof(window) !== 'undefined' ? window : _global;
}
}
else {
// we are in commonjs, define its namespaces in exports
shim.exports = exports;
}
even-lib.js
(function(exports) {
var isEven = function(i) {
return i % 2 === 0;
};
if (typeof(exports) !== 'undefined') {
exports.isEven = isEven;
}
})(shim.exports);
})(this);
header.js
Wednesday, January 8, 14
This approach has been quite popular for library developers to be able to meet end users requirements.
35. var shim = {};
if (typeof(exports) === 'undefined') {
if(typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
// we are in amd.
shim.exports = {};
define(function() {
return shim.exports;
});
} else {
// we are in a browser, define its namespaces in global
shim.exports = typeof(window) !== 'undefined' ? window : _global;
}
}
else {
// we are in commonjs, define its namespaces in exports
shim.exports = exports;
}
even-lib.js
header.js
(function(exports) {
even.js
footer.js
header.js
var isEven = function(i) {
return i % 2 === 0;
};
if (typeof(exports) !== 'undefined') {
exports.isEven = isEven;
}
})(shim.exports);
})(this);
Wednesday, January 8, 14
This approach has been quite popular for library developers to be able to meet end users requirements.
38. AMD & CommonJS
Wednesday, January 8, 14
For me, there’s really only two options here. AMD or CommonJS. Both of these solves the issue of loading modules.
39. Wednesday, January 8, 14
Various alternatives around for common js, as far as I know there is only require js for AMD
40. CommonJS
Wednesday, January 8, 14
Various alternatives around for common js, as far as I know there is only require js for AMD
43. What I want
•
•
•
•
•
Wednesday, January 8, 14
Simple
Active community
No complicated build environment
Works with existing libraries, with none or minimal adaption
Prototype friendly
46. Browserify
•
•
•
Wednesday, January 8, 14
require & npm modules in the browser
Browser versions of core node modules (events, stream, path, url, ...)
Extendable
47. How does it work?
•
•
•
•
Wednesday, January 8, 14
Small wrapper for the require function
Build process that keeps track of dependencies
Bundles to one file
No global leakage
48. ;
(function e(t, n, r) {
function s(o, u) {
if (!n[o]) {
if (!t[o]) {
var a = typeof require == "function" && require;
if (!u && a) return a(o, !0);
if (i) return i(o, !0);
throw new Error("Cannot find module '" + o + "'")
}
var f = n[o] = {
exports: {}
};
t[o][0].call(f.exports, function (e) {
var n = t[o][1][e];
return s(n ? n : e)
}, f, f.exports, e, t, n, r)
}
return n[o].exports
}
var i = typeof require == "function" && require;
for (var o = 0; o < r.length; o++) s(r[o]);
return s
})({
1: [
function (require, module, exports) {
var isEven = function (i) {
return i % 2 === 0;
};
module.exports = isEven;
}, {}
],
2: [
function (require, module, exports) {
var isEven = require('./even');
console.log(isEven(2));
}, {
"./even": 1
}
]
}, {}, [2]);
Wednesday, January 8, 14
51. Current Landscape
•
•
•
•
•
•
Ender (http://ender.jit.su/)
Volo (http://volojs.org/)
Jam (http://jamjs.org/)
Bower (http://bower.io/) 7 000+
Component (http://component.io/)
npm (http://npmjs.org) 53 000+
Wednesday, January 8, 14
Quite a few alternatives around. Not likely that all of these will survive. Currently it seems like Bower is the one getting more and more attraction for front end
developers