Enlarge your backbone project – Comment organiser son projet de façon modulaire ? – Kikon est ?



Enlarge your backbone project – Comment organiser son projet de façon modulaire ? – Kikon est ?

0 0


talk-enlarge-your-backbone-project

How to organize your growing backbone project in a modular way (FR)

On Github metidia / talk-enlarge-your-backbone-project

Enlarge your backbone project

Comment organiser son projet de façon modulaire ?

Présenté par Nicolas CARLO (@nicoespeon) et Fabien BERNARD (@fabien0102)

Kikon est ?

Nicolas CARLO

VP of Engineering

Fabien BERNARD

CTO

Saykoikon fait ?

Jeu social sur le thème du vin dans lequel on peutacheter en vrai les bouteilles que l'on produit virtuellement

  • En HTML 5… ‘‘full-stack JavaScript’’
    • Backbone.js + Marionette
    • Node.js + MongoDB
    • Grunt, Mocha, Yeoman, …

Les gars, ce soir on fait un proto !- un manager un peu trop confiant

// Probablement à l'arrache, dans un `main.js` FTW
// Note - pas besoin de routage pour nous donc on fait sans =)
$(function() {
  // (…)

  // On prépare nos bouteilles
  var bottlesModel = Backbone.Model.extend({ ... });
  var bottlesCollection = Backbone.Collection.extend({ ... });
  var bottlesView = Backbone.View.extend({ ... });
  var bottlesCollectionView = Backbone.View.extend({ ... });

  // (…)

  // Et on initialise tout le monde !
  var myBottlesList = new bottlesCollection();
  myBottlesList.fetch();

  var myBottlesListView = new bottlesCollectionView({
    collection: myBottlesList
  });
  myBottlesListView.$el.appendTo("#bottom-bar");
  myBottlesListView.render();

  // (…)
})();

Le lendemain- aka The Hangover

$(function() {
  // (…)

  // On prépare nos bouteilles
  var bottlesModel = Backbone.Model.extend({ ... });
  var bottlesCollection = Backbone.Collection.extend({ ... });
  var bottlesView = Backbone.View.extend({ ... });
  var bottlesCollectionView = Backbone.View.extend({ ... });

  // On prépare nos bâtiments
  var buildingsModel = Backbone.Model.extend({ ... });
  var buildingsCollection = Backbone.Collection.extend({ ... });
  var buildingsView = Backbone.View.extend({ ... });
  var buildingsCollectionView = Backbone.View.extend({ ... });

  // On prépare nos cépages
  var cepagesModel = Backbone.Model.extend({ ... });
  var cepagesCollection = Backbone.Collection.extend({ ... });
  var cepagesView = Backbone.View.extend({ ... });
  var cepagesCollectionView = Backbone.View.extend({ ... });

  // (…)
  // Idem avec nos ressources, nos timers, nos dialogs, nos rewards, etc.
  // 3.234 lignes plus tard
  // (…)

  // Et on initialise tout le monde !
  var myBottlesList = new bottlesCollection();
  var myBuildingsList = new buildingsCollection();
  var myCepagesList = new cepagesCollection();
  myBottlesList.fetch();
  myBuildingsList.fetch();
  myCepagesList.fetch();

  var myBottlesListView = new bottlesCollectionView({
    collection: myBottlesList
  });
  myBottlesListView.$el.appendTo("#bottom-bar");
  myBottlesListView.render();

  var myBuildingsListView = new buildingsCollectionView({
    collection: myBuildingsList
  });
  myBuildingsListView.$el.appendTo("#main");
  myBuildingsListView.render();

  var myCepagesListView = new cepagesCollectionView({
    collection: myCepagesList
  });
  myCepagesListView.$el.appendTo("#aside");
  myCepagesListView.render();

  // (…)
})();

Tiens, c'est drôle...

  • parfois ça plante, parfois ça passe…
  • quand j'enlève ce bout de code y'a tout qui plante !
  • je retrouve plus où on a codé cette boite de dialogue ?!
  • faudrait p'têtr qu'on mette des tests unit... oh wait!
  • le soleil se lève dehors =)

Il faut s'organiser!

Yaka séparer les fichiers par types

Concrètement, ça donne quoi ?

  • app/
    • main.js
    • models/
      • bottles.js
      • buildings.js
    • collections/
    • views/
    • tests/ (parce-qu'il faut pas déconner non plus)

The good parts =)

  • On y voit plus clair quand même !
  • Les tests sont isolés
  • Facile de trouver des exemples pour cette archi

The bof parts =/

  • L'achitecture du code ne reflète pas vraiment nos ‘‘modules’’
  • Et mon style, il est pas modulaire mon style ?

Pis faudra pas oublier :

  • beware la dépendance entre les ‘‘modules’’

Les questions à se poser- merci Addy Osmani \o/

  • A quel point ce module est instantanément ré-utilisable ?
  • Mes modules peuvent-ils exister indépendamment ?
  • Mes modules peuvent-ils être testés indépendamment ?
  • Mon application n'est-elle pas trop étroitement liée ?
  • Si une partie de mon appli fail, est-ce-que tout s'écroule ?

Notre solution(proposée)

La théorie

Source - http://scaleapp.org/ (inspired by N. Zakas)

Un dossier = Un module- Etpicétout !

  • app/
    • main.js
    • main.less
    • modules/
      • bottles/
        • bottles.js
        • bottles.less
        • models/
        • collections/
        • views/
          • bottles.views.composite.js
          • bottles.views.item.js
        • templates/
        • tests/

En pratiqueLes éléments de Bottles

// bottles.model.js
// Une seule dépendance -> Application Core
define( [ "app" ], function( app ) {

  return Backbone.Model.extend({ ... });

});
// bottles.views.item.js
// Une seule dépendance -> Application Core
define( [ "app" ], function( app ) {

  return Backbone.Marionette.ItemView.extend({ ... });

});

En pratiqueLe module bottles

// bottles.js
define( [
  "app",

  // Le modèle
  "bottles/models/bottles",

  // Les vues
  "bottles/views/bottles.views.item"
], function( app, Model, ItemView ) {

  // Initialise le module avec les méthodes de base.
  var Bottles = app.module();

  // On spécifie nos API
  Bottles.Model = Model.extend( { ... });
  Bottles.Views.Item = ItemView.extend( { ... });

  // On renvoie le tout
  return Bottles;

});

En pratiqueLe main.js

// main.js
define( [
  "app",

  // Les modules
  "bottles/bottles.js"
], function( app, Bottles ) {

  // Initialise le module avec les méthodes de base.
  var myBottlesList = new Bottles.Collection();
  myBottlesList.fetch();

  var myBottlesListView = new Bottles.Views.Collection( {
    collection: myBottlesList
  } );
  myBottlesListView.appendTo( "#bottom-bar" );

});

Un module c'est comme un enfant- Merci Monsieur Zakas \o/

  • Les modules doivent garder les mains dans leurs poches
    • On ne touche pas au DOM des autres
    • On ne touche pas aux méthodes des autres
  • Les modules demandent, ils ne prennent pas
  • Les modules ne laissent pas traîner leurs jouets : pas de variables globales, encapsulation
  • Les modules ne parlent pas à des étrangers : on ne parle pas directement aux autres modules

Nos outils

Require.jsGestion de dépendances + optimizer avec r.js

  • Détermine les dépendances de chaque bout de code
  • Re-compile ces dépendances en un seul fichier JS optimisé

Nom de code Brain.jsNotre pattern Mediator à nous

  • Tour de contrôle des modules
  • Centralise la communication = découplage des modules
  • Écoute les événements intéressants - Backbone.Event
  • Trigger d'autres événements pour déclencher des actions

Marionette.jsEntre autres utilitaires

  • Boite à outil indispensable pour gérer des vues de plus en plus complexes
  • Abstraction + cache de l'UI
  • Simplifier la conf.
  • Enregistrer de requests pour découpler son code
  • Sinon y'a Chaplin aussi si besoin d'aller plus loin

Abstraction + cache de l'UI

<div class="builds__menu">
  <ul class="builds__menu__tabs">
    <li data-ui="tab">Bâtiments</li>
    <li data-ui="tab">Décorations</li>
  </ul>
</div>

<!-- (…) -->
return Backbone.Marionette.ItemView.extend({

  ui: {
    "tab": "[data-ui~=tab]"
  },

  events: {
    "click @ui.tab": "switchTab"
  }

  // (…)

  highlightTabs: function() {

    this.ui.tab.each(function() {
      // Do something awesome!
    });

  }

  // (…)

});

Simplification de la conf.

return Backbone.Collection.extend({

  events: {
    "click": "displayName"
  }

  appEvents: {
    "bottles:do:setName": "setName"
  }

  // (…)

  initialize: function() {
    Marionette.bindEntityEvents( this, app, Marionette.getOption( this, "appEvents" ) );
  }

  setName: function( appellation, name ) {

    // Name this bottle

  }

  // (…)

});

Enregistrer des requests

return Backbone.Collection.extend({

  requests: {

    getCategories: function() {
      // Retrieve categories…
      return categories;
    }

  },

  // (…)

  initialize: function() {
    app.reqres.setHandler( "builds:getCategories", this.requests.getCategories, this );
  }

  // (…)

});
// Pendant ce temps, ailleurs dans le code

var buildsCategories = app.request( "builds:getCategories" );

// (…)

Axes d'amélioration

  • Améliorer l'init. des modules dans le main
  • Multiples Brain.js, parce-que ça grossit vite
  • Différents ‘‘channels’’ pour les événements

Des suggestions ?