JavaScript in 2016: let's make it clear

Today's post isn't quite of the strictly-technical kind I usually write, but rather a response to some late trend about JavaScript that has gone on for far too long.

This year's beginning has been scattered with ranting articles bearing overdramatic titles, such as this one and that one, coming from developers who have come to hate the modern JS ecosystem. As a strong believer in the opposite point of view, I will hereby review their concerns one by one.

  • "Node.js is immature": yeah, right, that must be why PayPal has been using it in production for two years now. Oh, and by the way, the whole fork story has come to an end, the most obvious proof of that being the fact Node's versioning has jumped from v0.x to v4.x, to account for io.js's own three stable major versions. However, apart from build systems (such as Grunt) running on the developer's machine, what does it ever have to do with front-end code? Those build systems could be running Ruby for that matter, it doesn't change a thing.
  • "Maintaining a project / a development environment is hard": maybe you bet on the wrong horse. I chose to use Gulp a while ago (as in "several years", actually not long after it came out in the first place) as it seemed to be the tool that best suited my needs. It hasn't changed since, nor have I switched it for anything else. I read up on Webpack and other solutions, but didn't change just because they were newer: Gulp kept being ideal for me while remaining relevant in the market (as in "used at all"). This leads me to the next item...
  • "There is no predominant solution": oh no, now we have to think about what we're doing! The JS ecosystem indeed is in its teenage phase, as tools are blooming everywhere all the time. But in the end, only a few will survive: the ones that are solidly maintained, that managed to gather a community around them and, the most important thing, do what they claim to do, and do it right. It's just like any other programming ecosystem around here, the only difference being there are more different choices. Intelligence is all it takes to choose the right tool for the right job, if that job needs any tool in the first place, and wiseness is all it takes to check which tools will last in the long run, and which of these are of interest to you: these are the ones you want to go on with. You know what? I brag about React a lot, but I haven't even tried any Flux implementation around (like Redux) yet, and thank you, I feel quite fine.
  • "Everyone uses JS for everything, even when it's wrong": yes, some people do that. It doesn't mean everyone does that. Not everyone fires up React for any static page. Stop thinking what you read and remember is everything that exists. Who's going to write an article just to say they built an app without a JS framework and a build system, just to keep people like you happy and confident in their own denial?

JavaScript is a messy language, but it's getting better: ES6 is great and ES7 will be, too. React is great. Node is great. You are not supposed to love them three (or anything else). If you do, you will probably build great stuff with them. If you don't, it's no big deal, but don't ruin the party for everyone. Stop thinking you know better and bring up one actually constructive argument to begin with. Target specifics, instead of just concluding everything is turning to shit, because you just sound alike.

Gawd.

Listen to stores outside of the view layer in a Flux application

What I'm going to demonstrate today is more of an interesting concept than a technical achievement. Have you ever wanted to respond to store data changes in your Flux application, but in a way that is totally unrelated to, say, React?

Well, you probably already have some components doing it, so it shouldn't be hard. Let's try it out by saying we would like to do something upon authentication when the app boots:

// src/listeners/loginListener.js
'use strict';

let userStore = require('../stores/userStore');

module.exports = userStore.addChangeListener.bind(
userStore,
function() {
let user = userStore.getUser();
console.log(user);
}
);

Binding is pretty easy, too; just do something like the following in your entry point:

// src/app.js
require('./listeners/loginListener')();

Repeating this pattern brings you a basic form of standalone store event listeners that you can use throughout your app, avoiding polluting view components with stuff that doesn't really belong in them.

Handle translations in component attributes with react-translate-component

The react-translate-component library provides a component, which is fed a translation key and yields a span with the translated string in return. When switching locales through a menu in your app as demonstrated in the docs, labels are updated accordingly. The problem is that you cannot use a component inside of a HTML attribute, for example; directly using Counterpart, the framework-agnostic library underneath the former, works on page load but the automatic update effect is lost, as its translate method is not called again.

The solution is quite simple: force this method to be called again by triggering a state change throughout the app. All we have to do is listen to Counterpart's events in our root component, pretty much like we would with a Flux store:

var React = require('react'),
Counterpart = require('counterpart');

export default class Root extends React.Component
{
constructor()
{
this.state = { locale: Counterpart.getLocale() };
}

onLocaleChange()
{
this.setState({ locale });
}

componentDidMount()
{
Counterpart.onLocaleChange(this.onLocaleChange);
}

componentWillUnmount()
{
Counterpart.offLocaleChange(this.onLocaleChange);
}

render()
{
// ...
}
}

Strings directly processed by Counterpart will now be reevaluated when the locale changes without any extra code.

I am still unsure whether this could become a performance pitfall on a large scale, please feel free to comment about that, or anything else for that matter!

Rendering React with PHP, part two: semantic response codes

If you've read my last article, you now know how to render React code in a PHP app in order to serve the same content for the same URL regardless of how we navigate to it (hitting the server or interacting in the client). Today, we are going to build upon last time's example in order to make our Silex app yield semantic HTTP response codes based on what react-router says: 302s for redirects, 404s for inexistant URLs and 500s for errors.

Let's start by modifying our PHP code:

<?php

use Silex\Application;
use Silex\Provider\TwigServiceProvider;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

require_once(__DIR__.'/vendor/autoload.php');

$app = new Application();
$app['v8js'] = new V8Js();

$app->register(new TwigServiceProvider(), ['twig.path' => __DIR__.'/views']);

$app
->get(
'/{uri}',
function ($uri) use ($app) {
// Define content markers for special cases
$redirectPrefix = 'redirect=';
$notFound = 'notfound';
$errorPrefix = 'error=';

$js = implode(';', [
'var uri = \'/'.$uri.'\'',
// Inject them to be able to use them on the JS side
'var redirectPrefix = \''.$redirectPrefix.'\'',
'var notFound = \''.$notFound.'\'',
'var errorPrefix = \''.$errorPrefix.'\'',
file_get_contents(__DIR__.'/dist/app.js')
]);

ob_start();
$app['v8js']->executeString($js);
$content = ob_get_clean();

if (preg_match('/^'.$redirectPrefix.'/', $content)) {
// JS-generated response is "redirect=/new/uri"
return new RedirectResponse(substr($content, strlen($redirectPrefix)));
}

if ($notFound === $content) {
throw new NotFoundHttpException();
}

if (preg_match('/^'.$errorPrefix.'/', $content)) {
// JS-generated response is "error=Some error message"
throw new RuntimeException(substr($content, strlen($errorPrefix)));
}

return $app['twig']->render('app.twig', compact('content'));
}
)
->assert('uri', '.*')
->value('uri', '');

$app->run();

We will now make our JavaScript code craft appropriately shaped responses to make it all work together:

import React from 'react';
import ReactDOM from 'react-dom';
import { renderToString } from 'react-dom/server';
import { match, RoutingContext } from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import routes from './routes';

(function() {
'use strict';

if ('undefined' !== typeof document) {
// client-side
// ...
} else {
match(
{
routes,
location: uri
},
function(error, redirectLocation, renderProps) {
print(error
? errorPrefix + JSON.stringify(error)
: redirectLocation
? redirectPrefix + redirectLocation.pathname + redirectLocation.search
: renderProps
? renderToString(<RoutingContext {...renderProps} />)
: notFound
);
}
);
}
})();

And it works! To try it out, you can add a redirect to your routes as follows:

import Root from './components/root';
import HomePage from './components/homePage';
import OtherPage from './components/otherPage';

export default {
path: '/',
component: Root,
indexRoute: {
component: HomePage
},
childRoutes: [
{
path: 'other',
component: OtherPage
},
{
path: 'redirect',
onEnter: function (nextState, replaceState) {
replaceState(null, '/');
}
}
]
};

You obviously also can use react-router's JSX notation, if you prefer to do so.

Routing-aware React server-side rendering on PHP with Silex and react-router

Not everybody can use Node.js on the server side to be able to craft truly universal JavaScript applications. Still, one might want to set up server-side rendering to enhance React's performances, and of course allow JS-uncapable clients to use their app seamlessly (at least regarding navigation).

In , I quickly demonstrated how to render React code with PHP's v8js extension. We will now see how to handle the routing with Silex and react-router.

Note: this article's snippets are written using ES6.

First, let's define our routes:

import Root from './components/root';
import HomePage from './components/homePage';
import OtherPage from './components/otherPage';

export default {
path: '/',
component: Root,
indexRoute: {
component: HomePage
},
childRoutes: [
{
path: 'other',
component: OtherPage
}
]
};

Here are the components:

import React from 'react';

export default class Root extends React.Component
{
render()
{
return (
<div>{this.props.children}</div>
);
}
}

import React from 'react';

export default class HomePage extends React.Component
{
render()
{
return (
<div>Hello world!</div>
);
}
}

import React from 'react';

export default class OtherPage extends React.Component
{
render()
{
return (
<div>Hi there! I am another page.</div>
);
}
}

We add the entry point (typically, app.js) to the mix:

import React from 'react';
import ReactDOM from 'react-dom';
import { renderToString } from 'react-dom/server';
import { match, RoutingContext } from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import routes from './routes';

(function() {
'use strict';

if ('undefined' !== typeof document) {
// We're on the client, nothing new here
ReactDOM.render(
<Router
routes={routes}
history={createBrowserHistory({ queryKey: false })}
/>,
document.getElementById('app') // don't use document.body
);
} else {
// We're on the server, this will be executed by v8js
match(
{
routes,
location: uri
},
function(error, redirectLocation, renderProps) {
print(error
? error.message
: renderToString(<RoutingContext {...renderProps} />)
);
}
);
}
})();

You can see I used the same hack as in my previous article to tell apart client and server-side rendering routines. You can also see that on the server, the matched location used for routing comes from a variable named uri, that wasn't declared anywhere. To understand this, we must jump to index.php:

<?php

use Silex\Application;
use Silex\Provider\TwigServiceProvider;

require_once(__DIR__.'/vendor/autoload.php');

$app = new Application();
$app['v8js'] = new V8Js();

$app->register(new TwigServiceProvider(), ['twig.path' => __DIR__.'/views']);

$app
->get(
'/{uri}',
function ($uri) use ($app) {
$js = implode(';', [
'var uri = \'/'.$uri.'\'', // inject URI
file_get_contents(__DIR__.'/dist/app.js')
]);

ob_start();
$app['v8js']->executeString($js);
$content = ob_get_clean();

return $app['twig']->render('app.twig', compact('content'));
}
)
->assert('uri', '.*')
->value('uri', '');

$app->run();

We end with our Twig template:

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<title>Test</title>
</head>

<body>
<div id="app">{{ content|raw }}</div>
<!-- This script tag is the reason we needed
a container inside the body -->
<script src="dist/app.js"></script>
</body>

</html>

In order for this to work, your browser must support the History API, and you must use a web server such as Apache configured in the likes of this:

<IfModule mod_rewrite.c>
Options -MultiViews

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
</IfModule>

You can then try out server-side rendering by commenting out the script tag in your template, in order to notice your React code truly is rendered by PHP! Navigate to /other and you will see the alternative content show up.

Your app can now be browsed with and without JavaScript!