mithril
Version:
A framework for building brilliant applications
347 lines (327 loc) • 16.7 kB
HTML
<head>
<meta charset=UTF-8>
<title> JSX - Mithril.js</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel=stylesheet>
<link href=style.css rel=stylesheet>
<link rel=icon type=image/png sizes=32x32 href=favicon.png>
<meta name=viewport content="width=device-width,initial-scale=1">
</head>
<body>
<header>
<section>
<a class=hamburger href=javascript:;>≡</a>
<h1><img src=logo.svg> Mithril <small>2.0.3</small></h1>
<nav>
<a href=index.html>Guide</a>
<a href=api.html>API</a>
<a href=https://gitter.im/MithrilJS/mithril.js>Chat</a>
<a href=https://github.com/MithrilJS/mithril.js>GitHub</a>
</nav>
</section>
</header>
<main>
<section>
<h1 id=jsx><a href=#jsx>JSX</a></h1>
<ul>
<li>Getting Started<ul>
<li><a href=index.html>Introduction</a></li>
<li><a href=installation.html>Installation</a></li>
<li><a href=simple-application.html>Tutorial</a></li>
<li><a href=learning-mithril.html>Learning Resources</a></li>
<li><a href=support.html>Getting Help</a></li>
</ul>
</li>
<li>Resources<ul>
<li><strong><a href=jsx.html>JSX</a></strong><ul>
<li><a href=#description>Description</a></li>
<li><a href=#setup>Setup</a></li>
<li><a href=#using-babel-with-webpack>Using Babel with Webpack</a></li>
<li><a href=#jsx-vs-hyperscript>JSX vs hyperscript</a></li>
<li><a href=#converting-html>Converting HTML</a></li>
</ul>
</li>
<li><a href=es6.html>ES6+ on legacy browsers</a></li>
<li><a href=animation.html>Animation</a></li>
<li><a href=testing.html>Testing</a></li>
<li><a href=examples.html>Examples</a></li>
<li><a href=integrating-libs.html>3rd Party Integration</a></li>
<li><a href=paths.html>Path Handling</a></li>
</ul>
</li>
<li>Key concepts<ul>
<li><a href=vnodes.html>Vnodes</a></li>
<li><a href=components.html>Components</a></li>
<li><a href=lifecycle-methods.html>Lifecycle methods</a></li>
<li><a href=keys.html>Keys</a></li>
<li><a href=autoredraw.html>Autoredraw system</a></li>
</ul>
</li>
<li>Social<ul>
<li><a href=https://github.com/MithrilJS/mithril.js/wiki/JOBS>Mithril Jobs</a></li>
<li><a href=contributing.html>How to contribute</a></li>
<li><a href=credits.html>Credits</a></li>
<li><a href=code-of-conduct.html>Code of Conduct</a></li>
</ul>
</li>
<li>Misc<ul>
<li><a href=framework-comparison.html>Framework comparison</a></li>
<li><a href=change-log.html>Change log/Migration</a></li>
<li><a href=https://mithril.js.org/archive/v1.1.6/ >v1 Documentation</a></li>
<li><a href=https://mithril.js.org/archive/v0.2.5/ >v0.2 Documentation</a></li>
</ul>
</li>
</ul>
<hr>
<h3 id=description><a href=#description>Description</a></h3>
<p>JSX is a syntax extension that enables you to write HTML tags interspersed with JavaScript. It's not part of any JavaScript standards and it's not required for building applications, but it may be more pleasing to use depending on you or your team's preferences.</p>
<pre><code class=language-jsx>function MyComponent() {
return {
view: () =>
m("main", [
m("h1", "Hello world"),
])
}
}
// can be written as:
function MyComponent() {
return {
view: () => (
<main>
<h1>Hello world</h1>
</main>
)
}
}</code></pre>
<p>When using JSX, it's possible to interpolate JavaScript expressions within JSX tags by using curly braces:</p>
<pre><code class=language-jsx>var greeting = "Hello"
var url = "https://google.com"
var link = <a href={url}>{greeting}!</a>
// yields <a href="https://google.com">Hello!</a></code></pre>
<p>Components can be used by using a convention of uppercasing the first letter of the component name:</p>
<pre><code class=language-jsx>m.render(document.body, <MyComponent />)
// equivalent to m.render(document.body, m(MyComponent))</code></pre>
<hr>
<h3 id=setup><a href=#setup>Setup</a></h3>
<p>The simplest way to use JSX is via a <a href=https://babeljs.io/ >Babel</a> plugin.</p>
<p>Babel requires npm, which is automatically installed when you install <a href=https://nodejs.org/en/ >Node.js</a>. Once npm is installed, create a project folder and run this command:</p>
<pre><code class=language-bash>npm init -y</code></pre>
<p>If you want to use Webpack and Babel together, <a href=#using-babel-with-webpack>skip to the section below</a>.</p>
<p>To install Babel as a standalone tool, use this command:</p>
<pre><code class=language-bash>npm install @babel/cli @babel/preset-env @babel/plugin-transform-react-jsx --save-dev</code></pre>
<p>Create a <code>.babelrc</code> file:</p>
<pre><code class=language-json>{
"presets": ["@babel/preset-env"],
"plugins": [
["@babel/plugin-transform-react-jsx", {
"pragma": "m",
"pragmaFrag": "'['"
}]
]
}</code></pre>
<p>To run Babel as a standalone tool, run this from the command line:</p>
<pre><code class=language-bash>babel src --out-dir bin --source-maps</code></pre>
<h4 id=using-babel-with-webpack><a href=#using-babel-with-webpack>Using Babel with Webpack</a></h4>
<p>If you're already using Webpack as a bundler, you can integrate Babel to Webpack by following these steps.</p>
<pre><code class=language-bash>npm install @babel/core babel-loader @babel/preset-env @babel/plugin-transform-react-jsx --save-dev</code></pre>
<p>Create a <code>.babelrc</code> file:</p>
<pre><code class=language-json>{
"presets": ["@babel/preset-env"],
"plugins": [
["@babel/plugin-transform-react-jsx", {
"pragma": "m",
"pragmaFrag": "'['"
}]
]
}</code></pre>
<p>Next, create a file called <code>webpack.config.js</code></p>
<pre><code class=language-jsx>const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './bin'),
filename: 'app.js',
},
module: {
rules: [{
test: /\.js$/,
exclude: /\/node_modules\//,
loader: 'babel-loader'
}]
}
}</code></pre>
<p>For those familiar with Webpack already, please note that adding the Babel options to the <code>babel-loader</code> section of your <code>webpack.config.js</code> will throw an error, so you need to include them in the separate <code>.babelrc</code> file.</p>
<p>This configuration assumes the source code file for the application entry point is in <code>src/index.js</code>, and this will output the bundle to <code>bin/app.js</code>.</p>
<p>To run the bundler, setup an npm script. Open <code>package.json</code> and add this entry under <code>"scripts"</code>:</p>
<pre><code class=language-json>{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch"
}
}</code></pre>
<p>You can now then run the bundler by running this from the command line:</p>
<pre><code class=language-bash>npm start</code></pre>
<h4 id=production-build><a href=#production-build>Production build</a></h4>
<p>To generate a minified file, open <code>package.json</code> and add a new npm script called <code>build</code>:</p>
<pre><code class=language-json>{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch",
"build": "webpack -p",
}
}</code></pre>
<p>You can use hooks in your production environment to run the production build script automatically. Here's an example for <a href=https://www.heroku.com/ >Heroku</a>:</p>
<pre><code class=language-json>{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch",
"build": "webpack -p",
"heroku-postbuild": "webpack -p"
}
}</code></pre>
<hr>
<h3 id=jsx-vs-hyperscript><a href=#jsx-vs-hyperscript>JSX vs hyperscript</a></h3>
<p>JSX and hyperscript are two different syntaxes you can use for specifying vnodes, and they have different tradeoffs:</p>
<ul>
<li><p>JSX is much more approachable if you're coming from an HTML/XML background and are more comfortable specifying DOM elements with that kind of syntax. It is also slightly cleaner in many cases since it uses fewer punctuation and the attributes contain less visual noise, so many people find it much easier to read. And of course, many common editors provide autocomplete support for DOM elements in the same way they do for HTML. However, it requires an extra build step to use, editor support isn't as broad as it is with normal JS, and it's considerably more verbose. It's also a bit more verbose when dealing with a lot of dynamic content because you have to use interpolations for everything.</p>
</li>
<li><p>Hyperscript is more approachable if you come from a backend JS background that doesn't involve much HTML or XML. It's more concise with less redundancy, and it provides a CSS-like sugar for static classes, IDs, and other attributes. It also can be used with no build step at all, although <a href=https://github.com/MithrilJS/mopt>you can add one if you wish</a>. And it's slightly easier to work with in the face of a lot of dynamic content, because you don't need to "interpolate" anything. However, the terseness does make it harder to read for some people, especially those less experienced and coming from a front end HTML/CSS/XML background, and I'm not aware of any plugins that auto-complete parts of hyperscript selectors like IDs, classes, and attributes.</p>
</li>
</ul>
<p>You can see the tradeoffs come into play in more complex trees. For instance, consider this hyperscript tree, adapted from a real-world project by <a href=https://github.com/isiahmeadows/ >@isiahmeadows</a> with some alterations for clarity and readability:</p>
<pre><code class=language-javascript>function SummaryView() {
let tag, posts
function init({attrs}) {
Model.sendView(attrs.tag != null)
if (attrs.tag != null) {
tag = attrs.tag.toLowerCase()
posts = Model.getTag(tag)
} else {
tag = undefined
posts = Model.posts
}
}
function feed(type, href) {
return m(".feed", [
type,
m("a", {href}, m("img.feed-icon[src=./feed-icon-16.gif]")),
])
}
return {
oninit: init,
// To ensure the tag gets properly diffed on route change.
onbeforeupdate: init,
view: () =>
m(".blog-summary", [
m("p", "My ramblings about everything"),
m(".feeds", [
feed("Atom", "blog.atom.xml"),
feed("RSS", "blog.rss.xml"),
]),
tag != null
? m(TagHeader, {len: posts.length, tag})
: m(".summary-header", [
m(".summary-title", "Posts, sorted by most recent."),
m(TagSearch),
]),
m(".blog-list", posts.map((post) =>
m(m.route.Link, {
class: "blog-entry",
href: `/posts/${post.url}`,
}, [
m(".post-date", post.date.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})),
m(".post-stub", [
m(".post-title", post.title),
m(".post-preview", post.preview, "..."),
]),
m(TagList, {post, tag}),
])
)),
])
}
}</code></pre>
<p>Here's the exact equivalent of the above code, using JSX instead. You can see how the two syntaxes differ just in this bit, and what tradeoffs apply.</p>
<pre><code class=language-jsx>function SummaryView() {
let tag, posts
function init({attrs}) {
Model.sendView(attrs.tag != null)
if (attrs.tag != null) {
tag = attrs.tag.toLowerCase()
posts = Model.getTag(tag)
} else {
tag = undefined
posts = Model.posts
}
}
function feed(type, href) {
return (
<div class="feed">
{type}
<a href={href}><img class="feed-icon" src="./feed-icon-16.gif" /></a>
</div>
)
}
return {
oninit: init,
// To ensure the tag gets properly diffed on route change.
onbeforeupdate: init,
view: () => (
<div class="blog-summary">
<p>My ramblings about everything</p>
<div class="feeds">
{feed("Atom", "blog.atom.xml")}
{feed("RSS", "blog.rss.xml")}
</div>
{tag != null
? <TagHeader len={posts.length} tag={tag} />
: (
<div class="summary-header">
<div class="summary-title">Posts, sorted by most recent</div>
<TagSearch />
</div>
)
}
<div class="blog-list">
{posts.map((post) => (
<m.route.Link class="blog-entry" href={`/posts/${post.url}`}>
<div class="post-date">
{post.date.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</div>
<div class="post-stub">
<div class="post-title">{post.title}</div>
<div class="post-preview">{post.preview}...</div>
</div>
<TagList post={post} tag={tag} />
</m.route.Link>
))}
</div>
</div>
)
}
}</code></pre>
<hr>
<h3 id=converting-html><a href=#converting-html>Converting HTML</a></h3>
<p>In Mithril, well-formed HTML is generally valid JSX. Little more than just pasting raw HTML is required for things to just work. About the only things you'd normally have to do are change unquoted property values like <code>attr=value</code> to <code>attr="value"</code> and change void elements like <code><input></code> to <code><input /></code>, this being due to JSX being based on XML and not HTML.</p>
<p>When using hyperscript, you often need to translate HTML to hyperscript syntax to use it. To help speed up this process along, you can use a <a href=https://arthurclemens.github.io/mithril-template-converter/index.html>community-created HTML-to-Mithril-template converter</a> to do much of it for you.</p>
<hr>
<small>License: MIT. © Leo Horie.</small>
</section>
</main>
<script src=https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/prism.min.js defer></script>
<script src=https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/components/prism-jsx.min.js defer></script>
<script src=https://unpkg.com/mithril@2.0.3/mithril.js async></script>
<script>
document.querySelector(".hamburger").onclick = function() {
document.body.className = document.body.className === "navigating" ? "" : "navigating"
document.querySelector("h1 + ul").onclick = function() {
document.body.className = ''
}
}
</script>