DubJS

DubJS is a dialect of JavaScript for making web apps with minimal effort. It comes with a strong core framework, and is designed for building any kind of web app, whether it be an entirely dynamic single-page app, a pre-generated static site,1 or something in between. It's focus is client-side development, and as such can be used in combination with your favourite server-side tools.

The project is very early in development. However, significant progress has been made towards an initial release. Any constructive feedback is greatly appreciated. (As much as I love positive feedback, I'm particularly interested in any bits you don't like, and those that aren't explained clearly enough.)

Aims

Inevitably, there are some conflicts between these goals. In those cases I've tried to strike the right balance.

Philosophy

Coding should involve minimal effort, thereby making it more enjoyable and productive.

Minimal effort is achieved via simplicity. In this context, simplicity doesn't mean limited; it means no more complex than necessary. It means easy to understand and use. It means not repeating yourself. It means faster development and fewer bugs. It means less code.

Usage

There will be a command-line utility with several modes (and other options). These are the modes relevant to this introduction:

--compile-c Compiles to JavaScript. This is the default mode.
--app-a Evaluates, then generates a web app, using the final value for the contents.2
--gen-g Evaluates, then generates output in the specified format, using the final value. Accepts web, css, html. The web option outputs in the appropriate web language(s), as you'll see later.
--web Shortcut for --gen web.

Additionally, the compiler will be embeddable in any JavaScript environment, including the browser.

Examples

For the purpose of this post I will assume you know at least the basics of JavaScript. (Eventually, I hope to write a gentler introduction to Dub for the absolute beginner.)

These examples compare equivalent snippets of Dub and web standards.3 In other words, the comparisons don't necessarily represent output, but rather what you might otherwise write using web standards. Examples of exact output will be posted to Features soon. Also, take note of the modes next to each heading, else the examples might not make much sense.

(Bear in mind, examples are only indicative of the final design; Dub is a work in progress. Some comparisons are deliberately simplified, and may ignore some compatibility issues.)

Static Renderingapp

In many cases you might want part (or all) of your app to be served as static markup. Reasons can include rendering speed, search engine optimization, and in the case of static sites, supporting environments where JavaScript is missing (or disabled). However, because Dub compiles to JavaScript, it's easy to switch to dynamic rendering if you change your mind. You can even have the best of both worlds: a static version for search engines to crawl, and a dynamic version (using the history API or hash-bangs) for users.

Let's start with the simplest possible app (if you can even call it that).

'Hi, world!'
Chars: 12
<!DOCTYPE html>
<html>
  <head>
    <title></title>
  </head>
  <body>Hi, world!</body>
</html>
Chars: 85

Here, the app's contents are determined by the final (and in this case, only) value. Note, the app's skeleton is automatically generated for you,4 which will be especially handy for quick mock-ups.


app.title = 'Try'
Button('Try', { alert("Do, or do not. There is no try.") })
Chars: 77
<!DOCTYPE html>
<html>
  <head>
    <title>Try</title>
  </head>
  <body>
    <button onclick='alert("Do, or do not. There is no try.")'>Try</button>
  </body>
</html>
Chars: 151

The app variable (available in app mode) allows you to specify general properties e.g. the title. Again, the final value – this time a Button – is used to generate the app's contents. There are a range of other built-in types for creating elements. In case you can't find the right one, you can easily create custom elementscustom elements, as well as define your own custom types.

There are also a few syntactical differences to notice. Semicolons are optional at the end of each line (like in Ruby and Python), which is actually different to JavaScript's slightly unintuitive semicolon insertion.5 Functions don't require a function prefix, nor do they require parens when there are no (defined) parameters. Also, Pythonists will recognize the instantiation syntax.

Dynamic Renderingapp

There are several benefits of dynamic rendering. It's inherently more flexible and can change in response to the user's actions, allowing a better user experience. For example, a dynamic (single-page) app allows the user to navigate without waiting for whole pages to load. Your app will be likely to load (not render) faster, due to a smaller downloads.6 (Bonus: smaller downloads and less server-side processing might lower your hosting costs.)

When switching a static app to a dynamic one, there's no need to translate markup into JavaScript; with Dub you already have the code to build it in JavaScript. This example is identical to the previous one, except that the app's contents are rendered dynamically.

app.title = 'Try'

app.load = { .add(
  Button('Try', { alert('Do, or do not. There is no try.') })
) }
Chars: 101
<!DOCTYPE html>
<html>
  <head>
    <title>Try</title>
    <script src='try.js' type='text/javascript'></script>
  </head>
  <body></body>
</html>
window.onload = function () {
  var but = document.createElement('button');
  but.innerHTML = 'Try';
  but.onclick = function () {
    alert('Do, or do not. There is no try.')
  };
  document.body.appendChild(but);
};
Chars: 335

As you can see, the Button has simply been pasted into the load function. It's also added to the app via the . shortcut for this.. In this example, the generated script is referenced from the main document. (However, there will be an option to embed scripts, and style sheets etc.)

Nestingweb

Often you'll want to nest elements within each other. Dub provides a few ways to do this, such as the prior add method. Maybe the simplest way is to include elements as parameters of their containers.

Div(Span('Node'))
Chars: 17
<div>
  <span>Node</span>
</div>
Chars: 30

Some elements handle nesting in their own specific way...

Table('Mario', 'Peach')
Chars: 23
<table>
  <tr>
    <td>Mario</td>
    <td>Peach</td>
  </tr>
</table>
Chars: 57

Rather than the nested parts being placed directly within this Table, they are placed into cells within a row. (Although this example nests only text, you could include any kind of element.)


Table(
  ['Tim', 'Princess'],
  ['Link', 'Zelda']
)
Chars: 47
<table>
  <tr>
    <td>Tim</td>
    <td>Princess</td>
  </tr>
  <tr>
    <td>Link</td>
    <td>Zelda</td>
  </tr>
</table>
Chars: 98

Here, arrays are used to designate the rows of another Table. (There will also be an easy way to specify columns instead, as well as various other options.)

Requests (AJAX)compile

Asynchronous requests are a great way to make your app more responsive. Let's say you have an organisation's Facebook ID, and you want to find its location. In this example, the location is fetched, then used to display a map.

id = 143861525631946

get('//graph.facebook.com/$id', JSON, (data) {
  lat, lon = *data#location['latitude', 'longitude']
  ref = '//maps.google.com/maps?q=$lat,$lon&output=embed'
  frame = Frame(ref, border: null, size: 384)
  app['#Demo'].add(frame)
})
Chars: 246
var id = 143861525631946, req = new XMLHttpRequest;

req.onreadystatechange = function () {
  if (req.readyState == 4) {
    var data = JSON.parse(req.responseText);
    var lat = data.location.latitude, lon = data.location.longitude;
    var ref = '//maps.google.com/maps?q=' + lat + ',' + lon + '&output=embed';
    var frame = document.createElement('iframe');
    
    frame.src = ref;
    frame.style.border = 'none';
    frame.style.height = frame.style.width = '384px';
    
    document.getElementById('Demo').appendChild(frame);
  }
};

req.open('GET', '//graph.facebook.com/' + id, true);
Chars: 554

Variable declaration doesn't require a var prefix (like in CoffeeScript). The Facebook API call is made via a dead-simple, Sinatra-inspired get method. There are a few cases of string interpolation using $ (which also allows parens for literals and more complex expressions).

The first line of the callback function is a multi-variable assignment, where lat and long are given values from the returned data (parsed as the specified format, JSON). Those values are obtained via index operators: # (for single items) and its counterpart [] (for any number of items).7 The obtained array is then 'flattened' using * – a.k.a. the 'splat' operator – which splits an array into separate parameters. A Frame is then created, having some properties specified using named parameters, which are allowed directly after the others (a la Ruby). The size property is short-hand for height and width. Finally, the frame is added to its container using a jQuery-like ID selector.

Custom Typescompile

For more complex apps it's usually a good idea to define your own types. This allows greater reuse, as well as cleaner and more manageable code. JavaScript's prototype system, while flexible, is fairly verbose. Dub is designed to keep that flexibility, but with a much simpler approach.

Snake = Type(Reptile,
  init: (name) { .name = name },
  attack: (prey) { .bite(prey) },
  limbs: 0
)

apep = Snake('Apep')
apep.name == 'Apep' // true.
Chars: 137
function Snake(name) {
  this.name = name
}

Snake.prototype = new Reptile;
Snake.prototype.attack = function (prey) {
  this.bite(prey);
};
Snake.prototype.limbs = 0;

var apep = new Snake('Apep');
apep.name == 'Apep'; // true.
Chars: 215

In this example, the Snake type is defined by two things: its base type – Reptile – and a list of properties. One of those properties, init, specifies the method to be called each time a new instance is created.

Inheritancecompile

In JavaScript, inheritance is a bit awkward and can be slightly confusing. Dub removes the need for arcane function calls and unnecessary repetition.

Python = Type(Snake,
  attack: (prey) { base(prey); .constrict(prey) },
  venomous: false
)

with monty = Python('Monty') {
  .limbs == 0 // true.
  .name == 'Monty' // true.
  .attack(apep) // bites, then constricts prey.
}
Chars: 164
function Python(name) {
  Snake.call(this, name);
}

Python.prototype = new Snake;
Python.prototype.attack = function (prey) {
  Snake.prototype.attack.call(this, prey);
  this.constrict(prey);
};
Python.prototype.venomous = false;

var monty = new Python('Monty');

monty.limbs == 0; // true.
monty.name == 'Monty'; // true.
monty.attack(apep); // bites, then constricts prey.
Chars: 321

Here, the init method for Python is inherited from Snake, meaning there is no need to explicity call the base constructor. There is also a handy base function, used in attack, which allows easy access to the corresponding method of the base type.

Unfortunately, JavaScript's with statement is generally considered dangerous.8 However, Dub provides a safe replacement, which requires properties to be preceded by . (as shown before). Also, parens are optional and, since variable declarations return values, you can declare variables before the block (unlike in JavaScript).

Type Extensioncompile

In some cases, you might want to modify an existing type, instead of defining a new one. Maybe there's a built-in type that doesn't quite fit your needs, or maybe you want to make some changes to a third-party library.

Ruby = Type(Gem,
  color: 'red',
  cut: { .shape(); .polish() },
  master: 'Matz'
)

Ruby#cut = { old(); .engrave() }

rb = Ruby()
rb.cut() // shapes, polishes, then engraves.
Chars: 133
function Ruby() { }

Ruby.prototype = new Gem;
Ruby.prototype.color = 'red';
Ruby.prototype.cut = function () {
  this.shape(); this.polish();
};
Ruby.prototype.master = 'Matz';

var old = Ruby.prototype.cut;

Ruby.prototype.cut = function () {
  old.call(this); this.engrave();
};

var rb = new Ruby;
rb.cut(); // shapes, polishes, then engraves.
Chars: 307

Index operators aren't only useful to obtain items in collections; for example, they can also return instance properties of a Type. In this case, the cut method is referenced via #, and replaced with an extended version of itself. A relative of base, the function old refers to the previous version of the current method.

Styleweb

A common requirement is to create a link that has both an icon and text. However, with the advent of vendor style prefixes and other incompatiblities, even a simple task can turn into a mess of duplicated code. In this example we'll create a 'Follow' button, like you'd see in Twitter profiles.

FollowLink = Type(Link, (name) {
  .ref = 'https://twitter.com/intent/follow?screen_name=$name'
  .add(Image('bird.png', align: 'bottom'), 'Follow')
}, {
  background.gradient: 'top #FFF #DDD',
  border: { color: '#CCC', radius: 4, width: 1 },
  font: ('Helvetica Neue, Arial', 13, '#333', decoration: null, weight: 'bold'),
  line.height: 18,
  spacing: 6,
  target: 'blank'
})

FollowLink('varboss')
Chars: 384
<a href='https://twitter.com/intent/follow?screen_name=varboss' target='_blank' class='FollowLink'>
  <div>
    <img src='bird.png' style='vertical-align:bottom' />
    <span>Follow</span>
  </div>
</a>
.FollowLink {
  border-radius: 4px; /* IE 9 */
  color: #333;
  display: inline-block;
  font: bold 13px;
  font-family: Helvetica Neue, Arial;
  line-height: 18px;
  overflow: hidden; /* IE 9 */
  text-decoration: none;
}

.FollowLink * { margin-left: 6px }
.FollowLink *:first-child { margin: 0 }

.FollowLink div {
  background: #EEE; /* fallback */
  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFF', endColorstr='#DDDDDD', GradientType=0); /* IE 6-9 */
  background: -moz-linear-gradient(top, #FFF, #DDD);
  background: -ms-linear-gradient(top, #FFF, #DDD);
  background: -o-linear-gradient(top, #FFF, #DDD);
  background: -webkit-gradient(linear, left top, left bottom, from(#FFF), to(#DDD)); /* Safari 4 */
  background: -webkit-linear-gradient(top, #FFF, #DDD);
  background: linear-gradient(to bottom, #FFF, #DDD);
  border: 1px solid #CCC;
  -moz-border-radius: 4px; /* Firefox 3.6 */
  -webkit-border-radius: 4px; /* Safari 4 */
  border-radius: 4px;
  padding: 6px;
}
Chars: 1,057

The type definition here shows how the init method can be included as an anonymous parameter, after the base type (Link). There is also a short-hand way to specify nested properties, as you can see with background.gradient and line.height. The handling of the gradient is an example of graceful degradation, with a fallback background being specified as the average of the gradient's colors. The font assignment demonstrates a special way to specify certain properties, mimicking the syntax of a function call. A final new addition is the spacing property, which specifies an element's padding, as well as the gap between inner elements. (By the way, for simplicity's sake this button is not an exact replica.)

For those wondering why there's a seemingly unnecessary div within the a tag: the purpose is to avoid a nasty issue in Internet Explorer 9, where an element's background 'bleeds' beyond its border, ruining its rounded corners. Sadly, this is the simplest way to avoid the issue.9


One more thing. You might've noticed the character counts below each example: in total, the Dub code is 2.4 times smaller (at 1,341 chars) than the equivalent web standards code (at 3,201 chars), ignoring comments and indentation. Obviously, this figure should be taken with a grain of salt, being a somewhat simplified comparison. However, it should at least give you an indication of the time-saving potential of Dub.

Direction

This introduction is nowhere near comprehensive. There are many more details to come, so keep an eye on the blog (or subscribe here). Features to be announced include the full range of operators, MV*-style pattern support, and much more.

There is still a lot of work to be done, but I am determined to make this happen. As things stand, I can only devote a small amount of spare time to it each week. My hope is to be able to work on it full-time (which will drastically speed up development), at least until the first usable version is released. You can help make that happen by backing the project on Indiegogo. At the point of public release, the source code will be freely available under a non-restrictive license (I'm leaning towards MIT). If there is enough interest in this project, I will start a campaign on Indiegogo to allow me to work on it full-time, which will will drastically speed up development. You can help by voting and discussing on Hacker News here.

You can also help out by joining the discussion on Hacker News. Alternatively, if you're a hacker with some time to spare and you believe in the vision for this project, reach out to me in regard to becoming a contributor. Relevant skills include JavaScript proficiency, unit testing, or experience with parser generators (especially Jison or similar).

Back this Project

Notes

1.A static site wouldn't normally be called an 'app', but here the term is used in the broadest sense. The point is to highlight the spectrum from static to dynamic, as opposed to a clear distinction.
2.There are some exceptions e.g. if the final value has no conversion method for the specified format.
3.It wouldn't make sense to single out any particular language or framework combination. I'll let others make their own comparisons.
4.HTML 5 requires a title tag, which may be empty.
5.See this post for an explanation of why, in JavaScript, it's considered best practice to always add semicolons.
6.JavaScript (and formats like JSON) can be far more compact than equivalent markup, especially for larger apps.
7.Like many languages, Dub differentiates between the properties and items of a collection. Among other reasons, this is to avoid name conflicts.
8.See this post for an explanation.
9.See this post for more info.
ArchiveFeed