My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.
Showing posts with label Builder. Show all posts
Showing posts with label Builder. Show all posts

Monday, December 12, 2011

Create a JS builder with node.js

A very good and common practice with JS projects bigger than 100 lines of code is to split code in different files.
Benefits are clear:
  • smaller pieces of code to maintain
  • swappable portions for experiments and/or improvements or new features, as example including for a build magic2.js and get it, rather than change drastically magic.js and follow the repository logs
  • better organization of the code, and I'll come back on this in this post
  • possibility to distribute bigger closures, as example the jQuery approach
  • create ad hoc builds including or excluding portion of the library, specially suitable for specific version of the code that must be compatible with IE only


Solutions All Over The Place

There are really tons of solutions able to make the described build process easy to use and easy to go. As example, I have created my own one and I am using it with basically every project I am working with: the JavaScript Builder.
However, this builder requires a couple of extra technologies such Python and Java ... but aren't we using simply JavaScript?
So why not an easy to create guide on how to build your code via JS only?
This is what this post is about, and I hope you'll find useful.

How To Structure Your Project

If all files are in the same directory is not easy to find the right file immediately since these could be many. A good solution I came up with is folder related structure with both namespaces and private keywords paths.
Here an example on how I would structure this library ( and please ignore the library itself )

var myLib = (function (global, undefined) {"use strict";

// private scope function
function query(selector) {
return document.querySelectorAll(selector);
}

function Wrapper(nodeList) {
this.length = nodeList.length;
this._list = nodeList;
}

// a prototype method of the Wrapper "class"
Wrapper.prototype.item = function item(i) {
return this._list[i];
};

// public static query method
query.asWrapper = function (selector) {
return new Wrapper(query(selector));
};

var // private scope variables
document = global.document,
slice = [].slice
;

// the actual object/namespace
return {
query: query,
internals: {
Wrapper: Wrapper
}
};

}(this));

The code should be easy enough to understand. The object used as namespace for myLib has a couple of methods, few private variables and functions and something exposed through the internals namespace.
It does not matter what the library does or how good/badly is structured, what matters is that our folder structure should be smart enough to be able to scale with any sort of allowed JS pattern ... OK?

The Folder

Well, to start with, let's say our source code should be inside an src folder so we can add other folders for tests or builds beside in the same hierarchy.

dist
src
tests
builder.js

We'll see the builder.js later, in the meanwhile, let's have a look into the src folder:

dist
src
intro.js
outro.js
var.js
function
Wrapper.js
query.js
Wrapper
prototype
item.js
query
asWrapper.js
tests
builder.js

The distinction will be much cleaner once you read above list through your editor or even your shell ... query and files are well distributed but bear in mind this is only the first example.
Let's see what we are going to write into each file ?

src/intro.js

var myLib = (function (global, undefined) {"use strict";


src/function/query.js

// private scope function
function query(selector) {
return document.querySelectorAll(selector);
}


src/function/Wrapper.js

function Wrapper(nodeList) {
this.length = nodeList.length;
this._list = nodeList;
}


src/function/Wrapper/prototype/item.js

// a prototype method of the Wrapper "class"
Wrapper.prototype.item = function item(i) {
return this._list[i];
};


src/function/query/asWrapper.js

// public static query method
query.asWrapper = function (selector) {
return new Wrapper(query(selector));
};


src/var.js

var // private scope variables
document = global.document,
slice = [].slice
;


src/outro.js

// the actual object/namespace
return {
query: query,
internals: {
Wrapper: Wrapper
}
};

}(this));

Got it?

Structure Rules

  • every part of the scope can be distributed
  • each file can or cannot be compatible as stand alone with a parser because to test the library we need to build it first ( eventually with automations )
  • function declarations should be included in a dedicated folder called function accordingly with the nested level
  • var declaration per scope could be included in a folder var accordingly with the nested level. Do not create a var folder per each function where you define variables 'cause if you need it it means the function is too complex. Split it in sub task and do not define 100 variables per a single function: closures are the only exception.
  • nested closure must be named in order to be able to define nested closure structure following previous rules. Every minifier will be able to remove function expression names, included named closures, while not every developer would like to deeply understand the whole code to recognize why the nested closure was useful. A classic example is the inclusion inside our own closure of an external library that uses its own closure. in this case name that closure so you know were to look for the library inside your folder structure.
  • function prototypes should be placed inside a prototype folder, inside the function folder.
    We don't need to reassign an object when we want to pollute the function prototype so please stop this awkward common practice ASAP: MyFunction.prototype = { /* THIS IS WRONG */ } and use the already available prototype object defined by default in every ECMAScript standard and per each function declaration or expression.
    If your argument is that the code will be bigger, use the outer scoped variables definition to address the prototype once and reuse this reference within the prototype folder. This approach will make your life easier once you get use to work with structured and distributed JavaScript files.

Specially about last example, we could have set a shortcut to the Wrapper.prototype object in the var.js file and reuse the reference inside Wrapper.
The structured folders will always help you to find references in the library thanks to the lookup that you, as well as the code, have to do.

// in the var.js file
WrapperPrototype = Wrapper.prototype,

// in the Wrapper/prototype/item.js file
WrapperPrototype.item = function item(i) { ... };


The Order Partially Matters

In ECMAScript 3rd or higher edition function declarations are always available at the very beginning of the scope. I really don't know why these are so much underrated in daily basis code ... the fact these are always available means we can reference their prototype at any moment in our code:

var internalProto = (function () {

// address any declaration made in this scope
var WhateverPrototype = Whatever.prototype;
return WhateverPrototype;

// even if defined after a return!!!
function Whatever() {}
}());

alert(internalProto); // [object Object]

Now, the above code is simply a demonstration about how function declarations work ... I am not suggesting a return in the middle, and declarations after, all I am saying is that the order of things in JavaScript may not be relevant, and function declarations are a perfect example.
Another example is the usage of variables ... if a function, as declaration or as expression, reference a variable defined in the outer scope nothing will break unless we are invoking that function before the referenced variable has been defined.

This are really ABC concepts we all should know about JS before even claiming that we know JavaScript ... OK?
Is really important to get these points because to simplify ASAP the builder file we need to rely in these assumptions.

The builder.js File

It's time to create the magic file that will do the job for us in possibly a smart way so that we can cover all edge cases we could think of.
This is the content of builder.js file, in the root of our project

// @name builder.js
// @author Andrea Giammarchi
// @license Mit Style License

// list of files to include
var
scriptName = "myLib", // the namespace/object.project name
fileList = [
"intro.js", // beginning of the closure
"var.js", // all declared variables
"function/*", // all declared functions
"function/Wrapper/prototype/*", // all methods
"function/query/*", // all public statics
"outro.js" // end of the library
],
fs = require("fs"), // file system module
out = [], // output
alreadyParsed = [] // parsed files for visual feedback
;

// per each file in the list ...
fileList.forEach(function addFile(file) {
// if the file contains a wild char ...
if (file.charAt(file.length - 1) == "*") {
// read the directory and per each file found there ..
fs.readdirSync(
__dirname + "/src/" + file.slice(0, -2)
).forEach(function (file) {
// if the file type is js
// and the file has not been defined explicitly
// in the original list
if (
file.slice(-3) == ".js" &&
fileList.indexOf(file) < 0
) {
// call this same function providing the whole path
addFile(this + file);
}
// the path is passed as context to simplify the logic
}, file.slice(0, -1));
// if the file has not been included yet
} else if (alreadyParsed.indexOf(file) < 0){
// put it into the list of already included files
alreadyParsed.push(file);
// add the file content to the output
out.push(fs.readFileSync(__dirname + "/src/" + file));
} else {
// if here, we are messing up with inclusion order
// or files ... it's a nice to know in console
try {
console.log("duplicated entry: " + file);
} catch(e) {
// shenanigans
}
}
});

// put all ordered content into the destination file inside the dist folder
fs.writeFileSync(__dirname + "/dist/" + scriptName + ".js", out.join("\n"));

// that's it

The reason there are so many checks if a wild char is encountered is quite simple ... the order may not matter but in some case the order matters.
If as example a prototype property is used runtime to define other prototype methods or properties, this cannot be pushed in the output randomly but at the very beginning, example

// src/function/Wrapper/prototype/behavior.js
WrapperPrototype.behavior = "forEach" in [];

// src/function/Wrapper/prototype/forEach.js
WrapperPrototype.forEach = WrapperPrototype.behavior ?
function (callback) {[].forEach.call(this._list, callback, this)} :
function (callback) { /* a shim for non ES5 compatible browsers */ }
;

Being file 2 strongly dependent on file 1, the list of files could be written as this:

fileList = [
"intro.js", // beginning of the closure
"var.js", // all declared variables
"function/*", // all declared functions
"function/Wrapper/prototype/behavior.js", // precedence
"function/Wrapper/prototype/*", // all methods
"function/query/*", // all public statics
"outro.js" // end of the library
],

When the wild char will be encountered and the behavior passed to the forEach, this will be simply ignored since it has been pushed already in the previous call.
Same concept could happen if a specific file must be parsed runtime at the end:

fileList = [
"function/Wrapper/prototype/behavior.js", // precedence
"function/Wrapper/prototype/*", // all methods
"function/Wrapper/prototype/doStuff.js" // after all
],

I believe these are already edge cases most of the time but at least now we can better understand what the builder will do.

How To Use The Builder

In console, inside the project folder where the builder.js is:

node builder.js

That's pretty much it ... if you try to open dist/myLib.js after above call you will find your beautiful library all in one piece and ready to be minified, debugged, and tested.
If the process does not take long time you may bind the builder to the Constrol+S action with a potential sentinel able to inform you if any problem occurred, as example checking if the output has been polluted with some redundant file logged through the process.

As Summary

All these techniques may be handy for many reasons. First of all it's always good to maintain a structure, rather than a single file with thousands of lines of code, and secondly once we understand how the process work, nothing can stop us to improve, change, make it ad-hoc for anything we may need such regular expressions to strip out some code before the output push or whatever else could come up for some reason at some point.
The minification can be done the way you prefer, as example adding this single line of code at the end of the process assuming you have a jar folder with, as example, google closure compiler.

require('child_process').exec(
['java -jar "',
__dirname + "/jar/compiler.jar",
'" --compilation_level=SIMPLE_OPTIMIZATIONS --language_in ECMASCRIPT5_STRICT --js "',
__dirname + "/dist/" + scriptName + ".js",
'" --js_output_file "',
__dirname + "/dist/" + scriptName + ".min.js",
'"'].join(""),
function (error, stdout, stderr) {
if (error) console.log(stderr);
}
);

Enjoy your new builder :)

Wednesday, May 25, 2011

JavaScript Builder from Falsy Values

Update: uglify-js as third option


Thanks to @kitgoncharov JSBuilder now includes the option to run uglify-js through Rhino ... still cross platform, a builder for any taste.




as promised during my workshop, I have created a Google Code Project with the builder used to demonstrate most advanced and common techniques to make a library, application, snippet, small and fast.

What Is JSBuilder

It's a truly simple Python module based over a certain hierarchy able to combine multiple files and produce a minified version of the application.
A similar approach has been used since ages with jQuery, and I am pretty sure there are tons of builders out there able to do similar task.
I personally created ages ago a similar project compatible with all windows platform.
However, that project was missing the possibility to put everything together, preserve some piece of code if necessary, replace some string runtime, and produce if necessary different outputs through the simplified module approach.
Here a build.py file example, the same you can find in the repository.

# JSBuilder example

# project name (from the root folder)
copyright = '(C) WebReflection JSBuilder from Falsy Values'
max_js = 'build/max.js'
min_js = 'build/min.js'

# file list (from the root/src folder)
files = [
"intro.js", #beginning of the closure
"variables.js", #local scope variables
"main.js", #some application logic
"functions.js", #functions declaration
"debug.js", #something useful or evil to debug
"outro.js" #end of the closure
]

# execute the task
import JSBuilder
JSBuilder.compile(
copyright,
max_js,
min_js,
files,
# optional list of serches
[ #mustaches in comments, hell yeah!
'/*{namespace}*/'
],
# with relative optional list of replaces
[
'var ' #produce var MyLib = ...

# it could be 'this.'
# this.MyLib = ...

# it could be 'my.personal.stuff.'
# my.personal.stuff.MyLib = ...
]
)

# let me read the result ...
import time
time.sleep(2)


Why Python

First of all the version is the 2.6 or higher but not compatible (yet) with version 3+.
Python to me is a sort of server side lingua-franca and it is missing by default only on Windows platform.
I would say the same about Java, with the slightly difference that in Python we don't need to create a class to perform a simple task as the one performed by JSBuilder.
If Python is not present in our system, we can easily find the installer in the python.org website while the build.py example code should be easy to understand even for non programmers.

Java As Well

Both YUICompressor and Google Closure Compiler come with a jar package which is a classic build out of one or more Java files. JSBuilder uses Java as well in order to call one compiler or another one with proper arguments and in a cross platform way so ... just in case you do not have Java installed, here the link to download what we need.

The Current Folder Structure

projectFolder
build
builder
src
Above structure is exactly the same you can find in the repository but don't worry, as you can spot in the build.py file we are free to change destination folders or paths without problems.
JSBuilder assumes in any case relative paths from the root of the application but if you really need a different structure feel free to replace the string '../' with the one you prefer ... it is an easily replaceable part of the script, just find and replace and that's it.

Multiple Projects

The build.py file is just an example but obviously we can create as many examples as we want, even inside the build.py file itself, changing just the part to replace, destination files, and/or copyright if necessary.
JSBuilder should be suitable for all sort of cases ... at least for all cases I have been facing since I have started with JavaScript (10 years of cases, I hope it's enough)

The Concept Behind

As you can see there are different sources behind the scene and the application is distributed across multiple files.
While this concept may be not that familiar or weird, this has been used by jQuery library since ever and even if teams may think it's not easy to maintain such distributed code, a proper structure following some standard could be more than we need to make everything simple.
As example, the file called variables.js contains all local scope variables reused inside other files as well.
Of course if a variable has a meaning for more than a file, it makes perfect sense to put it in the precedent one, even as undefined variables, to eventually assign it later.

Closure Compiler VS YUICompressor

If you checkout the whole project and you try to build once with YUI and once with Closure Compiler, simply swapping comment on line 63 of JSBuilder.py file, you will realize these minifiers are able to produce a completely different output.
I leave you read all comments inside JSBuilder.py in order to understand what happens there exactly, and I hope you will realize there is even more than zou have imagined.

Debugging Code

Sometimes you may need a portion of code for debugging purpose, and the not minified version of the file is the only place where it should be.
When it comes to debug, we would like to be able to export outside private variables or functions, still understanding their original names rather than the minified one.
The debug.js files as example is able to modify internal variables thanks to evil eval function.
This piece of code is surrounded by this comment:

//^
... the debug/evil code ...
//$

If you are familiar with regular expressions you can spot that ^ as "start evil code" and $ as "end evil code" have semantic meaning ... and this is what will never be included in the final minified version of our app.

Give It A Try

I hope those present in my Falsy Values workshops got the advantages a build system could bring on our daily basis work but I have written as many comments as possible to let you understand what is going on, how does it work, and why this simple script is freaking cool and it could potentially become our favorite build system.

What's Next

In my personal projects I have included an extra line at the end of the build.py able to create, per each build, documentation.
This kinda helps me to do not forget I have to document new or modified stuff, plus it gives me extra hints if some documentation has been lazily copied and pasted rather than being rewritten ... well, I honestly don't really like jsDocToolkit since JavaScript syntax has nothing to do with Java one but I agree is, at least, a tool flexible enough and still widely adopted and/or extended so ... if you all agree, I will put the magic documentation part there inside a build-with-doc.py file example.

Monday, December 27, 2010

ES5 Common Design Patterns Examples - Part 1


// Abstract Factory
/*
({}).createInstance()

(function (a, b, c) {
this.sum = a + b + c;
}).createInstance([1, 2, 3])

*/
Object.defineProperty(
// (C) WebReflection - Mit Style License
Object.prototype,
"createInstance",
{
value: (function (create) {
return function createInstance(args) {
var
self = this,
isFunction = typeof self == "function",
obj = create(isFunction ? self.prototype : self)
;
isFunction && args != null && self.apply(obj, args);
return obj;
};
}(Object.create))
}
);

// Abstract Builder
/*
var person = Object.builder({
setup: function (name) {
this.create();
this.instance.name = name;
}
});
person.setup("WebReflection");
alert(person.instance.name);
person.create();
person.instance.name = "Andrea";
alert(person.instance.name);
*/
Object.defineProperty(
// (C) WebReflection - Mit Style License
Function.prototype,
"builder",
{
value: function (methods) {
var
$create = Object.create,
self = this,
proto = self.prototype,
obj
;
return $create(methods, {
instance: {
get: function get() {
return obj;
}
},
create: {
value: function create() {
obj = $create(proto);
self.apply(obj, arguments);
}
}
});
}
}
);

// Multiton + Singleton pattern
// (via lazy initialization)
/*
function Car(){}
var car = Car.getInstance();
alert(car === Car.getInstance());
alert(car !== Car.getInstance("bmw"));
alert(Car.getInstance("bmw") === Car.getInstance("bmw"));
*/
Object.defineProperty(
// (C) WebReflection - Mit Style License
Function.prototype,
"getInstance",
{
value: function (key) {
var
create = Object.create,
instances = {},
self = this,
proto = self.prototype,
instance
;
Object.defineProperty(
self,
"getInstance",
{
value: function getInstance(key) {
return key == null ?
instance || (instance = create(proto)) :
instances.hasOwnProperty(key) ?
instances[key] :
instances[key] = create(proto)
;
}
}
);
return self.getInstance(key);
}
}
);

// Prototype
/*
var definition = {some:"thing"};
var inherited = Object.create(definition);
*/
Object.create;

// Abstract Adapter
/*
function Person() {}
Person.prototype.setName = function setName(_name) {
this._name = _name;
};

var getter = {
toString: function () {
return this._name;
}
};

var me = {};
me.adapt(Person);
me.setName("WebReflection");
me.adapt(getter);
alert(me);
*/
Object.defineProperty(
// (C) WebReflection - Mit Style License
Object.prototype,
"adapt",
{
value: (function (proto) {
return proto in {} ?
function adapt(Class) {
this[proto] = typeof Class == "function" ? Class.prototype : Class;
} :
function adapt(Class) {
var self = this;
typeof Class == "function" && Class = Class.prototype;
for (proto in Class)
self.hasOwnProperty(proto) || (self[proto] = Class[proto])
;
}
;
}("__proto__"))
}
);

// Abstract Composite
/*
function AddToBody(value) {
this.value = value;
}
AddToBody.prototype.value = "";
AddToBody.prototype.exec = function () {
document.body.appendChild(
document.createElement("p")
).innerHTML = this.value;
};

var many = AddToBody.composite();
many.push(
new AddToBody("this"),
new AddToBody("is"),
new AddToBody("a"),
new AddToBody("test")
);

this.onload = function () {
many.exec();
alert(many.value);
many.value = "everybody like this";
many.exec();
alert(many.value);
};
*/
Object.defineProperty(
// (C) WebReflection - Mit Style License
Function.prototype,
"composite",
{
value: (function (defineProperty) {
return function composite() {
function get(key) {
function retrieve(item) {
return item[key];
}
return function get() {
return map.call(this, retrieve);
};
}
function set(key) {
function assign(item) {
item[key] = this;
}
return function set(value) {
forEach.call(this, assign, value);
};
}
function wrap(method) {
function apply(item) {
method.apply(item, this);
}
return function wrap() {
forEach.call(this, apply, arguments);
};
}
var
composite = [],
forEach = composite.forEach,
map = composite.map,
proto = this.prototype,
key, value
;
for (key in proto) {
if (typeof(value = proto[key]) == "function") {
composite[key] = wrap(value);
} else {
defineProperty(
composite,
key,
{
get: get(key),
set: set(key)
}
);
}
}
return composite;
};
}(Object.defineProperty))
}
);

// Observer/Listener
/*
function hello(e) {
alert(e.type);
}
var obj = new Listener;
obj.addEvent("hello", hello);
obj.addEvent("hello", hello);
obj.fireEvent("hello");
obj.fireEvent({type:"hello"});
obj.removeEvent("hello", hello);
obj.fireEvent({type:"hello"});
*/
var Listener = (function () {
// (C) WebReflection - Mit Style License
function Listener() {
handler.value = {};
defineProperty(this, "_handler", handler);
}
function fire(callback) {
callback.call(this.target, this);
}
var
proto = Listener.prototype
defineProperty = Object.defineProperty,
handler = {
value: proto
},
empty = []
;
Object.defineProperties(
Listener.prototype,
{
addEvent: {
value: function addEvent(type, callback) {
var stack = this._handler[type] || (this._handler[type] = []);
stack.indexOf(callback) < 0 && stack.push(callback);
}
},
removeEvent: {
value: function removeEvent(type, callback) {
var
stack = this._handler[type] || empty,
i = stack.indexOf(callback)
;
-1 < i && stack.splice(i, 1);
}
},
fireEvent: {
value: function fireEvent(e) {
typeof e == "string" && (e = {type: e});
e.target = this;
(this._handler[e.type] || empty).forEach(fire, e);
}
}
}
);
return Listener;
}());