- Un object
modules[]
sera le contenant tous les modules actifs sur une page - Au chargement d'une page, on parse la source HTML, trouver les
data-module="my-module"
, load module, callinit()
, on skip si le module est déjà présent dansmodules
- si le module à une méthode destroy(), on l’invoke avant de passer à la page suivante, ensuite on le retire de
modules
- Les modules en dehors du
barba-container
ne sont pas initialisé à nouveau, donc ne pas inclure de méthodedestroy()
sur ces modules pour les garder en instance unique entre les pages - Les modules en chunk JS et CSS sont chargé et caché dans le
<head>
sur demande dès qu'un module est invoké la première fois, Webpack gère ensuite automatiquement les imports déjà présent dans le<head>
si invoké à nouveau, permettant ainsi un app.bundle de base plus petit
const modules = [];
const elements = source.querySelectorAll(`[data-module]`);
const emitter = new EventEmitter2()
// import each module as a webpack chunk
elements.forEach((el) => {
const { initialized, module } = el.dataset;
// element can cast 1 or multiple modules, each seperated by a coma
const modules = module.split(',')
// stop here if already initialized
if (initialized === `true`) return;
// set element initialized
el.dataset.initialized = true
// try to load each module attached
modules.forEach(module => {
import(`@modules/${module}/`)
.then((module) => {
const { instance } = module.default;
const { name } = typeof instance === `object` ? instance : null;
// return if module already loaded
if (this._modules[name]) return;
// push instance to all modules
if (instance && name) this._modules[name] = instance;
// attach emitter to instance
instance.emitter = this._emitter
// attach state
instance.state = this._state
// init attached instance
if (typeof instance.init === `function`) instance.init();
})
.catch((e) => {
console.error("Error loading module :", e);
});
});
Source html :
<body data-barba="wrapper">
<!-- init() invoke 1 seule fois -->
<header data-module="transitions-pane"></header>
<main data-barba="container" data-barba-namespace="home">
<!-- init() invoke à chaque changement de cette page, permet invokes de plusieurs modules -->
<h4 data-module="foo,typography">Should init FooBar and Typography module</h4>
<!-- init() invoke à chaque changement de cette page -->
<section data-module="sliders">
<img src="image-1.jpg" alt="image 1">
<img src="image-2.jpg" alt="image 2">
</section>
...
Dans ce cas, le chunk loader ci-haut fera 3 imports & init()
, par contre le premier est à l'extérieur du data-barba="container"
, donc pas de destroy() entre les changements de page, un invoke init()
unique.
// has no destroy() method
import('ui/transitions-pane/index.js')
// has destroy() methods
import('modules/foo/index.js')
import('modules/sliders/index.js')
Placer dans un répertoire unique chaque module, un fichier index.js qui va créer un object instance
du module et l'exporter comme default
// @modules/foo/index.js
import FoobarClass from './FoobarClass'
export const instance = new FoobarClass()
export default { instance }
Ensuite, dans le fichier incluant la Class, qui doit inclure :
- Constructor avec ses defaults
- une static method
get name()
- method
init()
- si non présente, l'instance ne sera pas auto-init au changement de page
- method
destroy()
- si non présente, l'instance ne sera pas détruite au changement de page
- Finalement invoquer via une balise
<div data-module="my-name"></div>
qui doit être exactement la valeur du dossier parent qui contient ce module, c'est cette valeur que webpack match au moment duimport(@modules/my-name)
// @modules/foo/FoobarClass.js
const SELECTOR = `[data-module="foo"]`
class FoobarClass {
constructor(defaultBoolean = true) {
this._defaultBoolean = defaultBoolean;
this._objects = [];
this.el = null;
this._dummy = this._dummy.bind(this)
}
// should return this class instance name
get name() {
return `FoobarClass`;
}
init () {
this._objects = ['foo', 'bar'];
this.el = document.querySelector(SELECTOR)
this._registerEvents()
}
_registerEvents() {
if(!this.emitter) return
this.emitter.once('Foobar.dummy', this._dummy)
}
destroy() {
this._objects = [];
this.el = null;
if(!this.emitter) return
this.emitter.off('Foobar.dummy', this._dummy)
}
_dummy() {
console.log(`Someone just called me, only once!`)
}
}
Le(s) fichiers CSS/SASS associés à un module sont dans le même dossier.
/* @modules/foo/Foo.style.scss */
[data-module="foo"] {
font-size: 4rem;
}
Un traversal système est souvent nécessaire pour permettre un ou plusieurs module de communiquer entre eux.
Exemple type, module SiteNav.js
et MegaMenu.js
, les éléments du menu doivent ouvrir le MenuMenu en :hover
, donc envoyer un event Emitter.emit("MegaMenu.open")
.
Chaque module reçoit le même EventEmitter2
global, à partir duquel il pourra attacher des events.
https://www.npmjs.com/package/eventemitter2
\\ app.js
const emitter = new EventEmitter2()
const FoobarClassInstance = new FoobarClass()
FoobarClassInstance.emitter = emitter
Ensuite dans la class
// @modules/foo/FoobarClass.js
_registerEvents() {
if(!this.emitter) return
this.emitter.once('Foobar.dummy', this._dummy)
}
_dummy() {
console.log(`Act Act !`)
}
Finalement dans un autre module :
// @modules/site-nav/SiteNav.js
button.addEventListener(`mouseenter`, this.emitter.emit('Foobar.dummy'))