This post is an extract of a github repo I’m working on chtefi/react-stack-step-by-step to explain step-by-step from scratch a full reactjs stack
The answer is : by applying some linting on your source code.
Code linting is a set of rules to apply, to enforce some styles and rules into the code, and even check if you’re writing bugs (due to typos mostly).
By applying the same rules on the whole source code, you can, for instance, make sure that everywhere there is no missing semicolons (or that there is not semicolons at all if you don’t like them), that the variables are properly named, that the order of methods to override is fixed, that the constructor is the first method, that the depth of the functions is not more than 4 etc. etc.
Hopefully, we can use some presets created by big tech companies to avoid to set them all manually.
Moreover, if they follow those styles, it’s a good opportunity to follow the same ones.!
Let’s see what is needed in order to apply this linting and configure it as you expect.
Moreoever, we will add some special packages to deal with Reactjs code, because that needs more.
What packages to install to do linting ?
We have multiple choices.
JSLint : original project
JSHint : fork of JSLint
ESLint : new recent alternative (2013), pluggable
We are going to stick with ESLint because it supports Reactjs special linting rules through a plugin eslint-plugin-react.
And because the linting is only necessary for the developers, the npm dependency is installed with --save-dev.
$ npm i -D eslint
How to use it
eslint gives us the command of the same name in ./node_modules/.bin/eslint.
It just takes in parameter a folder or a file on which we want to apply some linting such as :
$ ./node_modules/.bin/eslint src
For instance, if we have some ES6 code in there, doing that could lead to some errors :
It’s because by default, ESLint does not understand ES6.
Before fixing that, let’s simplify our life, and create a npm script command to run this command quickly.
Add a npm script
To avoid to type the eslint command each time, let’s add a simple npm script :
"scripts": {
"lint": "eslint src"
...
Remember: when npm executes the scripts, it has access to the ./node_modules/.bin folder automatically eslint refers to. No need to add the path in the script.
Now, let’s fix our ESLint.
ESLint + ES6 + JSX
As the documentation states, we need to create a file .eslintrc at the root of the project to set the configuration.
First of all, let’s make it understand imports.
{
"ecmaFeatures": {
"modules": true
}
}
6:2 error Parsing error: Unexpected token const
Now, it does not understand const. Let’s make it understand ES6.
Boom, it passes without error !
That means it could parse it properly at least.
There is no error, not because the code is already perfect, but because : All rules are disabled by default.
But there are a ton of rules, let’s see how to extend some existing defaults. We are not the first person who want to use it right ?
Extends some default ESLint configuration
It’s recommanded to extend the eslint:recommanded set of rules, to begin with.
But we can also extend some other known ones, such as :
: want to work at airbnb ? Learn their styles.
: a nice style overriding some properties of eslint:recommanded. I’m not fan because it forbids semicolons, commas on the last array item, all those useless things I like to write.
To extends those rules, npm i them or use the eslint:recommanded one directly :
3:8 error "Toolbar" is defined but never used no-unused-vars
19:378 error Unexpected trailing comma comma-dangle
Now we have some linting issues.
But it seems ESLint does not understand yet this kind of program :
import Toolbar from './Toolbar.js';
...
The variable Toolbar is used by (translated to React.createElement(Toolbar)), so the no-unused-vars error is not a true error.
To make it understand that the imported components are used in JSX, we need to install the plugin eslint-plugin-react and add a special rule jsx-uses-react from this plugin, that will remove this ESLint false error.
This one states that you have a line finishing by a trailing comma in object literal and that you should not (because you have a rule that forbids it).
You can also see this kind of error :
6:19 error "document" is not defined no-undef
Javascript environments
ESLint doesn’t know what is document, it didn’t found it in the scope. By default, it does not assume the environment is a browser (because it can be a pure nodejs program, where document does not exist). Therefore, we have to specify we are dealing with javascript that’s going to be used in the browser, and that will have access to document, window, console.log and so on.
This will generate errors if there is a missing trailing comma on arrays or objects that span multiple lines.
The option has no effect if the code is 0 (disabled).
The available options (if there is) depend on the rule, check http://eslint.org/docs/rules/comma-dangle for instance.
Personally, I like those dangle-commas because that means I can switch the lines order without playing with the end-of-lines.
Anyway, as you saw, it’s very configurable and anybody can match its code style and force it everywhere in it source code.
But that’s not useful only for that, but can help to find bugs before runtime.
Bug finding
The biggest issues when typing Javascript are the typos. Because we often lack a good auto-completion, or we are used to type everything, we do typos. And we find them at runtime, not funny eh?
Linting your code to find those typos is a big win :
38:34 error "decription" is not defined no-undef
Typo !
It’s never a good idea to disable the rule no-undef, you can understand why.
More Babel syntax available thanks to babel-eslint
ESLint uses espree to parse ES6.
But we are using Babel, and Babel handles some features such as the spread notation that are not handled by espree :
const obj = { a: 1, ...{ b: 2, c: 3 } };
ESLint won’t be able to parse that :
19:18 error Parsing error: Unexpected token ..
Meaning we need to plug the Babel parser to make it understand.
Hopefully, it’s planned and pretty straightforward, just install this package :
$ npm i -D
We install the latest beta because it is using Babel 6
And define Babel as the parser of ESLint (that will be our last update to .eslintrc) :
It’s quite standard to use this plugin nowadays, because most Javascript projects are using Babel, thus, you always want to parse your code source with Babel, for any third-party app working with it.
Now, you have a proper ESLint configuration, you can code properly and invite people to code with you. You are sure they will follow the style and conventions you enforced.
It’s often a good opportunity to check the linting when building or publishing your package, for instance, in package.json‘s "scripts":
"compile": "npm run lint && webpack",
If an error occurs, webpack won’t be executed.
Some common errors
Let’s quickly go through some classic linting errors :
"Toolbar" is defined but never used | no-unused-vars : if you’re using it won’t find it unless you are using react plugin in ESLint.
"items" is defined but never used | no-unused-vars : a plain js variable you are not using, remove it
Unexpected var, use let or const instead | no-var : var is evil
Strings must use singlequote | quotes : prefer ' over "
Unexpected trailing comma | comma-dangle : the famous trailing comma at the end of multilines
Extra semicolon | semi : if you want or don’t want semicolon at the end of statements
As you understood, the keyword on the right is the code you want to look for to understand what it means or to override it as you wish.
In-code ESLint hints
We only deal with a single file .eslintrc for now, meaning it’s global to all our source code.
But sometimes, you want to make some exception, mostly because you added a hack somewhere.
That generates you a linting warning or error and you just want ESLint to ignore it.
You can add special comment in your code to talk to ESLint :
Your text editor / IDE supports linting on-the-fly
Last but not least, every good text-editors or IDEs have a plugin to automatically apply linting to your code when typing, and display a marker next to the line if something is wrong : because you don’t want to check back your console and re-execute the full linting each time you write some lines.
Check your IDE doc for more info.
Sublime Text plugin
Atom
WebStorm
Visual Studio
…
In my case, I’m using the best text editor aka Sublime Text, you need to install :
This post is an extract of a github repo I’m working on chtefi/react-stack-step-by-step to explain step-by-step from scratch a full reactjs stack
– You already use HR ?
If you have a project using webpack, React, you probably have followed some tutorials and blogs to add the Hot Reloading piece.
Here, we are going to explain what’s going on under the hood, and how the HR projects / plugins you installed are working together (webpack HR middlewares, react-transform..).
We are going to use the latest React HR project in date react-transform-hmr and not the older one react-hot-loader which has been sentenced to the maximum penalty : the-death-of-react-hot-loader. I guess it’s still working properly but you know that the Javascript community is always on the bleeding edge side!
– You don’t already use HR ?
If the HR part does not sound familiar to you, that’s fine because I’m going to explain step-by-step why it’s mandatory to use HR nowadays, and what is the way to install and use it into your project.
Let’s just start by resuming why everybody is now talking about HR (beyond Javascript).
Why should we enjoy Hot Reloading ?
In one word : productivity.
In another word : DX (Developer eXperience).
Some more explanations :
– you broke your F5 key years ago, you can’t refresh anymore
– you don’t want to develop blindly, then refresh, then try again. It’s a backend thing. We have a UI, it’s alive
– you don’t want to lose your application state (by doing a full refresh) if you just fixed a typo or a color
– that can display your compilation and runtime errors directly in the browser in the exact spot in the UI where it’s used
– you can do HR with Javascript, CSS, anything. We’ll just focus on React components here.
– no caveat
As soon as you put it in place, it will work forever without any modifications, for any of the future files you’re going to add. It’s just some pipes to plug together to make it work and last forever.
2015-12-28: actually there is only one caveat (!) : the React HR plugin we are going to use does not handle the stateless functional components created with the simpler React v0.14 syntax : const App = (props) => {
Now, let’s tackle the packages we need to install to use it in a nodejs project.
What packages to install to use HR ?
Let’s suppose you already have a base project using :
– webpack to compile your javascript bundle(s)
– React for your frontend ofc
– a nodejs server that runs a http server such as expressjs to serve your static content (html, js, css..) while you’re developing
You now want to experiment HR.
webpack to the rescue
webpack is actually the main actor dealing with the HR, it already exposes an API to process some part of the HR pipeline.
We just need to add some wrapper around to use its API, and some more logic to manage the specific React components state : you don’t want to lose the current state of your components when you change something in a js file (a style, some constant, a prop, add a React component inside an existing one etc.).
We need to install 4 packages : 2 for webpack, 2 for React.
webpack-dev-middleware
– a classic expressjs middleware, where requests are passed on
– it automatically watches the sources for changes to recompile the javascript bundle server side if some javascript source have changed
– it always serves the bundle up to date
webpack-hot-middleware
– a classic expressjs middleware, where requests are passed on
– it automatically subscribes to the bundle recompilation events (such as “start”, “done”), to notify the frontend that something has changed and it need to update itself
– it uses SSE to communicate with the frontend
Specific packages for React HR
babel-plugin-react-transform
– it can add any code around React component methods during the Babel compilation ES6+JSX to ES5. You should already have configured your babel loader in webpack, such as :
react-transform-hmr
– it is used by babel-plugin-react-transform to add specific code around the React components to properly handle HR and their current state
That gives us :
$ npm install --save-dev webpack-dev-middleware
$ npm install --save-dev webpack-hot-middleware
$ npm install --save-dev babel-plugin-react-transform@beta
$ npm install --save-dev react-transform-hmr
// or for the copy/paste, all together :
$ npm i -D webpack-dev-middleware webpack-hot-middleware babel-plugin-react-transform@beta react-transform-hmr
2015-12-28: we explicitely ask for the beta (>=2.0.0) of babel-plugin-react-transform because for now, the latest published version does not work with Babel 6. But work has been done and is just waiting to be merged.
Now that we have installed the necessary packages, it’s time to configure them.
Configure Babel
We need to configure Babel (we are going to use .babelrc) to use babel-plugin-react-transform and react-transform-hmr to add the HR code around them.
Basically :
– that adds the transform babel-plugin-react-transform for the development NODE_ENV only
– this transform retrieve all the React components it can find in the source code
– it passes them down to each of its processors defined in "transforms" to let them add their custom code. (in our case, react-transform-hmr will add the HR code)
For the record, babel-plugin-react-transform handles as many "transforms" as we want. They are just going to be called right after each other. For instance :
The React component code will pass through react-transform-hmr, then through react-transform-catch-errors.
Each of them will add its code around each components.
FYI, the latter is used to catch errors that are thrown in the render() method of the React components. Then it’s using its "imports" property to redirect the error to a visual React component. Here redbox-react displays a big red screen of the death with the stacktrace for instance. But it could be anything else.
Basically, react-transform-catch-errors just adds try { render() } catch (e) { ... } around the original render() method of your components, and in the catch, it’s returning the React component you gave in "imports". Makes sense right ?
Now, our React code is ready to handle HR.
We now have to make the server communicate to the browser that the code has changed and that it needs to update.
Handle server/client communication to send/receive HR updates
Bundle recompilation on the fly
First, we need to make the server aware that the source code has changed to recompile the bundle, and then notify the browser.
That’s the role of webpack-dev-middleware and webpack-hot-middleware.
– webpack-dev-middleware will automatically start to watch the source code for changes and recompile the bundle
– webpack-hot-middleware will be notified a new bundle is compiled and will notify the browser
We just need to plug them into expressjs as middlewares to start them :
var express = require('express');
var webpack = require('webpack');
var path = require('path');
var webpackDevMiddleware = require("webpack-dev-middleware");
var webpackHotMiddleware = require("webpack-hot-middleware");
var webpackConfig = require('../webpack.config');
var app = express();
var compiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(compiler));
app.use(webpackHotMiddleware(compiler));
app.use(express.static('src'));
app.listen(3000);
But how is the browser going to handle the updates ? It’s where webpack itself rises.
Browser live update
We need to add some code client-side to deal with it, otherwise, it’s not possible. HR is not browser native or anything.
Therefore, to inject some more code, we will use the webpack bundle entry point in webpack.config.js, to add another entry.
A bundle could have several entry points.
It is just there to say to webpack : “hey, resolve the import dependency (for make the bundle) tree starting from those files!”.
The entrypoint webpack-hot-middleware/client simply refers to the file node_modules/webpack-hot-middleware/client.js.
It is the file that contains the code that will be used in the browser to handle the SSE communication with the server (to intercept the update notifications).
Then we need to add a specific webpack internal plugin HotModuleReplacementPlugin to expose the generic webpack HR API in the browser :
plugins: [
new webpack.optimize.OccurenceOrderPlugin(), // recommanded by webpack
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin() // recommanded by webpack
]
This API will be used by the code injected from webpack-hot-middleware/client when this one will retrieve some “update” events through its SSE. (specifically, it will call module.hot.apply(..) from HotModuleReplacementPlugin).
You follow ?
Nothing more to do, you’re good to go !
See it in action
Process :
– start your nodejs server
– go to your page using the bundle on your browser
– go to your code editor and change some javascript bits
– see the live update !
– if not, check your console in the browser to see some nice error messages
Behind the scene :
– the server automatically recompile the bundle if some javascript code is updated and notify the browser to update itself via SSE
– the bundle used in the browser contains some SSE code to intercept those notifications, some generic HR code to “patch” the javascript, and some custom HR code for the React components to not lose their current state
A boilerplate on github
You can checkout this boilerplate from @dan_abramov https://github.com/gaearon/react-transform-boilerplate to give a try.
It’s very simple and does exactly what we just talked about.
You will see some more options used but nothing fancy.
Its .babelrc is using react-hmre instead of our two transforms packages (babel-plugin-react-transform@beta and react-transform-hmr), but it is exactly the same actually. react-hmre simply encapsulates them.
More bits to learn
Let’s explain some more in-depth some aspects of the code used.
webpack-dev-middleware is optional, but…
Without webpack-dev-middleware, you just need to launch the webpack watch yourself :
// app.use(webpackDevMiddleware(compiler));
// replace the dev-middleware with a simple watch() (args are mandatory)
compiler.watch({}, function(){});
app.use(webpackHotMiddleware(compiler));
Because webpackHotMiddleware subscribes to the bundle compilation events (no matter what started it), it will work.
But you’ll suffer from some consequences : a bunch of .js and .json files in your project will appears each time a compilation occurs.
They contain the delta sent to the client to update itself (webpack only sends the updated chunks, not the whole bundle each time). The advantage of using webpack-dev-middleware is that you won’t see those files. They will be handled in-memory by itself. That’s why you need to install this particular package too.
babel-plugin-react-transform and react-transform-hmr
Without the code added by react-transform-hmr, webpack would not be able to hot update the React components, you would get this in the browser console :
[HMR] bundle rebuilding
[HMR] bundle rebuilt in 160ms
[HMR] Checking for updates on the server...
[HMR] The following modules couldn't be hot updated: (Full reload needed)
[HMR] - ./src/App.js
What’s inside the SSE ?
1. The browser initializes a SSE request : (thanks to the code webpack-hot-middleware/client.js injected in the bundle) on this specific url : GET localhost:3000/__webpack_hmr (it never returns). It’s handled by the server that knows it’s SSE. (by the webpack-hot-middleware expressjs middleware)
Then if a Javascript file is edited, because webpack-dev-middleware started a watch on the sources, webpack-hot-middleware is notified (because it subscribed to the recompilation events) and notify the frontend via SSE with a new module map (used by webpack), such as :
data: {"action":"building"}
// few ms later ...
data: {"action":"built","time":260,"hash":"6b625811aa23ea1ec259","warnings":[],"errors":[],"modules":{"0":"multi main","1":"./~/fbjs/lib/invariant.js","2":"./~/react/lib/Object.assign.js","3":"./~/fbjs/lib/warning.js","4":"./~/fbjs/lib/ExecutionEnvironment.js","5":"./~/react/lib/ReactMount.js","6":"./~/react/lib/ReactElement.js", ...
Then the frontend asks for those 2 files:
(I guess the hash used in the GET comes from the SSE data ? I didn’t check)
– GET localhost:3000/0.0119cbdcd4c2cf8d27c2.hot-update.js
{"h":"6b625811aa23ea1ec259","c":[0]}
– GET localhost:3000/0119cbdcd4c2cf8d27c2.hot-update.json
Those particular urls are served by webpack-dev-middleware which is keeping those files (*.hot-update.js[on]) in memory and serves them as any classic static file. They are requested by the code injected by webpack.HotModuleReplacementPlugin() that handles the responses and hot-updates the javascript with the new code.
Conclusion
Before I dug into, I was finding that the HR was a bit complicated to understand how it works, who are the actors, how does it communicate, etc. I was never sure what package was used for what exactly, and when. I hope those explanations were clear enough and that now, you know as much as me ! (or more!) Don’t hesitate to add more details or correct me if I’m wrong.
Finally, it’s not that complex but it just need a lot of “pipes” to plug everything together. But it all makes sense.
Unfortunately, there are still some caveats with the way I exposed :
– it doesn’t work for functional stateless React componentsconst App = (props) => {
Hello {props.name}
};
– babel-plugin-react-transform is simply an experiment, maybe it will die
– is working on to handle HR at the function level directly, to make it more generic
– webpack is an awesome tool
Click to email this to a friend (Opens in new window)
So, I was wondering if there was any performance downside to use React inline styles (I like them!) vs keep the old plain css stylesheets (that we love to override!). Of course there is, but I wanted some numbers!
I think both ways are useful actually, as Michael Chan said at React Europe ():
– the css can be used for the layout (such as the bootstrap layout classes: row-fluid, span*, etc.)
– the inline styles should be used for the “state-styles” (the style that can change according to a state, which is dynamic).
Why stupid
I decided to try something stupid and not truly relevant but anyway :
Generate a big table, one with inline styles, the other (in another page), with the same data, but with css, and then check the performance/size/time of both.
That’s why it’s not relevant because you would not generate a big table in your DOM. You will more likely have some kind of virtual scrolling / lazy rendering.
But, what about a test, just to see if that’s more slow when I scroll or not, and what about the rendering time ?
Chrome 45b, React 0.13.3 (debug and production)
Test case
So I generated a
of 1,000 rows with 9 columns, all of them with the same content “”, then I did the same with 10,000 rows to check the trend.
I’m going to test :
– the smoothness when I scroll down the page through Chrome DevTools Timeline
– size of the DOM : useful to know for isomorphic universal javascript (server-side rendering)
– time to mount the DOM : I’m going to use componentDidMount, this is where the DOM is available after render is called
– time to be rendered by the browser : I’m going to use requestAnimationFrame to know when the next frame after render is ready
Basically, the skeleton of the React component is :
I found no difference when scrolling, Chrome DevTools timeline supports my perception : no difference, no fps drop.
The fact that the DOM is inline or in a proper css seems to not matter, I guess it’s already in the browser memory when it’s rendered so it does not influence the smoothness.
Size of the DOM
Without surprise, the DOM size is huge with the inline styles and is linearly proportional to the number of rows.
Type
1,000 rows
10,000 rows
Ratio
CSS
517,725 chars
~5,177,250 chars
x10
Inline
2,283,742 chars
~22,837,420 chars
x10
x4.4
x4.4
That’s clearly something to take into account if you do some universal javascript. 2,000,000 chars is 2MB of data. (but who sends a big table anyway?!)
Time to mount the DOM
This is where componentDidMount is useful. We start a timer in the render method and end it when componentDidMount is called.
React has done its job converting the virtual DOM into a proper DOM and has injected it into our mounted node.
I let the times with the development version of React, because I started with and used the production version afterwards. They should not be taken into account, it’s not relevant in our case.
Type
1,000 rows
10,000 rows
Ratio
CSS (debug)
650ms-750ms
6,000ms
~x10
Inline (debug)
1,000ms-1100ms
10,000ms
~x10
CSS
310ms
2,300ms
~x7.5
Inline
600ms
4,900ms
~x8
x2
x2.1
Yes, it takes more time to stringify our virtual DOM / mount it into the DOM.
The delta decreases for less rows but it’s still significant: 70ms (css) against 105ms (inline) for 100 rows (x1.5).
Rendering time
The DOM is mounted, the browser needs to render the table now. We can assume it will take longer to render the inline styles, because it has more to parse and store every style attribute of every td.
Type
1,000 rows
10,000 rows
Ratio
CSS (debug)
1,100ms-1,200ms
9,500ms
~x10
Inline (debug)
1,500-1,600ms
14,000ms
~x10
CSS
720ms
6,000ms
~x8
Inline
1,080ms
9,400ms
~x8.7
x1.5
x1.56
Still a small overhead. I guess we could see some nice differences according to which browser is tested out!
What about another browser ? Edge !
I gave a try on Edge with 10,000 rows (React production version) :
Type
mount time
rendering time
CSS
4,115ms
11,591ms
Inline
11,418ms
21,113ms
It’s basically the double of Chrome times. :cry:
We knew that
So yes, we were kinda aware of all these behaviors, but now, we have some numbers !
This global analysis demonstrated that the inline styles take way more size and more time to be handled by the browser, but it has no more impact as soon as it’s rendered.
Compared to all the advantages the inline styles offer, we just have to be aware of those potential performance bottlenecks when thinking about the .
A l’heure où l’on parle de micro-services, où on cherche à tout modulariser, et à créer des systèmes à base de dépendances explicites, à ne plus rien mettre dans le scope global et à utiliser le scope local uniquement, histoire de maîtriser ce à quoi on a accès et d’éviter des effets de bord : on utilise encore un moteur CSS où on balance tous les sélecteurs à sa racine, qui utilise donc un scope global. Heureusement, pour ceux qui utilise le shadow DOM, ce problème est résolu. Mais quid de ceux qui ne l’utilise pas ?
Une partie de ce problème peut être évité en adoptant des normes d’écriture et de nommage telle que la norme BEM qui peuvent malgré tout ne pas suffir ou être délicate à utiliser.
Enfin, surtout avec BEM, les noms de classes peuvent être à rallonge, on aimerait bien généraliser le process de minification qu’on utilise sur le contenu des .js et des .css, sur le nom des classes elles-même ! Mais cela oblige à modifier d’une part le nom de la classe dans le fichier .css et également le code js qui l’utilise.
Il est même maintenant possible de complétement se passer de fichier .css et d’utiliser uniquement du style inline (quid des performances ?). Par exemple, avec React, le style peut être défini directement dans les composants javascript, et avec des outils tel que Radium on peut même utiliser les sélecteurs spéciaux css tel que :hover, ou les média queries.
Facebook et le CSS
Facebook a déjà résolu ce problème. Sans doute avez-vous vu la présentation de où il évoque comment Facebook s’assure qu’il n’y a aucun conflit de nom de classe css, que le scope global n’est pas pollué, et que les développeurs peuvent facilement rajouter du style sans avoir peur d’avoir des effets de bord et de modifier le layout quelque part, sans le savoir.
Ils ont étendu le langage css en rajoutant une syntaxe spéciale pour les sélecteurs : button/container qui ne peut être utilisé que dans le fichier button.css qui a son tour est référence dans un composant React button.js, qui enfin, fait référence à className={cx('button/container')} pour définir la classe d’un élément.
Le process de build vérifie ces références et génére un nom de classe unique à partir de button/container (qui n’est pas valide en CSS) par quelque-chose comme ._f8z qui fera parti du scope css global mais qui n’entrera jamais en conflit avec quoi que ce soit vu que le nom est généré aléatoirement (et est unique), personne ne pouvant le deviner à l’avance. Tout le monde doit donc utiliser ce système pour travailler et styler son contenu.
Moi aussi j’aimerai faire ça. Ca tombe bien, webpack est là.
webpack et css-loader
webpack, et en particulier le plugin css-loader combiné à extract-text-webpack-plugin, permet de former un (ou des) bundle css à partir de fichiers .css.less ou .sass (avec les loaders qui vont bien) qui sont eux-même importés dans des fichiers .js :
import React from 'react';
import './App.less';
export default class extends React.Component {
render() {
return
}
}
Ce build permet de créer un bundle css qui contiendra tout le contenu référencé par les imports de fichiers .css ou .less dans les fichiers javascript.
import './App.less';
css-loader local scope
La nouveauté (d’avril 2015) est une nouvelle syntaxe au niveau des fichiers css pas encore transformés, mais prise en compte par css-loader :
:local(.container) {
font-size: 30px;
}
Se transforme en :
._3ImWIJ65ktg-PxiyA_aFIC {
font-size: 30px;
}
Cela signifie que vous ne pouvez plus utiliser simplement className="container", cette classe css n’existe plus. Et vous ne pouvez pas mettre className="_3ImWIJ65ktg-PxiyA_aFIC" quand même ! Il faut importer différemment le fichier .css ou .less :
import AppStyles from './App.less';
...
Les classes définies dans le fichier css seront accessibles via leur nom (si :local(.my-class) alors via my-class) dans l’objet importé, qu’on peut alors utiliser pour indiquer la className. Au niveau HTML, on aura ce rendu :
Autre exemple avec un autre sélecteur css à l’intérieur :
Attention à bien utiliser className={ AppStyle.container } et pas style={ AppStyle.container } sans quoi l’erreur suivante se produirait :
Uncaught Error: Invariant Violation: The `style` prop expects a mapping from style properties to values, not a string. For example, style={{marginRight: spacing + 'em'}} when using JSX.
Le nom des classes générées
On peut modifier la manière dont le nom des classes sont générés. Par défaut, il s’agit d’un hash comme on peut voir, mais on peut le modifier de la sorte (via webpack.config.js) :
local: le nom utilisé dans le fichier css :local(.container)
Il n’y a malheureusement pas de méthodes (pour l’instant) pour générer des noms du genre ._a, ._b, … ._az histoire de rester ultra court et unique (étant un simple compteur).
La gestion de cet identifiant provient du module webpack loader-utils.
Plusieurs imports
Si un fichier Javascript dépend de plusieurs fichiers de styles et qu’un, il faut simplement concaténer le nom des classes provenant des 2 imports :
import AppStyle from './App.less';
import GlobalStyle from './Global.less';
...
className={GlobalStyle.container + ' ' + AppStyle.container}
Plus loin
D’autres fonctionnalités sont disponibles, comme l’héritage de règles ou la définition de règle globale via :global, je vous conseille donc d’aller lire la page du projet : https://github.com/webpack/css-loader
Tout cela est encore récent et en développement (allant même à provoquer des breaking changes, des changements de syntaxe), ce qui la rend instable à utiliser pour l’instant, attention!
Click to email this to a friend (Opens in new window)
Jank is when missed frame appears when navigating a website, you don’t want that. http://jankfree.org/
You can find a lot of talks and articles from Paul Lewis, Tom Wiltzius and Nat Duca, who are great speakers btw, that explain clearly how to avoid jank, how to diagnose it, and how to fix it. Check the resources at the end of this post to have some pointers.
I decided to resume quickly everything I learned from a lot of different sources in order to not do those mistakes again and help you and me to understand what can cause this jank. I don’t explain in details everything, every term I use, check out the resources of the post to have a better understanding if needed.
I’ll start with general javascript concepts and then I’ll continue with deeper concepts such as the layout/painting/layers processes the browser do and how to optimize them, then I’ll finish with some network advices.
Some of the items are maybe not relevant anymore nowadays for certain browsers because it was optimized since. I tried to filter them but still.
Feel free to correct me if I’m wrong.
Object instances
If you have a bunch of object to create, forget about module pattern, prototypes are faster to create, or create plain simple object. http://jsperf.com/prototypal-performance/55
Holey array
Don’t do holey array (it can be converted to a dictionary internally thus being way slower). Try to not use the delete keyword. It can change on the fly the internal class being used for the object you just deleted from (for instance, an array with a delete in the middle can transform it into a map). http://jsperf.com/packed-vs-holey-arrays
Objects pooling
If you are creating and destructing a lot of objects/references, consider using an object pool to avoid triggering the Garbage Collector (which would make you lose some precious ms at random times). Check http://www.html5rocks.com/en/tutorials/speed/static-mem-pools/ for more details.
Image resizing
Resize your image exactly as the size you are using in your page (we are not talking about multi-support here) to avoid the browser to resize the image itself (cpu consuming). Consider using srcset on or use if you want to handle high DPI devices.
Background-size
Beware of background-size, keep the image at the original size instead of asking the browser to resize on the fly. Create multiple version of the same image if needed. You can also create a spritesheet image of your images to do only one HTTP request (and maybe have a faster memory access but don’t take this into consideration, that’s way too insignificant to be considered).
CSS gradients
Beware of repeating gradients. That slows down a lot the browser rendering. For instance, if you put it on the background of your page in which you can scroll. Prefer to use a tiled background image.
Custom fonts instead of image
Don’t forget that you can use nice custom fonts instead of images to draw symbolish things. Such as FontAwesome. You will save bandwidth and designer time. http://fortawesome.github.io/Font-Awesome/icons/
Or you can use unicode characters (it has a lot of icons, not only characters) or svg (light and scalable).
Image lazy loading
Consider using an image lazy loading plugin to load images only if the user can see them of if everything else has been loaded first. If by default the image is not displayed in the first view at loading time, you can defer its request when the user is scrolling into for instance.
If you have a carousel of images, consider to not load every images of the carousel at the beginning. Either wait the user to hover it or load them after everything else is loaded. If the user does not even use the carousel, it’s useless to load the other images.
Event listeners
Try to not bind event listeners on the whole document just to handle the event on a deep child. Try to bind listeners on the element you need or a nearest parent. Otherwise the compositor (a thread dedicated to handle inputs and scrolling) will be triggered for nothing each time the event will happen on the page, that could have no link with your element.
Memory management / variable scoping
Try to not use global variables. Release what you can when you can. With ES6, use only let, no more var. Remove the event listeners you don’t need. Be especially careful with closures, they can contain a lot of references that will never be released if you are careless.
Events throttling
Throttle the events that can be called numerous times : onscroll, onmousemove, onresize, ondragover. They will be called only a few times and that will be enough.
requestAnimationFrame() is your best friend
Do not use setTimeout() or setInterval() to throttle event handlers such as onscroll or onresize. Use requestAnimationFrame() to only change the visual state when a new frame is going to be rendered, to not compute things that won’t be render in-between 2 frames. Check http://www.html5rocks.com/en/tutorials/speed/animations/ for more details.
Event handlers logic
Don’t do any complex logic in the event handlers. Just save somewhere the values which you care of the current state (scrollTop, mouse position…), and let the browser pursues its events pipeline. If you are doing something heavy in a handlers, you will delay every other events to be handle and will delay the rendering (then you call a requestAnimationFrame(fn) to use those saved values). Try to bind global events only once, such as onscroll or mousemove. Just save their positions in those handlers and when you need them, just call functions in requestAnimationFrame() using those saved values.
:hover and scrolling
Be careful with :hover effect on elements in the middle of a scrollable page. If the user scrolls using its mousewheel on top of those elements, they will trigger the :hover at the same time the scrolling is occurring. That can slow down dramatically the fps and you end with jank (missed frames during scrolling becausee of a lot of restyle/paint/composite layers operations). You can for instance add a class on the container when it’s scrolling to not trigger the :hover effect while it’s present.
Dynamic class adds
Don’t add classes on top of your document if that only change an item deep in the DOM (that could cause a full repainting from the parent). Try to target specifically the element or a near parent.
Avoid Layout Trashing
When you need to read some positions or sizes from the DOM (.offsetTop, .offsetWidth …), don’t alternate them with writes of positions or sizes on the DOM (element.style.width = width). That would cause what is called “Layout Trashing” because each time you change a size or position on the DOM, that triggers a forced synchronous layout (painting). Do all your readings at the beginning, store them into variables, then do your writings. You can call a requestAnimationFrame() to read those values, then inside, call another requestAnimationFrame() to write your transformations.
To know if have to kind of cycles, check your timeline in the debugger and look for cycles of :
– recalculate style
– layout
– recalculate style
– layout
You can know what property triggers what on http://csstriggers.com/ (only Chrome for now)
transformZ(0) aka the null transform hack
There is a trick called the “null transform hack” to force the browser to create a distinct layer containing a DOM element that will be painted using the hardware acceleration (and independently of the other layers) using transform: translateZ(0) or transform: translate3d(0,0,0). Those statements changes nothing visually but on a desktop computer, that can really boost your fps. If you combine that on elements that have a shadow (like, a lot on your page), you clearly see the difference when playing with. But be careful, mobiles do not have the same capability than desktops, and the performances could be worse.
Will-change
Instead of this hack, consider implementing the fairly new css property will-change: [transform|scroll-position|contents| to hint the browser to create a distinct composite layer for this element. You just notify it that the particular property will probably change for this element. The browser will try to optimize what it can (ie: create a new layer for it). But don’t abuse it to not create another problem (it’s useless to do * { will-change: * } the browser is already trying hard).
Remove this property when you are done with the change. Do not apply it on a global rule, just apply it when just before it’s needed. For instance, listen to the :hover state of its container to set it on the child using css : no hover on parent = no will-change on child. Or set it using javascript and reset it to auto when you’re done.
Composite layer
What creates a new composite layer is the when you use translationZ/3d, and (they are automatically embedded in a new layer), animations with opacity and transform, and some css filters that use hardware acceleration. It has nothing to do with z-index layers. Check http://www.html5rocks.com/en/tutorials/speed/layers/ for more details.
Animation/Transitions
Do not animate with javascript. Use css animations/transitions. The animations will be processed by another thread than the main one. Even if your js does complex calculations and slows down the main thread, the css animation will still be smooth.
Transform to animate position
When you have element which position is dynamic, do not use css top/left, use transform: translate. top/left should be reserved for initial positioning only, just to organize the layout. css transform is especially done for transformations, it does not reflow/redraw whereas top/left does. In DevTools, enable Show Paint Rectangle (Timeline > Rendering) to see what is repainting on your page.
Moreover, the translate transform animation is using subpixel rendering instead of pixel per pixel for top/left animation. The rendering is smoother.
FLIP
It’s a technique used to animate an element the cheapest way possible when the positions/dimensions are unknown/dynamic. Paul Lewis explains it:
Basically, it stands for: First, Last, Invert, Play. From the initial state, you set directly its last state, read some values from it (position, size), apply an inverted transform style to put it back where it was, and clear the transform style to trigger the animation.
Fixed elements
If you have a top bar in fixed position, consider to put it in its own layer. Otherwise, scrolling the content underneath cause the element to be repainted each time. You can check with the Show Paint Rectangle toggle.
Chrome DevTools <3
In Chrome DevTools, you can press h on an element to toggle its visibility. That avoids you to add a display: none manually on its style. Life saver. Of course, their performance tools is uberly useful and you can use about:tracing if you are desperate (and you will probably be more desperate with that ;-).
To check if you have some weird or expansive painting behavior, enable the continuous painting mode and toggle elements visibility to see which one(s) is(are) causing the problem : Timeline > Rendering (press to toggle the panel visibility if not there) > Show paint rectangles + Enable continuous page repainting. You can enable the fps meter too. Now, scroll into your page, do some actions, hide some elements to see if you can find the culprit (if you notice a boost).
Multiple adds to the DOM / DocumentFragment
When you want to append several items to the DOM, use document.createFragment(). It’s available in all browsers since a while (jQuery internally use it when you call append() with an array for instance). You can use traditional DOM operations on it (appendChild, style etc.) without the browser to render it while you create it. Then when you are done, you just append it to the real DOM in one shot.
Avoid to read value from CSS
Try to not use getComputedStyle() (or .css() getter from jquery, it’s using that internally) to read value from CSS directly, that’s calling the “Recalculate Style” process. Save the values you need before somewhere if you can and use them, or if it’s for animate something, use css transition/animation.
getBoundingClientRect()
This magic function returns in one call the position and the dimension of the item it’s called on : height, width, top, left, bottom, right. Use it once when you need to read those differents values.
User reaction
When a user clicks on a button, you have about 100ms before doing any animation. The user won’t notice anything before, it’s its reaction time. Therefore, you can prepare anything you need (get sizes, positions…) and have an ugly 100ms frame if you need, he won’t notice. ;-)
If you want to deal with mobiles, add in your . That could enable the GPU rasterization on the phone (Androids). But basically, add that even if it’s not an Android, that won’t do harm on the contrary.
DOM element count
Try to not have too many DOM nodes on your page. For instance, 1000 for a mobile app is a good number. (desktop version : 4000 twitter.com, 2500 for amazon or facebook, mobile version: 1000 for m.twitter.com, 1700 for m.facebook)
CSS stylesheets
CSS are blocking resources for the browser. It waits for them to be loaded and parsed before rendering anything to avoid a flickering effect. So, keep it light, avoid to load unnecessary css rules. You can split your stylesheet by media for instance. The browser won’t wait for them if their rule does not apply :
Put your CSS in the for the browser to request them as soon as possible.
Avoid to use @import in stylesheets to not block again the browser (because it will request again a stylesheet).
Async JS scripts
Inline or blocks the DOM construction when the browser encounters them, which will therefore delay the initial render. Generally, you put your at the end of the body to render it directly, and because the scripts often references DOM parts (in nothing is constructed yet).
You can add the async flag on the tag to defer its loading. The browser won’t block anything and will pursue its process.
Page load time and runtime performance
You have to be careful to these 2 metrics. The first one is the time to get the initial rendering of your website. The other is the performance when the user is using it.
At loading, ensure to not have too many redirects, each one of them add noticeable delay : xxx.com => m.xxx.com => m.xxx.com/start
Also consider about gzipping the content your server sends. You can easily reduce by 80%-90% the size of your js/css (because they are text based, gzip does miracles).
Browser loading events
You need to understand the events the browser expose when it’s accessing your website. The most known is DOMContentLoaded, this is the one that is called when every resources has been requested and parsed (js/css) but not processed yet. Then ‘loaded’ is when every resources has been processed (even images). Those events need to be as short as possible.
To have an explanation on each of those events, check out
Your browser extensions have a great impact on that! If you have AdBlock for instance, check with and without it (eg: use the private mode without any extension loaded).
Or, you can check the network tab in devtools to have those numbers directly.
Cache your resources
Use the browser cache system to avoid transmitting each time your static resources : use ETag (browser will then use If-None-Match) and Cache-Control flags (for the browser to cache it locally for instance) . Explanations on
Don’t delay the navigation
Don’t delay the unload of your page, when navigating to another page for instance (not just when the user closes the tab). Instead of sending a classic ajax and add some trick to wait (as a empty for loop !) use navigator.sendBeacon, it exists for that.
Profiling tools
Use the developer tool, it’s very, very handy.
chrome://tracing to see what’s under the hood when you are desperate
to have a first review of your website performance and hints to improve it
http://www.webpagetest.org/ to test your website using different configuration/location : try to have a speed index around 1000 for a mobile version.
Even if it’s not fully standardized (last draft) and should get out mid-2015, ES6 is already there. We can already find several transpilers (a compiler that translates a language to another one) that translate ES6 to ES5 for our browsers or Node to run normally with the old known syntax.
Soon, it will be even useless to transpile with Node because ES6 is already on its way. For instance, Node has already implemented those features :
If you are not aware of all the new features, or just want to have a look again, check this page that resume clearly with examples what is available in ES6.
Babel FTW
But the best transpiler, the one that implements most of the features is Babel (formerly es5to6, they changed their name because they planned to go further than ES6 certainly, ES7 already being on its way !).
On the 25/02/2015, Babel is already 78% compatible with the ES6 syntax. That’s pretty neat. Jsx being far at 16% ({harmony: true}), Node at 25% (--harmony).
Let’s not forget about the client side, Chrome being around 50% and Firefox 65%.
Before talking about React with ES6 in Node, I’m going to quickly present the most important and confortable features I think to not revamp our code later (with React at least). You can skip this part if you already know what features ES6 provide.
object literals
var name = "henry";
var phone = "+333456789"
var person = { name, phone };
Just sugar to avoid retyping the variable name and to be consistent.
templates
var what = "awesome";
console.log(`it's so ${what}`);
You just use backquotes and you have a basic template engine where you can evaluate any javascript inside.
No more unreadable “my name is ” + name + ” and I’m ” + yearOld + ” year” + (yearOld > 1 ? “s” : “”) + ” old”.
arrow functions
arr.map(item => item.value)
this being automatically passed to the function, no more need of ugly var self = this;
class TopBarComponent extends React.Component {
constructor() {
super(props); // can be useful sometimes!
this.state = { value: props.initialValue };
}
...
Finally, a class keyword with its extends. No more prototype to play with and other module pattern, no more React.createClass, no more getInitialState(), you have a true constructor, let’s make some nice OO. No multiple inheritance through. Notice that the React props are now available in the constructor arguments.
short functions notations in classes
render() { return
No function keyword
; }
And no comma between the functions or that’s not valid.
destructuring
(function({ name, age }) { // auto-associate the args
console.log(`hi ${name} ${age}`);
})({ age: 12, name: 'henry', phone: '+333456789' });
// auto-associate the function result into the left member
var { name } = (() => { return { name: "toto", age: 30 } })();
console.log(name);
This is super useful and avoid to create manually bunch of variables.
module
import React from 'react';
basically that replaces var React = require('react');
you could also import several items at the same time using the destructuring :
import { React, cx } from 'react';
But it doesn’t work for now in Babel at least.
Those were the most useful features to know, and to run some React in Node.
Check out the others features, such as the promises, the generators, the spread parameters, the default arguments, let, const, symbol, iterators, and the new data structures (Map Set WeakMap, WeakSet), new functions on String, Math, Number…! So much to talk about. You can check out the Babel website to know what it’s all about.
React ES6 style
Let’s focus on React server-side now, using Node. We are going to use Babel to have the latest available features, and because it’s pretty straightforward to make it work.
– Install Babel globally, to have access to the babel executables npm install -g babel.
– Install react. You must have at least react>=0.13 otherwise Babel is going to fail with this error :
TypeError: Super expression must either be null or a function, not undefined
If the 0.13 is not yet available (it’s not yet at this time), force the installation using : npm install
Babel has an executable babel-node, that starts node with all its es6 features: babel-node app.jsx
Here is a program sample full ES6 style that just renders as a string (we are server side!) a component in another file:
App.jsx
import React from 'react';
import MyComponent from './MyComponent.jsx';
console.log(React.renderToString());
We are already using the class, module, destructuring and arrow features in this tiny example.
So, this is the base template if you want to start playing with React in Node being full ES6 compliant. It took some time for me to find this result. I tested a lot of things, other frameworks and techniques to get my full ES6 working, and I found this one with Babel simple enough. So, it’s time to refactor our stuff now and combine this with some client side !
Instead of using babel-node, you can use the executable babel to get the transpiled output. It’s pretty interesting to see how Babel translates ES6 to ES5.
For instance, our previous sample, if you do babel app.jsx, that gives us :
"use strict";
var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; };
var React = _interopRequire(require("react"));
var MyComponent = _interopRequire(require("./MyComponent.jsx"));
console.log(React.renderToString(React.createElement(MyComponent, { initialValue: "3" })));
The import feature is automatically replaced by an _interopRequire + require function. If we babel MyComponent.jsx, that’s WAY more verbose :
"use strict";
var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; };
var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); };
var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
var React = _interopRequire(require("react"));
var MyComponent = (function (_React$Component) {
function MyComponent(props) {
_classCallCheck(this, MyComponent);
this.state = { value: props.initialValue };
}
_inherits(MyComponent, _React$Component);
_prototypeProperties(MyComponent, null, {
render: {
value: function render() {
var _this = this;
var items = [1,, 3].map(function (x) {
return React.createElement(
"span",
{ key: x },
x * _this.state.value
);
});
return React.createElement(
"div",
null,
items
);
},
writable: true,
configurable: true
}
});
return MyComponent;
})(React.Component);
module.exports = MyComponent;
We can see a bit how it’s translated :
– Class, superclass, and inheritance.
– Everything being a true ES5 object property value/writable/configurable using Object.defineProperties
– module.exports = MyComponent
You may have noticed the default keyword when we defined our React component: export default class MyComponent {
If we omit it, the end of the generated file will be :
But we want this CommonJS syntax to work with Node, so we need this default keyword. If you don’t use it, you will get this error :
Warning: React.createElement: type should not be null or undefined. It should be a string (for DOM elements) or a ReactClass (for composite components).
Warning: Only functions or strings can be mounted as React components.
C:\test\node_modules\react\lib\ReactDefaultInjection.js:53
tagName: type.toUpperCase(),
^
TypeError: Cannot read property 'toUpperCase' of undefined
You can use babel-node with the experimental flag : babel-node --experimental app.jsx to give a try to the already implemented ES7 features of Babel if you want.
Click to email this to a friend (Opens in new window)
If you’re not familiar enough with browserify options and features, check out my previous post explaining browserify in-depth.
I love streaming
Gulp is a stream-based project builder. Basically: a bunch of modules that take one input and output something, which is taken by another module etc. until the whole process is done.
First thing to do, create our gulpfile.js at the root of our project and require() what we need: gulp and browserify. Once, there was a gulp-browserify plugin. It’s still downloadable but it is not maintained anymore so don’t use it. You have to use browserify itself, that’s it.
var browserify = require('browserify');
var gulp = require('gulp');
gulp.task('default', function() {
console.log('todo');
});
> gulp
[14:16:44] Starting 'default'...
todo
[14:16:44] Finished 'default' after 85 µs
Before throwing lines of code and browserify, let’s explain a bit more how this stream management work with some examples.