E4X (ECMAScript for XML) is a Javascript syntax extension and a runtime to manipulate XML. It was promoted by Mozilla but failed to become mainstream and is now deprecated. JSX was inspired by E4X. In this article, I'm going to go over all the features of E4X and explain the design decisions behind JSX.
Historical Context
E4X has been created in 2002 by John Schneider. This was the golden age of XML where it was being used for everything: data, configuration files, code, interfaces (DOM) ... E4X was first implemented inside of Rhino, a Javascript implementation from Mozilla written in Java.
At the time, a very common operation was to transform XML documents into other XML documents, especially in the Java world. The two major ways to do that were either XSLT or the DOM API. Both those technologies suffer from very bad reputation as they are very tedious to work with.
Since then, the Javascript landscape evolved and the assumptions E4X was developed under do not hold true anymore. JSON has now largely replaced XML to represent data and JSON can be manipulated natively within Javascript. Libraries like jQuery made DOM searching, filtering and basic manipulation a lot easier thank to CSS selectors.
Creating a DOM structure is the only major problem that is still not properly solved. Current solutions involve creating a different "templating" language language (Mustache, Jade), creating a poor man's DSL (MagicDOM) or to use HTML modified with special attributes (Angular). This is the problem JSX is trying to solve.
The Good Parts
XML syntax is particularly good at expressing interfaces. Many people (including myself) have tried to create pure Javascript libraries that have a syntax similar to XML but none of them look really good.
With E4X, you can write XML within Javascript like this:
var header = <div> <h1><a href="/">Vjeux</a></h1> <h2>French Web Developer</h2> </div>; |
The real power of E4X comes from the interpolation mechanism. You can write Javascript expressions within {}
. This lets you write dynamic HTML.
var links = [ {name: 'Talks & Written Reports', url: '/reports'}, {name: 'Contact', url: '/contact/'} ]; var body = <body> {header} <div class="left_nav"> <input type="text" name="search" /> <h2>About Me</h2> <ul>{section.elements.map(function(element) { var isActive = element.url == window.location; return <li class={isActive ? 'active' : ''}> {isActive ? element.name : <a href={element.url}>{element.name}</a> } </li>; })}</ul> </div> </body> |
This example shows that this simple construct coupled with Javascript can solve what templates have been designed to.
- partials:
header
has been defined somewhere else and the resulting DOM node is just being included. In this caseheader
was just a variable but I could as easily have used a function call to create it. - lists: The result of the interpolation can be a Javascript array. In order to create it, you can use the default
map
,filter
, or any Javascript library that manipulate arrays (like Underscore for example). - conditions: Again, I don't need a special syntax here, Javascript already has the ternary operator. For more complex conditions you can call a function that will contain if/then/else statements.
- nesting: Within an interpolated block, you can write XML in which you can use another interpolated block, and so on ... With templates you can only do that one level, if you have a problem that requires you to have more, then you have to go back to string concatenation.
The XML notation and the extremely powerful interpolation mechanism have been re-used as is in JSX. Now let's see the other parts of E4X that didn't work so well and what JSX does to address them.
XML Objects
While the XML object looks and behaves in a similar way to a regular JavaScript object, the two are not the same thing. E4X introduces new syntax that only works with E4X XML objects. The syntax is designed to be familiar to JavaScript programmers, but E4X does not provide a direct mapping from XML to native JavaScript objects; just the illusion of one.
Downsides
The major use case of XML within Javascript is to write HTML tags. Unfortunately, what E4X generates is not a DOM node. In order to use it to generate DOM nodes, you've got to do a conversion phase not provided by default.
The second use case of XML is to represent data. In Javascript world, this use case is already being fulfilled by JSON. E4X only supports strings as a data type where Javascript objects can contain numbers, booleans, functions ...
All the code is not going to be converted to E4X right away. There's going to be a transition phase where E4X and non E4X code will have to co-exist. The fact that the objects E4X generates are not accessible from non E4X code means that none of the libraries ever written can work with E4X structures.
JSX
JSX, contrary to E4X, does not contain a runtime for XML, it is only a syntactic sugar. It translates XML syntax into function calls:
<Node attr1="first" attr2="second"> <Child /> </Node> |
is converted into the following standard Javascript:
Node({attr1: 'first', attr2: 'second'}, Child({}) ) |
The syntax is no longer tied to a specific implementation of the node representation. JSX is pure syntactic sugar. There are cases where you cannot use JSX, for example if you are writing your application in CoffeeScript, but it doesn't prevent you from using the underlying implementation of the XML nodes.
Because it is only a syntactic sugar, there is no need to provide a way to express all the edge cases. Computed attributes, for example, are tricky because they introduce a lot of implementation specific questions when the same attribute is specified more than once. Instead of dealing with them, it has been decided not to support it in JSX and let the users do it in regular Javascript.
var attributes = {a: 1, b: 2, c: 3}; <Node *{attributes} /> // Not valid JSX Node(attributes) // Regular Javascript equivalent |
Namespaces
Each node is identified by a name. In order to prevent conflict in the meaning of the nodes, each node also contains a namespace, encoded as an URI.
default xml namespace = "http://www.w3.org/1999/xhtml"; <div />.name(); // { localName: 'div', uri: 'http://www.w3.org/1999/xhtml' } <svg xmlns="http://www.w3.org/2000/svg" />.name(); // { localName: 'svg', uri: 'http://www.w3.org/1999/svg' } <svg xmlns="http://www.w3.org/2000/svg"><circle /></svg>..circle.name(); // { localName: 'circle', uri: 'http://www.w3.org/1999/svg' } |
Every single element in E4X contains a namespace. Most of them use the default namespace that you can override using a special JS syntax default xml namespace =
. You can also set a namespace on a node using the xmlns
attribute and it's going to be propagated to all the sub-tree.
The namespaces solve the problem of name conflict. E4X implements it making an identifier (URI namespace, String name) unique. This is indeed working but have downsides.
Downsides
This is very weird to have to specify a URL because this URL is just a unique identifier, it is not going to get fetched or influence the way the program run.
Maintaining a unique URL and making sure it is going to stay valid is not a trivial task. There are also many questions that are raised such as what happens if your project is private. How do you deal with versioning.
URLs are usually very long and they are a distraction when you look at the code. Everyone just copy and pasted the doctype value pre-HTML5.
In the end, the XML namespaces didn't get really popular in the front-end community.
JSX
JSX uses Javascript to deal with the namespace problem. In E4X, each node type is not represented by a unique string across your program. In JSX a node type is represented by a Javascript variable. You can use all the Javascript features (eg variable hoisting, capturing via closure, function arguments ...) in order to find the proper node type you want to use.
var div = ReactDOM.div; <div /> var div = JSXDOM.div; <div /> var div = SomeJSFunctionThatReturnsADiv(); <div /> |
JSX is also totally optional. The actual representation of a node type is behind the scenes just a Javascript function. If you are not using JSX, you can create the node by calling it the following way:
// Using JSX: <div attr="str"><br /></div> // Without JSX: ReactDOM.div({attr: 'str}, ReactDOM.br()); |
Because native DOM elements are used so often, JSX also have a way to declare a default namespace. You can add a special comment at the top of the file @jsx ReactDOM
and JSX will assume that all the native elements are attributes of the object you mentioned. This is not the cleanest API but it works.
/** @jsx React.DOM */ <div /> // ReactDOM.div |
Query Language
E4X is not only about creating XML fragments, it also extends Javascript syntax in order to find elements in an XML document. The integration is well done and expressive, it is worth looking at it.
var people = ( <people> <person><name>Bob</name><age>32</age></person> <person><name>Joe</name><age>46</age></person> </people> ); people.person.(name == "Joe").age // 46 // Filter expressions can even use JavaScript functions: function over40(i) { return i > 40; } people.person.(over40(parseInt(age))).name // Joe elem.@attr // attribute |
Downsides
In my opinion, this is one of the main reason that prevented E4X from being more widely adopted.
In order to get a new language extension to be adopted by all the browsers, the bar is extremely high and this feature doesn't seem to cut it.
- It isn't really an issue in the community. jQuery became pretty successful thanks to its way to query the DOM. The overall feeling is that the current solution is satisfying and here hasn't been a strong push for help at the syntax level.
- The query language extends the surface area. Creating XML and querying XML are two orthogonal concepts that do not need to be addressed together. Bundling them together highly reduces the chance of them being accepted.
- A query language is highly controversial. There isn't a consensus on what's the best way to query a XML document and will probably never be. It is even harder to sell as it isn't even using an already existing standard such as XPath or CSS selectors but comes up with a completely new one.
JSX
It is not part of JSX.
Interpolation
Since E4X is not manipulating a plain string, it is able to differentiate between the parts that are nodes and the parts that are attributes and children. This has the extremely strong property from a security point of view that it can prevent code injections by automatically escaping attributes.
var userInput = '"<script>alert("Pwn3d!");</script>'; <div class={userInput} />.toXMLString() // <div class=""<script>alert("Pwn3d!")</script>"></div> |
Downsides
XML can only manipulate strings and nodes. This means that all the attributes are first converted to strings.
JSX
Since we are now living in a Javascript world, we don't need to restrict ourself to strings. For example, it is possible to use a Javascript object to represent the style
property:
<div style={{borderRadius: 10, borderColor: 'red'}} /> // <div style="-webkit-border-radius: 10px; border-color: red;" /> |
Callbacks don't need to be passed as string that is going to be eval
uated, it is possible to use normal Javascript functions.
<div onClick={function() { console.log('Clicked!'); }} /> |
More is Less
When studying E4X, I stumbled across many small things that I either found weird or not really needed. This section is a compilation of them.
Attributes values are not Strings
The attributes values are not Javascript strings, they are special objects with the toString method implemented.
<div class="something">.@class === 'something' // false <div class="something">.@class.toString() === 'something' // true |
Prototype syntax
They introduced a special syntax function::
to add new methods on XML objects:
XML.prototype.function::fooCount = function fooCount() { return this..foo.length(); }; <foobar><foo/><foo/><foo/></foobar>.fooCount() // returns 3 |
Processing Instructions
E4X supports an obscure variant of XML tags: processing instructions of the form ... ?>
. There is a special flag to enable them in the output, but they are always properly parsed.
<foo><?process x="true"?></foo>.toXMLString() // <foo />; XML.ignoreProcessingInstructions = false; <foo><?process x="true"?></foo>.toXMLString() // <foo><?process x="true"?></foo>; |
Operator overload +=
The += operator can be used to append new elements to an XMLList.
var xmlList = <></>; xmlList += <node />; |
Operator overload for attributes
You can get and set attributes using @attr
.
var element = <foo bar="1" />; console.log(element.@bar); // "1" element.@bar = 2; |
Conclusion
E4X had a lot of potential but unfortunately did not take off. JSX attempts to keep only the good parts of E4X. Since it is much smaller in features and only a Javascript transform, it is more likely to be adopted.