cream-and-sugar
Version:
A deliciously functional syntax for JavaScript with native support for JSX
208 lines (194 loc) • 11.3 kB
HTML
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Cream & Sugar: CreamML</title>
<link rel="icon" type="image/x-icon" href="https://jgnewman.github.io/cream-and-sugar/assets/images/favicon.ico" />
<link href="https://fonts.googleapis.com/css?family=Bitter:400,700|Open+Sans:300,400,800" rel="stylesheet">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css" />
<link rel="stylesheet" href="../../assets/css/app.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.8.0/styles/default.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.8.0/highlight.min.js"></script>
</head>
<body class="docs">
<div class="branding"></div>
<div class="content">
<div class="left-border">
<h1>CreamML</h1>
<h2 class="subhead">JSX, But As a Real Part of the Language</h2>
</div>
<blockquote>
<p>If you do not already know what JSX is, you should visit the <a href="https://facebook.github.io/react/docs/jsx-in-depth.html">React JS in-depth explanation</a> first. If React is not your cup of tea, you can skip ahead to the section entitled "CreamML Without React".</p>
</blockquote>
<pre><code># JSX designed for JavaScript
<div id={id} className="foo">
<span>Hello, world!</span>
</div>
function createJSX(id) {
return (
<div id={id} className="foo">
<span>Hello, world!</span>
</div>
);
}
</code></pre><p>As you should already be aware, JSX is basically HTML that you can write into JavaScript. It was invented for use with React.js and looks like the provided example, including a function that creates some.</p>
<p>When compiled, each of the nodes in our JSX is converted into a call to <code>React.createElement</code>. With Cream & Sugar, you can do the same thing with only a few minor differences. Because these differences exist, C&S' XML-like syntax is called "CreamML" rather than JSX. This next example illustrates how to write the same function with C&S.</p>
<p><br/>
<br/></p>
<pre><code>createJSX id =>
<div id={id} className="foo">
<span>"Hello, world!"</span>
</div>
</code></pre><p>What's pretty cool is, C&S is smart and will detect if React exists within your available code. If so, it will convert each CreamML node into a call to <code>React.createElement</code>. If not, it will convert each CreamML node into a call to <code>CNS_.createElement</code> which will still produce a series of nested DOM nodes.</p>
<blockquote>
<p>Protip: <code>CNS_</code> is a reserved word in C&S. If you try to use it, the
compiler will complain. It's reserved for built-in functions that are included
in the compiled code, only if they are needed.</p>
</blockquote>
<p>As previously stated, there are a few minor differences between pure JSX and CreamML. To learn more about that, read on.</p>
<h2 id="creamml-without-react">CreamML Without React</h2>
<pre><code>container = dom '#my-container'
update 'innerHTML', <div>'Hello'</div>, container
#=> <div id="my-container">
# <div>Hello</div>
# </div>
</code></pre><p>C&S' ability to use handle JSX-like syntax is pretty handy because, if you aren't using React, you can still make use of this syntax to dynamically build DOM nodes</p>
<blockquote>
<p>Another protip: <code>dom</code> is a built-in function that makes selecting DOM nodes
easy. Cream and Sugar contains many such built-in functions.</p>
</blockquote>
<p>One thing to keep in mind, of course, is that C&S is a functional language and,
as such, it (usually) doesn't modify currently existing values. Instead it creates new
ones. So in the previous example, the <code>#my-container</code> div was only "updated"
in the sense that a clone of that node was created and had its <code>innerHTML</code>
populated.</p>
<p>There are a couple of other <em>minor</em> differences between C&S' version of JSX
and the pure React version as well.</p>
<h3 id="quote-all-text">Quote all text</h3>
<pre><code># JSX
<div>This is some floating text.</div>
# CreamML
<div>"This is some floating text."</div>
</code></pre><p>In C&S, you will need to surround any floating text with quotes. Because
CreamML syntax is integrated into the language and is not handled as a
separate processing step, the compiler will see any floating text as a bunch of
variable names, try to convert them into nested function calls, and get very
confused.</p>
<pre><code># Bad output from non-quoted text
React.createElement('div', {}, [
This(is(some(floating(text.))))
])
#=> Error!
</code></pre><p>Another way to explain this is that the language grammar doesn't change
if you're inside a CreamML node. Therefore, you need to quote text to prevent the compiler from doing what is shown in the example. A string, after all, is still a string, even if you're using html-like syntax.</p>
<pre><code><div>`Hello, my name is ${name}`</div>
</code></pre><p>Of course, you can still easily drop dynamic values into text, thus making it a little safer to use curly braces in your text. You can also avoid using curly braces at all in a lot of places.</p>
<pre><code># JSX
<div>
Two plus two is
(function () {
return <strong>{2 + 2}</strong>;
}())
</div>
# CreamML
<div>
'Two plus two is'
apply fn => <strong>2 + 2</strong>
</div>
</code></pre><p>It also becomes <em>extremely</em> easy to execute functions right in the middle
of the html body.</p>
<blockquote>
<p>Notice the use of the <code>apply</code> function in this example. It's a function that takes
another function as an argument and executes it. Optionally, you can pass in
a list of args in the form of an array as a second argument to <code>apply</code>.</p>
</blockquote>
<h3 id="using-parens-instead-of-curlies">Using parens instead of curlies</h3>
<p>In JSX, you use curly braces every time you want to reference a value or perform a calculation. While you can often do the same thing with CreamML, the curlies sometimes make it dangerous to work with certain data types.</p>
<pre><code><div
className="foo"
attr={fn => {foo: bar}}>
# Error! ––––––––––––––^^
# Unexpected "}}"
</code></pre><p>The provided example shows the potential for an error caused by placing an object near the termination of these curly braces, incorrectly causing the compiler to detect a tuple-closing double curly.</p>
<p><br/>
<br/></p>
<pre><code><div
className="foo"
attr={ fn => {foo: bar} }>
<div
className="foo"
attr=(fn => {foo: bar})>
</code></pre><p>The previous example can be prevented via smart use of spaces or by using parens instead of curly braces to surround dynamic values. Parens are actually more in line with "the C&S way" but curlies are allowed in CreamML because many users are familiar with curlies in the context of JSX.</p>
</div>
<section id="docs-app"></section>
<script>
// Section for random JavaScript hacks just to get this out quickly.
window.addEventListener('load', function () {
// Fix indentation in code blocks the best we can since panini's
// markdown compiler injects whitespace into pre tags.
Array.prototype.slice.call(document.querySelectorAll('pre code')).forEach(function (code) {
var lines = code.innerHTML.trim().split('\n');
var out = [lines[0]];
var sliceAmount;
lines.slice(1).forEach(function (line, index) {
if (index === 0) {
var whitespace = line.match(/^\s+/);
whitespace = whitespace ? whitespace[0] : '';
if (/([=-](\>)?|\{|match|\>|div|try|chain|default\s+[^\n])$/.test(lines[0])) {
whitespace = whitespace.slice(2);
}
sliceAmount = whitespace;
}
out.push(line.replace(sliceAmount, ''));
});
code.innerHTML = out.join('\n');
code.className = 'visible lang-coffeescript';
hljs.highlightBlock(code);
});
// Attach id's to h4 tags so that we can link to specific bifs.
Array.prototype.slice.call(document.querySelectorAll('h4')).forEach(function (h4) {
var html = h4.innerHTML.split(/\s+/g);
html[0] = '<span class="bif" id="' + html[0] + '">' + html[0] + '</span>';
h4.innerHTML = html.join(' ');
h4.className = 'visible';
});
// Fix relative link issues with the github site living in a subdirectory
Array.prototype.slice.call(document.querySelectorAll('.content a')).forEach(function (a) {
if (/^\/cream-and-sugar\//.test(location.pathname)) {
a.setAttribute('href', '/cream-and-sugar' + a.getAttribute('href'))
}
});
// Some script conflict is preventing us from linking to a certain
// spot on the page so here we just wait until the next run loop
// and if we have a hash in the location object, scroll to it.
(function () {
var hash = location.hash;
if (hash) {
var item = document.querySelector(hash);
setTimeout(function () {
item && window.scroll(0, item.offsetParent.offsetTop);
},0);
}
}());
// Fixes a responsiveness issue where on large screens sometimes
// the offgray sidebar isn't tall enough. Make sure its always
// tall enough whenever the screen resizes.
(function () {
var branding = document.querySelector('.branding');
var content = document.querySelector('.content');
function resizerizer() {
var brandingHeight = branding.offsetHeight;
content.style.height = '';
if (content.offsetHeight < brandingHeight) {
content.style.height = brandingHeight + 'px';
}
}
window.addEventListener('resize', resizerizer);
resizerizer();
}());
});
</script>
<script src="../../assets/js/app.js"></script>
</body>
</html>