Skip to content

Commit 03ac33f

Browse files
committed
Add requirejs
1 parent 21f7f17 commit 03ac33f

File tree

1 file changed

+357
-0
lines changed

1 file changed

+357
-0
lines changed
Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
---
2+
layout: post
3+
title: Organiser son code JavaScript avec RequireJS
4+
author: filirom1
5+
tags: [requirejs javascript webapp]
6+
---
7+
8+
Je suis certain que vous avez connu la frénésie jQuery. Le JavaScript prend de plus en plus de place au sein de l'application. On commence à avoir des difficultés à gérer l'ordre des balises scripts, la navigateur télécharge un nombre incroyable de fichiers js et le navigateur ramme, on perd le compte du nombre de variables globales, on trouve du code javascript dans l'html et du html dans du javascript, le code dupliqué se multiplie et très vite, on ne sait plus qui fait quoi et rien n'est testé...
9+
10+
Alors comment s'en sortir ?
11+
Personnellement à m'en sortir avec [RequireJS](http://requirejs.org/) et cette méthode que je vais vous expliquer.
12+
13+
14+
Il existe également d'autre méthodes qui couvrent un spectre plus ou moins large des problème exposés ci dessus. Je vais commencer par vous les expliquer avant de commencer RequireJS.
15+
16+
17+
Module Pattern
18+
--------------
19+
<http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth>
20+
Il nous permet de ne pas pourrir notre scope gloable. En Javascript un scope est défini à l'aide de fonction. Pour éviter que nos variables arrivent dans le scope globale il suffit de wrapper notre code autours d'une fonction et de l'executer tout de suite après.
21+
22+
(function(){
23+
var private = 'I am private, the global scope is free of me !';
24+
25+
function iAmPrivateToo(){
26+
return 'This function is private and will not be avalaible via the global scope !'
27+
}
28+
})()
29+
30+
31+
Object Litteral Pattern
32+
-----------------------
33+
<http://stackoverflow.com/questions/1600130/javascript-advantages-of-object-literal>
34+
Maintenant que nous savons isolé notre code, nous allons apprendre comment le découper pour éviter de se retrouver avec un seul gros fichier.
35+
36+
A partir du moment où l'on découpe notre code, nous allons avoir besoin d'exporter des fonctions et des attributs publiquement dans une variable globale (ça va une seule, on ne crie pas au scandale tout de suite).
37+
38+
// script1.js
39+
(function(){
40+
var myApp = window.myApp = window.myApp || {};
41+
42+
var aPublicVariable = 'I am public, yeahhh';
43+
44+
function aPublicFunction(){
45+
return 'This function will be public';
46+
}
47+
48+
var aPrivateVariable = 'I am private, Yeahh !!!';
49+
50+
function aPrivateFunction(){
51+
return 'I will never export this function';
52+
}
53+
54+
// exports public functions and variables
55+
myApp.aPublicVariable = aPublicVariable;
56+
myApp.aPublicFunction = aPublicFunction;
57+
})();
58+
59+
// script2.js use script1 functions
60+
(function(){
61+
var myApp = window.myApp = window.myApp || {};
62+
63+
console.log(myApp.aPublicFunction());
64+
})();
65+
66+
Attention script2 dépend de script1. Il y a un ordre à respecter au niveau de la déclaration des dépendances.
67+
68+
<script src="script1.js"></script>
69+
<script src="script2.js"></script>
70+
71+
Grouper les fichiers js pour la prod
72+
------------------------------------
73+
74+
On se retrouve avec deux fichiers javascripts, pour le développement c'est mieux mais pour la prod c'est moins bien ! On a augmenté le nombre de ressource à faire télécharger par le navigateur. Maintenant, il faut grouper et minifier ses ressources js avant de les servir en prod. Pour cela nous pouvons utiliser un simple script shell:
75+
76+
cat js/src/* >> js/build/app.js
77+
78+
Mais attention, les dépendances risquent d'être cassé. Pour cela nous pouvons utiliser des outils plus évolués : <http://code.google.com/p/wro4j/> qui possède même un plugin Tapestry <https://github.com/lltyk/tapestry-wro4j> ;)
79+
80+
Ou sinon utiliser le script de build de [html5 boilerplate](https://github.com/h5bp/html5-boilerplate/wiki/Build-script)
81+
82+
Si vous avez d'autres outils, laissez un commentaire à cet article.
83+
84+
85+
Gestion des dépendances avec RequireJS
86+
--------------------------------------
87+
88+
C'est à ce moment que je commence à vous parler de RequireJS.
89+
90+
RequireJs va vous permettre de faire :
91+
92+
* de l'encapsulation (visibilité public / privée )
93+
* de la modularité
94+
* de la gestion de dépendances
95+
* de ne plus écrire d'HTML dans des fichiers JS
96+
* de l'optimisation d'assests (grouper et minifier les js et les css)
97+
98+
RequireJS est actuellement la solution mise en avant par :
99+
100+
* [Rebecca Murphey pour utiliser RequireJS avec jQuery](http://jqfundamentals.com/)
101+
* [Addy Osmani pour utiliser RequireJS avec Backbone](https://github.com/addyosmani/backbone-fundamentals)
102+
* [Alex Sexton le père de YepNope, qui explique pourquoi il préfère RequireJS](http://www.quora.com/What-are-the-use-cases-for-RequireJS-vs-Yepnope-vs-LABjs)
103+
104+
Voici un exemple de code de RequireJS, suivez les commentaires pour les explications.
105+
106+
/* js/script1.js */
107+
define(
108+
id, /* (optionnel) */
109+
[ 'jquery', 'underscore' ], /* La liste des dépendances */
110+
function($, _) {
111+
/* on retrouve le module pattern ici */
112+
var privateVariable = 'I am private';
113+
function privateFunction(){ ... }
114+
115+
var publicVariable = 'I am public';
116+
function publicFunction(){ ... }
117+
118+
/* Variables et functions à exporter */
119+
return {
120+
publicVariable: publicVariable,
121+
publicFunction: publicFunction
122+
}
123+
}
124+
)
125+
126+
/* bootstrap.js qui utilise la dépendance defini ci dessus. */
127+
require({
128+
paths: {
129+
jquery: 'js/libs/jquery-1.7.1.min',
130+
underscore: 'js/libs/underscore-min'
131+
}
132+
},['js/script1'], function(script1){
133+
console.log(script.publicFunction());
134+
})
135+
136+
Reprenons l'exemple précédent tout doucement.
137+
138+
RequireJs définie deux notions : `define`, `require`.
139+
140+
* `define` permet de définir un module avec des dépendances et des methodes/variables à exporter.
141+
* `require` permet de charger dynamiquement des modules.
142+
143+
### Definir un module avec `define`
144+
145+
Si nous prenons l'exemple d'un module simple.
146+
147+
define([
148+
'jquery',
149+
'underscore',
150+
'js/myScript',
151+
...
152+
], function($, _, myScript, ...){
153+
154+
/* your private methods and variable here */
155+
156+
return { /* your public variables and functions here */ }
157+
});
158+
159+
Je n'ai pas spécifier l'id, RequireJs va s'en occuper pour moi et je vous conseil de faire de même. Votre code sera réutilisable plus facilement.
160+
161+
Dans mon module, j'ai spécifié une liste de dépendences.
162+
163+
[
164+
'jquery',
165+
'underscore',
166+
'js/myScript',
167+
...
168+
]
169+
170+
Nous remarquons que pour myScript, le nom de la dépendance correspond à son chemin sans l'extension '.js'. `js/myScript` va charger `http://myApp.com/js/myScript.js`. Nous verrons par la suite comment configurer RequireJS afin de donner des chemins particulier (comme pour jQuery et underscore).
171+
172+
173+
RequireJs m'assure que les dépendances seront chargées et accessibles dans le scope de la fonction : `function($, _, myScript, ...){ ... }`
174+
175+
Mon module peut exporter des choses: `return { publicFunction: publicFunction, .... }` Tout ce qui n'est pas exporté est privé. Simple n'est-ce pas ;)
176+
177+
Et comme vous l'aurez supposé, tout ce qui est exporté par un module, est disponible par les autres modules via les attributs de la fonction : `$, _, myScript, ...`
178+
179+
RequireJs va s'occuper de charger les dépendances des dépendances, ... et au final je n'aurai plus qu'à définir qu'un seul module : myApplication et de ne charger que ce module pour que toutes les dependances de mon application soit chargées automatiquement (ce n'est plus à nous de gérer l'ordre c'est cool ;) ).
180+
181+
182+
### Charger des modules avec `require`
183+
184+
Maintenant c'est sympa nous avons défini des modules, mais il nous manque un point d'entrée à notre application. C'est le rôle de `require` de faire ça. `require` va charger dynamiquement un module, puis ses dépendances, puis appeler la fonction passée en dernier paramètre.
185+
186+
require({
187+
paths: {
188+
jquery: 'js/libs/jquery-1.7.1.min',
189+
underscore: 'js/libs/underscore-min'
190+
}
191+
},['jquery', 'js/script1'], function($, script1){
192+
$(function(){
193+
$('#myID').text('Loaded ' + script1.publicFunction());
194+
});
195+
})
196+
197+
198+
### Configurer RequireJS
199+
200+
Comme vous l'avez remarqué le premier paramètre de requireJS est un objet de configuration :
201+
202+
paths: {
203+
jquery: 'js/libs/jquery-1.7.1.min',
204+
underscore: 'js/libs/underscore-min'
205+
}
206+
207+
Le paramètre `paths` nous permet de definir l'emplacement des librairies utilisés
208+
209+
Tout à l'heure je vous est dit, n'utiliser pas d'ID dans vos modules et vous allez maintenant comprendre pourquoi.
210+
211+
jQuery définie dans son [code ](https://github.com/jquery/jquery/blob/cae1b6174917df3b4db661f20ef4745dd6e7f305/src/core.js#L949) un `define('jquery', function(){ return jQuery });` Le premier paramètre `jquery` est l'id et nous oblige à utiliser `jquery` dans nos dépendances.
212+
213+
Normalement nous aurions pu faire appel à jquery via son chemin complet :
214+
`define(['js/libs/jquery-1.7.1.min'], function($){ ... })`, mais à cause de l'ID nous devons faire réference à jquery par son id :`define(['jquery'], function($){ ... })`
215+
216+
C'est le rôle de la configuration `paths: { jquery: 'js/libs/jquery-1.7.1.min', }`. RequireJS va remplacer chaque dépendance `jquery` par son chemin réel afin de trouver la librairie.
217+
218+
219+
### Intégrer RequireJS dans notre HTML
220+
221+
Il faut rajouter dansle HTML les balises script pour require.js et notre bootstrap.js (celui faisant appel à toute nos dépendances).
222+
223+
<html>
224+
<head>...</head>
225+
<body>
226+
<!-- Ajoutons requireJS et notre script à la fin du body -->
227+
<script src="require.js"></script>
228+
<script src="bootstrap.js"></script>
229+
</body>
230+
</html>
231+
232+
RequireJs rajoutera les dépendences chagé via des balise script dans le head du HTML et le navigateur les chargera automatiquement.
233+
234+
Maintenant lorsque nous naviguons sur notre site, le navigateur télécharge require.js, le bootstrap, et toute les dependances.
235+
236+
237+
### Optimiser les ressources
238+
239+
Avec RequireJS nous avons découpé notre application en plusieurs fichiers, malheuresement tous les fichiers sont téléchargés un par un par le navigateur.
240+
241+
Nous aurions besoin de grouper et minifier l'ensemble de ces fichiers avant de les servir en production.
242+
243+
RequireJS arrive avec un [script de build](https://github.com/jrburke/r.js/) qui a exactement ce rôle là.
244+
245+
Si on appelle r.js avec le paramètre -o et en lui spécifiant un fichier de configuration : `r.js -o build.js`
246+
247+
r.js va parcourir l'ensemble des scripts et les minifiers, puis va parcourir nos dépendance afin de les grouper. Les fichiers résultant seront placer dans un nouveau repertoire afin de ne pas écraser les sources.
248+
249+
({
250+
appDir: './src', /* Repertoire des sources */
251+
baseUrl: ".", /* Le repertoire racine */
252+
dir: "./build", /* Repertoire de destination */
253+
modules: [ {
254+
name: 'bootstrap' /* Une dependance vers le fichier appelant le require */
255+
} ],
256+
optimizeCss: "standard.keepLines", /* On va même optimiser les CSS */
257+
dirExclusionRegExp: /node_modules|test|build/ /* on va exclure du processus de build certain repertoires. */
258+
})
259+
260+
Le fichier bootstrap.js du réperoire build contiendra l'ensemble de notre code js minifié.
261+
262+
263+
264+
### Chargement dynamique de code
265+
266+
Il se peut que lors de la consrtuction d'une Single Page Web App vous ayez besoin de definir des ensembles de modules. Par exemple un module qui contient l'ensemble du code JS pour le frontend de votre appli et un autre module qui va contenir le codeJS supplémentaire nécessaire pour charger le backend.
267+
268+
Vous ne souhaitez pas que les internautes qui accèdent à votre frontend soit ralenti par le télechargement des ressources du backend.
269+
270+
C'est pour répondre à cette problématique que le script de build r.js peut être configuré avec plusieurs modules.
271+
272+
### RequireJS sur mobile
273+
274+
Sur mobile on a toujours des contraintes des tailles. Nous ne voulons surtout pas charger de libraries inutilement. Du fait que nous avons utiliser la structuration AMD (Asynchrnous Module Definition) dans notre code, nous sommes obligé d'utiliser une implementation AMD pour lancer notre application et requireJs n'est pas la seule implémentation existante.
275+
276+
Actuellement il en existe plusieurs dont 3 que j'ai testé personnelement.
277+
278+
* Almond la plus petite (mois de 1Ko gzippé) ne fait pas de chargement dynamique. Elle fonctionne très bien en complément du script builder r.js. Comme tout le code est groupé en un seul fichier, Almond peut faire son travail. Almond est la solution privilégié sur mobile.
279+
* Curl.js qui est deux fois moins gros que RequireJs mais qui fait également moins de choses. Mais de toute façon RequireJS fait beaucoup beaucoup de chose dont vous n'aurez pas forcement besoin et curl.js peut donc être une bonne alternative si vous avez besoin de chargement dynamique de code.
280+
281+
Par contre, sur des projets plus gros, souvent RequireJS est le seul à s'en sortir.
282+
283+
Et voici une comparisons des implémentations en terme de taille
284+
285+
$ ls -l
286+
-rw-r--r-- 1 romain users 925 29 nov. 15:41 almond.min.js.gz
287+
-rw-r--r-- 1 romain users 2,6K 29 nov. 15:41 curl.js.gz
288+
-rw-r--r-- 1 romain users 5,5K 29 nov. 15:41 require.js.gz
289+
290+
291+
Si vous utilisez almond qui ne fait pas de dynamique loading, vous aurez besoin en prod de remplacer vos dependances requireJS + votre code par un seul fichier minifier contenant tout.
292+
293+
- <script src="require.js"></script>
294+
- <script src="boostrap.js"></script>
295+
+ <script src="almond-and-bootstrap.js"></script>
296+
297+
Pour cela vous pouvez utiliser :
298+
299+
* soit utiliser [httpBuild](https://github.com/jrburke/r.js/blob/master/build/tests/http/httpBuild.js) afin de toujours compiler en dev vos assets RequireJS côté serveur
300+
* soit utiliser les fonctionnalité de [has avec RequireJS](http://requirejs.org/docs/optimization.html#hasjs) : [exemple](https://github.com/alankligman/gladius/blob/develop/src/gladius.js)
301+
302+
### Stop au JS dans les HTML et à l'HTML dans les JS
303+
304+
Avec requireJs et son plugin text vous avez la chance, de ne plus jamais écrire de Javascript dans des fichiers HTML ni d'écrire du HTML dans les fichiers Javascript.
305+
306+
Voici un exemple de
307+
308+
/* bootstrap.js */
309+
require([
310+
'jquery',
311+
'underscore',
312+
'text!views/template.html'
313+
], function($, _, tmpl){
314+
var compiledTemplate = _.template(tmpl);
315+
316+
$(function(){
317+
var $el = $('.injectHere');
318+
$('.btn').click(function(){
319+
$el.html(compiledtemplat({
320+
firstName: firstName,
321+
lastName: lastName
322+
})));
323+
});
324+
});
325+
})
326+
327+
Et voci le template en HTML utilisant le [templating underscore](http://documentcloud.github.com/underscore/#template)
328+
329+
/* views/template.html */
330+
<div class="personn">
331+
<p>My name is <%= firstName %> <%= lastName %></p>
332+
</div>
333+
334+
Si on faisait tourner le script de build r.js on obtiendrait dans le même fichier :
335+
336+
* jQuery
337+
* undercore
338+
* le template inliner. Voici à quoi cela ressemblerait : define('text!views/template.html', function(){ return '< div class= ....' });
339+
* et notre bootstrap.js
340+
341+
RequireJS possède même des plugins afin de compiler les templates via le script de build r.js : [underscore template](https://github.com/ZeeAgency/requirejs-tpl), [handlebars](https://github.com/SlexAxton/require-handlebars-plugin) . Encore une chose de moins à faire côté client.
342+
343+
344+
Conclusion
345+
----------
346+
347+
Quoi que RequireJs peut parraitre un poil complexe à configurer, RequireJs permet de répondre à pas mal de problématique et pour l'instant je n'ai rien trouvé de mieux pour faire des Single Page Web App.
348+
349+
Si vous souahitez plus de resources sur RequireJS :
350+
351+
* [Lisez le site officiel de RequireJS](http://requirejs.org/)
352+
* [Plongez vous dans la présentation de AMD, CommonJS et des ES imports/exports par Addy Osmani](http://addyosmani.com/writing-modular-js/)
353+
* [Lisez l'article de mklabs](http://blog.mklog.fr/article/require-js)
354+
* [Si vous souhaitez des plugins supplémentaire pour RequireJS](https://github.com/millermedeiros/requirejs-plugins)
355+
* [Sur mobile essayez Almond](https://github.com/jrburke/almond)
356+
* [sinon curl](https://github.com/unscriptable/curl)
357+

0 commit comments

Comments
 (0)