# node-atlas # [![Faites un don](https://img.shields.io/badge/don-%E2%9D%A4-ddddff.svg)](https://www.paypal.me/BrunoLesieur/5) [![Travis CI](https://travis-ci.org/Haeresis/NodeAtlas.svg)](https://travis-ci.org/Haeresis/NodeAtlas/) [![Version 1.8](https://img.shields.io/badge/version-1.8-brightgreen.svg)](https://github.com/MachinisteWeb/NodeAtlas) **For an international version of this README.md, [follow this link](README.en.md).** ![NodeAtlas](../media/images/ex-logo-node-atlas.png) ## Avant-propos ## NodeAtlas est un Framework JavaScript MVC(2) côté serveur sous forme de [module npm](https://www.npmjs.com/package/node-atlas) ([node-atlas](https://www.npmjs.com/package/node-atlas)) et tournant avec [Node.js](https://nodejs.org/). Il vous permet de : - Créer, maintenir et documenter des interfaces utilisateurs HTML / CSS / JavaScript pour créer un ensemble de livrables clients cohérants afin de les fournirs en tant que guide de style pour la réalisation de divers sites ou applications web. Exemple : [Pages, Composants et Documentation d'interface web](https://www.lesieur.name/test-case-atlas/) ou le site officiel NodeAtlas. - Créer, maintenir et faire tourner des sites internationalisés (et localisables) sans mettre en place le moindre fichier JavaScript. Particulièrement taillé pour les débutants ou la réalisation de sites vitrines hautement performant et maintenable en des temps records. Exemple : [Simple page web](http://bruno.lesieur.name/) - Développer des sites, des applications ou des API distantes en [Node.js](https://nodejs.org/en/) de manière évolutives et tournant côté serveur tout en vous permettant grâce à l'éco-système [npm](https://www.npmjs.com/) et les built-in fonctions de créer des contenus temps réel, de packager et optimiser vos sites pour de hautes performances, d'être orienté composant avec des réponses HTTP passant la validation W3C et parfaitement indexable par les moteurs de recherche pour le SEO. Exemple : [Blog](http://blog.lesieur.name/), [Portfolio](http://www.lesieur.name/) ou [API Distante](http://www.lesieur.name/api/) ### Pourquoi NodeAtlas ? ### NodeAtlas est designé pour créer des sites évolutifs et pour permettre au Front-end ou Back-end développeur d'embrasser [Node.js](https://nodejs.org/). Commencez avec une simple page HTML, - puis internationalisez là, - puis créez d'autres pages, - puis minifiez/offusquez/compressez vos sources, - puis utiliser Less ou/et Stylus, - puis prenez la main sur la logique serveur, - puis connectez vous à [MySQL](https://www.mysql.fr/), [MongoDB](https://www.mongodb.org/), [ElasticSearch](https://www.elastic.co/)..., - puis rendez tout ça temps réel avec [Socket.io](http://socket.io/), - puis soyez orienté composant avec [ComponentAtlas](https://github.com/MachinisteWeb/ComponentAtlas), - puis laissez votre client éditer son site avec [EditAtlas](https://github.com/MachinisteWeb/EditAtlas), - puis créer des plugins, - puis... ### Et les autres Frameworks JavaScript ? ### Contrairement aux Frameworks JavaScript comme Vue, Angular ou React, NodeAtlas fonctionne côté serveur et délivre son contenu derrière des urls par retour HTTP. Les sites sont indexables et valides W3C c'est à dire que le code utile est bien renvoyé par la réponse HTTP en premier lieu, et est ensuite modifié par requête AJAX ou Websocket si vous le souhaitez. Cela signifie donc que NodeAtlas n'est pas une alternative au nombreux Frameworks Front-end JavaScript qui ne se servent que de [Node.js](https://nodejs.org/en/) pour l'utilisation de [npm](https://www.npmjs.com/) ou [jspm](http://jspm.io/) ou [gulp](http://gulpjs.com/). NodeAtlas est plutôt une alternative à Sails ou Meteor. Il forme un socle au dessus de Node.js et remplace bien votre code PHP, JAVA ou encore C# côté serveur. À l'instar de [Meteor.js](https://www.meteor.com/), NodeAtlas vous fournit un cadre de travail et une structure initiale (que vous pouvez modifier) et des outils vous permettant de vous passer de [gulp](http://gulpjs.com/) mais contrairement à [Meteor.js](https://www.meteor.com/) l'objet `NA` n'est disponible que côté serveur. Il vous est donc laissé le choix d'étendre les mécanismes NodeAtlas à votre partie cliente ou d'utiliser la structure de votre choix. Pour un comparatif avec d'autre Librarie/Framework/API JavaScript côté serveur, [vous pouvez consulter cette grille](#nodeatlas-vs-les-autres). ### Exemples de réalisations avec NodeAtlas ### Voici une liste de repository que vous pouvez décortiquer à votre gré : - [Génération et maintenance de maquette HTML](https://github.com/MachinisteWeb/ResumeAtlas/). - [Test et Documentation d'Interface Ulilisateur](https://github.com/MachinisteWeb/TestCaseAtlas/). - [Maintenance de site HTML (sans Back-end)](https://github.com/MachinisteWeb/NodeAtlas/tree/gh-pages/). - [Site Node.js avec Websocket et PopState](https://github.com/MachinisteWeb/BookAtlas/). - [Site Node.js avec base MongoDB et Redis](https://github.com/MachinisteWeb/BlogAtlas/). - [Exemple Node.js de modification de contenu live sans Back-office](https://github.com/MachinisteWeb/EditAtlas/). - [Simple Serveur Web pour un dossier](https://github.com/MachinisteWeb/SimpleAtlas/). - [Exemple d'API REST](https://github.com/MachinisteWeb/ApiAtlas/). - [Utilisation du préprocesseur Less en temps réel côté serveur](https://github.com/MachinisteWeb/LessAtlas/). - [Création d'extensions pour booster les capacités natives](https://github.com/MachinisteWeb/ComponentAtlas/). ### Table des matières ### - [Avant-propos](#avant-propos) - [Pourquoi NodeAtlas ?](#pourquoi-nodeatlas-) - [Et les autres Frameworks JavaScript ?](#et-les-autres-frameworks-javascript-) - [Exemples de réalisations avec NodeAtlas](#exemples-de-réalisations-avec-nodeatlas) - [Table des matières](#table-des-matières) - [Documentation](#documentation) - [Contribution](#contribution) - [Installation](#installation) - [Installation de NodeAtlas](#installation-de-nodeatlas) - [Installation de Node.js](#installation-de-nodejs) - [Commencer avec NodeAtlas](#commencer-avec-nodeatlas) - [Ensemble de fichiers](#ensemble-de-fichiers) - [Configuration minimale](#configuration-minimale) - [Lancer le site avec NodeAtlas](#lancer-le-site-avec-nodeatlas) - [Partie View et Template](#partie-view-et-template) - [Plusieurs pages](#plusieurs-pages) - [Raccourci de template](#raccourci-de-template) - [Héberger des images, polices, CSS, JS, etc.](#héberger-des-images-polices-css-js-etc) - [Gérer des inclusions pour éviter la redondance du code](#gérer-des-inclusions-pour-éviter-la-redondance-du-code) - [Gérer des variations au sein d'un même template](#gérer-des-variations-au-sein-dun-m%C3%AAme-template) - [Gérer le multilingue](#gérer-le-multilingue) - [Changer les paramètres d'url](#changer-les-paramètres-durl) - [Créer ses propres variables de webconfig](#créer-ses-propres-variables-de-webconfig) - [Utiliser NodeAtlas pour générer des maquettes HTML](#utiliser-nodeatlas-pour-générer-des-maquettes-html) - [Partie Controller et Model](#partie-controller-et-model) - [Cycle de Vie et Hooks](#cycle-de-vie-et-hooks) - [Utiliser les Websocket à la place des échanges AJAX](#utiliser-les-websocket-à-la-place-des-échanges-ajax) - [Utiliser une base de données MySQL (SQL)](#utiliser-une-base-de-données-mysql-sql) - [Utiliser une base de données MongoDB (NoSQL)](#utiliser-une-base-de-données-mongodb-nosql) - [Pour aller plus loin](#pour-aller-plus-loin) - [Gérer le routage (Url Rewriting)](#gérer-le-routage-url-rewriting) - [Gérer les pages inexistantes](#gérer-les-pages-inexistantes) - [Gérer les redirections](#gérer-les-redirections) - [Gérer les Headers de page](#gérer-les-headers-de-page) - [Faire tourner le site en HTTPs](#faire-tourner-le-site-en-https) - [Minifier les CSS / JS](#minifier-les-css--js) - [Générer les CSS avec Less](#générer-les-css-avec-less) - [Générer les CSS avec Stylus](#générer-les-css-avec-stylus) - [Optimiser les Images](#optimiser-les-images) - [Injecter du CSS inline pour maintenir des assets Email](#injecter-du-css-inline-pour-maintenir-des-assets-email) - [Autoriser / Interdire les demandes GET / POST](#autoriser--interdire-les-demandes-get--post) - [Autoriser / Interdire les demandes PUT / DELETE](#autoriser--interdire-les-demandes-put--delete) - [Changer les paramètres des Sessions](#changer-les-paramètres-des-sessions) - [Stockage externe des Sessions](#stockage-externe-des-sessions) - [Changer les chevrons <% %> du moteur de template](#changer-les-chevrons---du-moteur-de-template) - [Changer l'url final des hostname et port d'écoute](#changer-lurl-final-des-hostname-et-port-découte) - [Générer les urls dynamiquement](#générer-les-urls-dynamiquement) - [CLI / Commandes de lancement](#cli--commandes-de-lancement) - [--directory <path>](#--directory-path) - [--webconfig <webconfigName>](#--webconfig-webconfigname) - [--browse [subpath]](#--browse-subpath) - [--httpHostname <httpHostname>](#--httphostname-httphostname) - [--httpPort <httpPort>](#--httpport-httpport) - [--generate](#--generate) - [--lang <culture-country>](#--lang-culture-country) - [--init [path]](#--init-path) - [--httpSecure [pathName]](#--httpsecure-pathName) - [API / NodeAtlas comme module npm](#api--nodeatlas-comme-module-npm) - [<node-atlas-instance>.init()](#node-atlas-instanceinit) - [<node-atlas-instance>.config(Object)](#node-atlas-instanceconfigobject) - [<node-atlas-instance>.run(Object)](#node-atlas-instancerunobject) - [<node-atlas-instance>.started(Function)](#node-atlas-instancestartedfunction) - [<node-atlas-instance>.generated(Function)](#node-atlas-instancegeneratedfunction) - [<node-atlas-instance>.created(Function)](#node-atlas-instancecreatedfunction) - [NodeAtlas comme simple serveur web](#nodeatlas-comme-simple-serveur-web) - [Environnement de Développement](#environnement-de-développement) - [Debug du Front-end](#debug-du-front-end) - [Debug du Back-end](#debug-du-back-end) - [Tests de Périphériques](#tests-de-périphériques) - [Environnement de Production](#environnement-de-production) - [Dans un environnement Windows Server avec iisnode](#dans-un-environnement-windows-server-avec-iisnode) - [Dans un environnement Unix avec forever](#dans-un-environnement-unix-avec-forever) - [Dans un environnement Unix avec Nginx](#dans-un-environnement-unix-avec-nginx) - [Proxy](#proxy) - [Plus sur NodeAtlas](#plus-sur-nodeatlas) - [NodeAtlas VS les autres](#nodeatlas-vs-les-autres) ### Documentation ### En complément de cette documentation, vous avez également accès aux, - [tl;dr](https://github.com/MachinisteWeb/NodeAtlas#node-atlas), - [détails des fonctions de l'objet NA](https://node-atlas.js.org/v1.x/doc/index.html) (En) et vous pouvez aussi - [discuter sur le chat ou demander de l'aide pour NodeAtlas](https://gitter.im/NodeAtlas/Aide). ### Contribution ### Si vous souhaitez contribuer avec : - De l'amélioration ou de la correction de code, - De la correction d'orthographe pour la documentation française ou - De la traduction décente pour la documentation anglaise Merci de respecter ses étapes : 1. Forkez le repository NodeAtlas. 2. Travaillez sur une branch créé à partir de la branch master. 3. Commitez et pushez votre branch. 4. Faites une pull request. 5. Soyez patient. ;-) Tout en respectant les conventions suivantes : - [Passez le test Sonarqube JS avec un rang A](http://www.sonarqube.org/) : Bugs, Vulnerabilités et Dette Technique. Merci d'avance pour votre aide ! ## Installation ## Avant de pouvoir installer NodeAtlas, assurez-vous d'avoir installé [Node.js](https://nodejs.org/), nous verrons cela dans la section : [Installation de Node.js](#installation-de-nodejs) plus bas. ### Installation de NodeAtlas ### *Note: Si vous êtes sous Linux, il faudra ajouter `sudo` en amont des commandes si vous n'êtes pas root.* Il y a plusieurs manières d'installer NodeAtlas : - `npm install node-atlas` (recommandé pour un [usage sous forme de module](#api--nodeatlas-comme-module-npm) dans un projet). *Ceci installera* **NodeAtlas** *dans le dossier `node_modules/node-atlas` du dossier d'exécution de la commande.* - `npm install -g node-atlas` (recommandé pour un [usage sous forme de module](#api--nodeatlas-comme-module-npm) dans beaucoup de projet ou pour [un usage à la ligne de commande](#cli--commandes-de-lancement)). *Ceci installera* **NodeAtlas** *dans le dossier `node_modules/node-atlas` global.* - Cloner le répertoire depuis [GitHub](https://github.com/MachinisteWeb/NodeAtlas/) (recommandé pour participer au développement). *Ceci installera* **NodeAtlas** *dans le dossier d'accueil du clonage.* **Lancez au moins une fois NodeAtlas à la ligne de commande `\> node node-atlas/`, pour installer les _node_modules_.** - Télécharger NodeAtlas depuis le site dépôt [NodeAtlas](https://github.com/MachinisteWeb/NodeAtlas). *Une fois téléchargé, dézippez* **NodeAtlas** *dans le dossier qui vous conviendra.* **Utilisez `npm install` depuis le dossier `node-atlas/` pour installer toutes les dépendences.** ### Installation de Node.js ### NodeAtlas est développé sous la forme d'un [Node.js Module Package](https://www.npmjs.com/) ce qui signifie qu'il a besoin de Node.js pour fonctionner. Node.js permet de rapidement et efficatement faire tourner du JavaScript en dehors du navigateur, rendant possible l'utilisation du même langage côté front-end et back-end. *Note: Python 2.6 ou 2.7 est requis pour build depuis les sources tarballs.* #### Installer sur Windows #### En utilisant un installeur : - [Download Windows Installer](https://nodejs.org/en/download/). En utilisant [chocolatey](http://chocolatey.org/) pour installer Node: ``` cinst nodejs ``` ou en l'installant avec NPM : ``` cinst nodejs.install ``` #### Installer sur OSX #### En utilisant un installeur : - [Download Macintosh Installer](https://nodejs.org/en/download/). En utilisant [homebrew](https://github.com/mxcl/homebrew): ``` brew install node ``` En utilisant [macports](http://www.macports.org/): ``` port install nodejs ``` #### Installer sur Linux #### Using a package: - [Download Linux Binaries](https://nodejs.org/en/download/). Example install with apt-get: ``` sudo apt-get install python-software-properties python g++ make curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash - sudo apt-get install -y nodejs ``` Il y a un conflit de nom entre le node de package (Amateur Packet Radio Node Program), et les binaires de nodejs on été renommé de node à nodejs. Vous pouvez effectuer un symlink de /usr/bin/node à /usr/bin/nodejs ou désinstaller Amateur Packet Radio Node pour éviter le conflit. ## Commencer avec NodeAtlas ## Nous allons voir pour commencer mettre en place l'ensemble de fichier minimal afin de réaliser un `hello-world`. ### Ensemble de fichiers ### Après avoir installé NodeAtlas quelque part sur votre machine, créez-vous un ensemble de fichiers représentant un site n'importe où ailleurs comme la structure ci-dessous. ``` site-hello-world/ ├─ templates/ │ └─ index.htm └─ webconfig.json ``` Voici le fichier « /site-hello-world/templates/index.htm » : ```html
C'est la page d'accueil.
C'est la page des membres.
C'est la page d'accueil.
" } ``` *variations/members.json* ```js { "titlePage": "Liste des membres", "classPage": "members", "content": "C'est la page des membres.
" } ``` vous aurez accès aux adresses : - *http://localhost/* - *http://localhost/liste-des-membres/* *Note : Si* ***variationsRelativePath*** *n'est pas présent dans « webconfig.json », par défaut le dossier des variations est bien* ***variations***. ***variationsRelativePath*** *est donc utile seulement pour changer le nom/chemin de répertoire.* ### Gérer le multilingue ### #### Toutes les langues sur le même site #### Sur le même principe, les variations peuvent être utilisées pour créer la même page, mais dans des langues différentes : ```js { "languageCode": "en-gb", "variationsRelativePath": "languages", "routes": { "/": { "template": "landing.htm", "variation": "landing.json" }, "/home/": { "template": "home.htm", "variation": "home.json" }, "/accueil/": { "template": "home.htm", "variation": "home.json", "languageCode": "fr-fr" } } } ``` *Note : Dans cet exemple j'ai décidé de me passer d'un fichier de variation commune, car je n'ai pas précisé de* ***commonVariation***. *J'ai également totalement arbitrairement décidé de renommer mon dossier* ***variations*** *en* ***languages***. avec les fichiers suivants : ``` ├─ components/ │ ├─ head.htm │ └─ foot.htm ├─ languages/ │ ├─ landing.json │ ├─ en-gb │ │ └─ home.json │ └─ fr-fr │ └─ home.json ├─ templates/ │ ├─ landing.htm │ └─ home.htm └─ webconfig.json ``` *components/head.htm* ```htmlThis is a home page.
" } ``` *languages/fr-fr/home.json* ```js { "titlePage": "Bienvenue", "classPage": "home", "content": "C'est la page d'accueil.
" } ``` vous aurez accès aux adresses : - *http://localhost/* - *http://localhost/home/* - *http://localhost/accueil/* *Note : Par défaut c'est le* ***languageCode*** *racine qui conditionne la langue d'affichage du site. Cependant, spécifiquement par page on peut changer la langue avec également le* ***languageCode***. *Il faut également savoir que dès que le site ou une page à un* ***languageCode*** *dans la configuration, ses fichiers de variations doivent être placées dans un sous répertoire portant le nom du* ***languageCode***. #### Utiliser seulement les variations avec le multilingue actif #### Vous avez peut-être constaté dans l'exemple précédent que le fichier `landing.json` n'était pas dans le dossier `en-gb/` ou `fr-fr/`. Cela est tout à fait possible et signifie qu'il sera utilisé dans les langues qui ne le possèdent pas dans leur dossier. Aussi, quand un `languageCode` est précisé, NodeAtlas part d'abord chercher la valeur dans le fichier du dossier correspondant. Si celle-ci n'y ai pas, alors il part la chercher dans le dossier parent (celui utilisé en standard pour les variations sans multilingue). Cela va vous permettre par exemple de manager la langue maître directement dans le dossier de variation. Ainsi avec l'exemple suivant : ``` │ ┊┉ ├─ variations/ │ ├─ common.json │ ├─ home.json │ ├─ fr-fr │ │ ├─ common.json │ │ └─ home.json ┊┉ ``` vous pouvez - gérer la version `en-gb` directement à la racine de `variations/` (comme NodeAtlas ne trouve rien dans `en-gb` il utilise alors les valeurs des fichiers racines) et - gérer la version `fr-fr` dans le dossier `fr-fr/`, ainsi, si une phrase n'est pas encore traduite dans un fichier `fr-fr`, au lieu de renvoyer une erreur, NodeAtlas renverra la version racine, soit la version `en-gb`. #### À chaque langue sa configuration #### Vous pouvez également décider de faire tourner chaque langue dans un « webconfig.json » différent. Avec l'ensemble de fichier suivant : ``` ├─ components/ │ ├─ head.htm │ └─ foot.htm ├─ variations/ │ ├─ landing.json │ ├─ en-gb │ │ ├─ home.json │ │ └─ members.json │ └─ fr-fr │ ├─ home.json │ └─ members.json ├─ templates/ │ ├─ landing.htm │ ├─ home.htm │ └─ members.htm ├─ webconfig.json ├─ webconfig.en-gb.json └─ webconfig.fr-fr.json ``` vous pourriez avoir les « webconfig.json » suivant : *webconfig.json* ```js { "routes": { "/": { "template": "landing.htm", "variation": "landing.json" } } } ``` *webconfig.en-gb.json* ```js { "httpPort": 81, "urlRelativeSubPath": "english", "languageCode": "en-gb", "routes": { "/": { "template": "home.htm", "variation": "home.json" }, "/members-list/": { "template": "members.htm", "variation": "members.json" } } } ``` *webconfig.fr-fr.json* ```js { "httpPort": 82, "urlRelativeSubPath": "francais", "languageCode": "fr-fr", "routes": { "/": { "template": "home.htm", "variation": "home.json" }, "/liste-des-membres/": { "template": "members.htm", "variation": "members.json" } } } ``` et avoir accès aux adresses : - *http://localhost/* - *http://localhost:81/english/* - *http://localhost:81/english/* - *http://localhost:81/english/members-list/* - *http://localhost:82/francais/* - *http://localhost:82/francais/liste-des-membres/* Il est ensuite possible de faire du reverse proxy avec [Bouncy](#proxy) (par exemple) pour ramener l'ensemble des urls sur le port 80 afin d'obtenir : - *http://www.website.ext/* - *http://www.website.ext/english/* - *http://www.website.ext/english/* - *http://www.website.ext/english/members-list/* - *http://www.website.ext/francais/* - *http://www.website.ext/francais/liste-des-membres/* ### Changer les paramètres d'url ### Par défaut, si vous utilisez la configuration suivante : ```js { "routes": { "/": { "template": "index.htm" } } } ``` cela est identique à utiliser celle-ci : ```js { "httpHostname": "localhost", "httpPort": 80, "httpSecure": false, "urlRelativeSubPath": "", "routes": { "/": { "template": "index.htm" } } } ``` et vous pourrez accéder à l'url : *http://localhost/*. Changez alors la configuration en ceci : ```js { "httpHostname": "127.0.0.1", "httpPort": 7777, "httpSecure": true, "urlRelativeSubPath": "sub/folder", "routes": { "/": { "template": "index.htm" } } } ``` pour accéder à : *https://127.0.0.1:7777/sub/folder/* ### Créer ses propres variables de webconfig ### Imaginons deux webconfigs dans lesquels nous allons créer nos propres variables comme suit : 1. « webconfig.json » ```js { "routes": { "/": { "template": "index.htm" } }, "_minified": "" } ``` 2. « webconfig.prod.json » ```js { "routes": { "/": { "template": "index.htm" } }, "_minified": ".min" } ``` avec cet ensemble de fichiers ``` ├─ assets/ │ ├─ stylesheets/ │ │ ├─ common.css │ │ └─ common.min.css │ └─ javascript/ │ ├─ common.js │ └─ common.min.js ├─ templates/ │ └─ index.htm ├─ webconfig.json └─ webconfig.prod.json ``` et « index.htm » contenant : ```htmlC'est la page d'accueil.
" } ``` *templates/index.htm* ```html <%- include('head.htm') %>C'est la page d'accueil.
" } ``` *templates/index.htm* ```htmlC'est la page d'accueil.
I am using markdown.
La date actuelle est :
" } ``` Jusque là, rien d'inhabituel et tout fonctionnerait sans partie contrôleur. Mais nous allons mettre en place la communication via Socket.IO côté Serveur puis côté Client. Côté serveur, nous utiliserons les fichiers suivant : **controllers/common.js** ```js var privates = {}; // Chargement des modules pour ce site dans l'objet NodeAtlas. exports.loadModules = function () { // Récupérer l'instance « NodeAtlas » du moteur. var NA = this; // Associations de chaque module pour y avoir accès partout. NA.modules.socketio = require('socket.io'); NA.modules.cookie = require('cookie'); }; // Exemple d'utilisation de Socket.IO. privates.socketIoInitialisation = function (socketio, NA, next) { var optionIo = (NA.webconfig.urlRelativeSubPath) ? { path: NA.webconfig.urlRelativeSubPath + '/socket.io', secure: ((NA.webconfig.httpSecure) ? true : false) } : undefined, io = socketio(NA.server, optionIo), cookie = NA.modules.cookie, cookieParser = NA.modules.cookieParser; // Synchronisation des Sessions avec Socket.IO. io.use(function(socket, next) { var handshakeData = socket.request; // Fallback si les cookies ne sont pas gérés. if (!handshakeData.headers.cookie) { return next(new Error('Cookie de session requis.')); } // Transformation de la String cookie en Objet JSON. handshakeData.cookie = cookie.parse(handshakeData.headers.cookie); // Vérification de la signature du cookie. handshakeData.cookie = cookieParser.signedCookies(handshakeData.cookie, NA.webconfig.session.secret); // Garder à portée l'ID de Session. handshakeData.sessionID = handshakeData.cookie[NA.webconfig.session.key]; // Accepter le cookie. NA.sessionStore.load(handshakeData.sessionID, function (error, session) { if (error || !session) { return next(new Error('Aucune session récupérée.')); } else { handshakeData.session = session; next(); } }); }); // Suite. next(io); }; // Ajout d'évènements d'écoute pour un controller spécifique « index.js » (voir exemple dans le fichier d'après). privates.socketIoEvents = function (io, NA) { var params = {}; params.io = io; // Evènements pour la page index (voir exemple dans le fichier d'après). require('./index').asynchrone.call(NA, params); }; // Configuration de tous les modules. exports.setConfigurations = function (next) { var NA = this, socketio = NA.modules.socketio; // Initialisation de Socket IO. privates.socketIoInitialisation(socketio, NA, function (io) { // Écoute d'action Socket IO. privates.socketIoEvents(io, NA); // Étapes suivante du moteur. next(); }); }; ``` *Note : Ceci est la configuration global de Socket.IO côté serveur.* **controllers/index.js** ```js // Intégralité des actions Websocket possible pour ce template. // Utilisé non pas par « NodeAtlas » mais par « common.js » (voir fichier précédent). exports.asynchrone = function (params) { var NA = this, io = params.io; // Dès qu'on a un lien valide entre le client et notre back... io.sockets.on("connection", function (socket) { // ...rester à l'écoute de la demande « create-article-button »... socket.on("server-render", function (data) { var sessionID = socket.request.sessionID, session = socket.request.session, variation = {}; // On récupère les variations spécifiques dans la bonne langue. variation = NA.addSpecificVariation("index.json", data.lang, variation); // On récupère les variations communes dans la bonne langue. variation = NA.addCommonVariation(data.lang, variation); // On récupère le fragment HTML depuis le dossier `componentsRelativePath` et on applique les variations. data.render = NA.newRender("index.htm", variation); // Et on répond à tous les clients avec un jeu de donnée dans data. io.sockets.emit('server-render', data); }); }); }; ``` Quand au côté client, nous utiliserons les fichiers suivant : **assets/javascript/common.js** ```js window.website = window.website || {}; (function (publics) { "use strict"; var privates = {}, optionsSocket, body = document.getElementsByTagName("body")[0]; // On configure Socket.IO côté Client. optionsSocket = (body.getAttribute("data-subpath") !== "") ? { path: "/" + body.getAttribute("data-subpath") + ((body.getAttribute("data-subpath")) ? "/" : "") + "socket.io" } : undefined; publics.socket = io.connect((body.getAttribute("data-subpath") !== "") ? body.getAttribute("data-hostname") : undefined, optionsSocket); }(website)); // On exécute le JavaScript Spécifique à la page en cours, ici ["index"]. website[document.getElementsByTagName("body")[0].getAttribute("data-variation")].init(); ``` *Note : Ceci est la configuration global de Socket.IO côté client en ce basant sur `data-subpath` et `data-hostname`.* **assets/javascript/index.js** ```js window.website = window.website || {}; (function (publics) { "use strict"; var html = document.getElementsByTagName("html")[0], body = document.getElementsByTagName("body")[0], layout = document.getElementsByClassName("layout")[0]; // On associe sur le bouton l'action de communiquer avec le serveur en cliquant dessus. function setServerRender() { var button = document.getElementsByTagName("button")[0]; button.addEventListener("click", function () { website.socket.emit("server-render", { lang: html.getAttribute("lang"), variation: body.getAttribute("data-variation") }); }); } // On créer le code qui s'exécutera au lancement de la page. publics.init = function () { // On affecte l'action au bouton. setServerRender(); // Quand le serveur répond après notre demande auprès de lui... website.socket.on("server-render", function (data) { // ...on met à jour le contenu... layout.innerHTML = data.render; // ...et ré-affectons l'action au bouton du nouveau contenu. setServerRender(); }); }; }(website.index = {})); ``` Lancer votre projet et rendez-vous à l'adresse `http://localhost/` dans deux onglets différent, voir même, dans deux navigateurs différent. Vous constaterez alors qu'à chaque clique sur « Update », la page se remettra à jour (comme le montre la date courante) sur tous les onglets ouvert. Grâce à `NA.addSpecificVariation`, `NA.addCommonVariation` et `NA.newRender`, il est possible de générer une nouvelle compilation d'un template (composant) et d'une variation commune et spécifique. Si `data.lang` dans notre exemple est de type `undefined`, alors les fichiers seront cherchés à la racine. Si `variation` est de type `undefined` alors un objet contenant uniquement le scope demandé sera renvoyé. ### Utiliser une base de données MySQL (SQL) ### Nous allons voir à présent comment utiliser des informations venant d'une base de donnée. Pour cela nous allons utiliser le module npm `mysql`. Il va également nous falloir [installer un serveur MySQL](https://dev.mysql.com/downloads/installer/). #### Base de données MySQL #### Tout d'abord, nous allons alimenter la base de données avec la base `demo` : ``` CREATE DATABASE demo; ``` et la sélectionner : ``` USE demo ``` puis créer la table `user` : ``` CREATE TABLE user ( id INT PRIMARY KEY NOT NULL AUTO_INCREMENT, lastname VARCHAR(100), firstname VARCHAR(100), email VARCHAR(255), birthdate DATE, gender TINYINT(1), country VARCHAR(255), town VARCHAR(255), zipcode VARCHAR(5), address VARCHAR(255) ); ``` et la remplir avec un jeu de données : ``` INSERT INTO user ( lastname, firstname, email, birthdate, gender, country, town, zipcode, address ) VALUES ( "Lesieur", "Bruno", "bruno.lesieur@gmail.com", "1988/07/18", true, "France", "Annecy", 74000, "66 avenue de Genève" ); ``` #### Fichiers NodeAtlas #### Avec le jeu de fichier suivant : ``` ├─ assets/ │ └─ javascript/ │ └─ models/ │ └─ user.js ├─ controllers/ │ ├─ common.js │ └─ index.js ├─ models/ │ └─ user.js ├─ templates/ │ └─ index.htm ├─ variations/ │ ├─ common.json │ └─ index.json └─ webconfig.json ``` Nous allons utiliser le `webconfig.json` suivant avec une variable custom `_mysqlConfig` qui contiendra toutes les informations pour se connecter à la base de données : ``` { "commonController": "common.js", "commonVariation": "common.json", "routes": { "/": { "template": "index.htm", "variation": "index.json", "controller": "index.js" } }, "_mysqlConfig": { "host": "localhost", "user": "root", "password": "root", "database": "demo" } } ``` Avec les fichiers suivant pour afficher la page : **templates/index.htm** ```htmlDétail de l'entrée `bruno`.
" } ``` Enfin nous allons nous connecter à la base de données avec le controlleur globale `controllers/common.js` : ```js exports.loadModules = function () { var NA = this; NA.modules.mysql = require('mysql'); NA.models = {}; NA.models.User = require('../models/user.js'); }; exports.setConfigurations = function (next) { var NA = this, mysql = NA.modules.mysql; NA.mySql = mysql.createPool(NA.webconfig._mysqlConfig); next(); }; ``` Et afficher les résultats via le controlleur spécifique `controllers/index.js` : ```js exports.changeVariation = function (params, next) { var NA = this, variation = params.variation, User = NA.models.User, bruno = User(); NA.mySql.getConnection(function(err, connection) { if (err) { console.log(err); return false; } bruno .setConnection(connection) .firstname("bruno") .readFirst(function () { variation.id = bruno.id(); variation.lastname = bruno.lastname(); variation.firstname = bruno.firstname(); variation.email = bruno.email(); variation.birthdate = bruno.birthdate(); variation.gender = (bruno.gender()) ? variation.common.male : variation.common.female; variation.country = bruno.country(); variation.town = bruno.town(); variation.zipcode = bruno.zipcode(); variation.address = bruno.address(); next(variation); }); }); }; ``` en utilisant le model `user` via le fichier de connexion à la base de données `models/user.js` : ```js /* jslint esversion: 6 */ var user = require('../assets/javascript/models/user.js'); function User(connection) { var privates = {}, publics = this; privates.connection = connection; if (!(publics instanceof User)) { return new User(); } publics.setConnection = function (connection) { privates.connection = connection; return publics; }; user.call(publics); publics.readFirst = function (callback) { var select = `SELECT id, lastname, firstname, email, birthdate, gender, country, town, zipcode, address FROM user`, where = "", limit = " LIMIT 0,1 ", addWhere = " WHERE "; if (publics.id()) { where += addWhere + "`id` = '" + publics.id().replace(/'/g, "''") + "'"; addWhere = ' && '; } if (publics.lastname()) { where += addWhere + "`lastname` = '" + publics.lastname().replace(/'/g, "''") + "'"; addWhere = ' && '; } if (publics.firstname()) { where += addWhere + "`firstname` = '" + publics.firstname().replace(/'/g, "''") + "'"; addWhere = ' && '; } if (publics.email()) { where += addWhere + "`email` = '" + publics.email().replace(/'/g, "''") + "'"; addWhere = ' && '; } if (publics.birthdate()) { where += addWhere + "`birthdate` = '" + publics.birthdate().replace(/'/g, "''") + "'"; addWhere = ' && '; } if (publics.gender()) { where += addWhere + "`gender` = '" + publics.gender().replace(/'/g, "''") + "'"; addWhere = ' && '; } if (publics.country()) { where += addWhere + "`country` = '" + publics.country().replace(/'/g, "''") + "'"; addWhere = ' && '; } if (publics.town()) { where += addWhere + "`town` = '" + publics.town().replace(/'/g, "''") + "'"; addWhere = ' && '; } if (publics.zipcode()) { where += addWhere + "`zipcode` = '" + publics.zipcode().replace(/'/g, "''") + "'"; addWhere = ' && '; } if (publics.address()) { where += addWhere + "`address` = '" + publics.address().replace(/'/g, "''") + "'"; addWhere = ' && '; } privates.connection.query(select + where + limit, function(err, rows, fields) { if (err) console.log(err); if (rows[0]) { publics.id(rows[0].id); publics.lastname(rows[0].lastname); publics.firstname(rows[0].firstname); publics.email(rows[0].email); publics.birthdate(rows[0].birthdate); publics.gender((rows[0].gender) ? true : false); publics.country(rows[0].country); publics.town(rows[0].town); publics.zipcode(rows[0].zipcode); publics.address(rows[0].address); } callback(); }); }; } User.prototype = Object.create(user.prototype); User.prototype.constructor = User; module.exports = User; ``` basé sur une classe `user` partagé entre le Front et le Back `assets/javascript/models/user.js` : ```js (function (expose, factory) { if (typeof module !== 'undefined' && module.exports) { module.exports = factory; } else { expose.User = factory; } }(this, function User() { var privates = {}, publics = this; if (!(publics instanceof User)) { return new User(); } publics.id = function (id) { if (typeof id === 'undefined') { return privates.id; } else { privates.id = id; return publics; } }; publics.lastname = function (lastname) { if (typeof lastname === 'undefined') { return privates.lastname; } else { privates.lastname = lastname; return publics; } }; publics.firstname = function (firstname) { if (typeof firstname === 'undefined') { return privates.firstname; } else { privates.firstname = firstname; return publics; } }; publics.email = function (email) { if (typeof email === 'undefined') { return privates.email; } else { privates.email = email; return publics; } }; publics.birthdate = function (birthdate) { if (typeof birthdate === 'undefined') { return privates.birthdate; } else { privates.birthdate = birthdate; return publics; } }; publics.gender = function (gender) { if (typeof gender === 'undefined') { return privates.gender; } else { privates.gender = gender; return publics; } }; publics.country = function (country) { if (typeof country === 'undefined') { return privates.country; } else { privates.country = country; return publics; } }; publics.town = function (town) { if (typeof town === 'undefined') { return privates.town; } else { privates.town = town; return publics; } }; publics.zipcode = function (zipcode) { if (typeof zipcode === 'undefined') { return privates.zipcode; } else { privates.zipcode = zipcode; return publics; } }; publics.address = function (address) { if (typeof address === 'undefined') { return privates.address; } else { privates.address = address; return publics; } }; })); ``` Vous obtiendrez la sortie suivante : ```htmlDétail de l'entrée `bruno`.
Détail du document `{ \"identity.firstname\": \"Bruno\" }`.
" } ``` Enfin nous allons nous connecter à la base de données avec le controlleur globale `controllers/common.js` : ```js exports.loadModules = function () { var NA = this, path = NA.modules.path; NA.modules.mongoose = require('mongoose'); NA.models = {}; NA.models.User = require('../assets/javascript/models/user.js'); }; exports.setConfigurations = function (next) { var NA = this, mongoose = NA.modules.mongoose, config = NA.webconfig._mongodbConfig; mongoose.Promise = global.Promise; mongoose.model("user", NA.models.User, "user"); mongoose.connect("mongodb://" + config.host + ":" + config.port + "/" + config.database, function (error) { next(); }); }; ``` Et afficher les résultats via le controlleur spécifique `controllers/index.js` : ```js exports.changeVariation = function (params, next) { var NA = this, variation = params.variation, mongoose = NA.modules.mongoose, User = mongoose.model('user'); User .findOne({ "identity.firstname": "Bruno" }) .exec(function (err, bruno) { variation.id = bruno._id; variation.lastname = bruno.identity.lastname; variation.firstname = bruno.identity.firstname; variation.birthdate = bruno.identity.birthdate; variation.email = bruno.email; variation.gender = (bruno.identity.gender) ? variation.common.male : variation.common.female; variation.country = bruno.location.country; variation.town = bruno.location.town; variation.zipcode = bruno.location.zipcode; variation.address = bruno.location.address; next(variation); }); }; ``` en utilisant sur une classe `user` partagé entre le Front et le Back `assets/javascript/models/user.js` : ```js var mongoose; if (typeof module !== 'undefined' && module.exports) { mongoose = require('mongoose'); } (function (expose, factory) { if (mongoose) { module.exports = factory; } else { expose.User = factory; } }(this, new mongoose.Schema({ _id: mongoose.Schema.Types.ObjectId, email: { type : String, match: /^\S+@\S+$/ }, identity: { lastname: String, firstname: String, gender: Boolean, birthdate : { type : Date, default : Date.now } }, location: { country: String, town: String, zipcode: String, address: String } }))); ``` Vous obtiendrez la sortie suivante : ```htmlDétail de l'entrée `{ "identity.firstname": "Bruno" }`.
This line is red.
``` *assets/stylesheets/common.less* ```css p { color: #f00; } ``` vous générerez le fichier `assets/stylesheets/common.css` en appelant l'url `http://localhost/` ou `http://localhost/stylesheets/common.css`. #### Source Map et Minification #### Par défaut, dans l'exemple ci-dessus un fichier `common.css.map` sera généré. Celui-ci permet à votre navigateur de vous indiquer qu'elle ligne du fichier `.less` a générée la propriété CSS de l'élément que vous avez sélectionné dans votre débuggeur. Cela se désactive avec `enableLess.sourceMap` à `false` : ``` "enableLess": { "sourceMap": false }, "routes": { "/": "index.htm" } ``` Vous pouvez également générer des fichiers CSS déjà minifiés avec : ``` "enableLess": { "compress": true }, "routes": { "/": "index.htm" } ``` #### Compiler les Less avec `--generate` #### Comme les Less sont compilés a la volé, quand le fichier est demandé en http(s), toutes modifications dans le Less demandera de faire tourner le site pour la répercuter dans le CSS. Ensuite seulement vous pourrez minifier vos CSS. Il est possible d'automatiser cette tâche pour ne pas avoir à démarrer le site grâce à `enableLess.less`. Avec le `webconfig.json` suivant : ```js { "enableLess": { "less": [ "stylesheets/common.less", "stylesheets/component-1.less", "stylesheets/component-2.less", "stylesheets/component-3.less" ] }, "routes": { "/": "index.htm" } } ``` ou suivante : ```js { "enableLess": { "less": "less.json" }, "routes": { "/": "index.htm" } } ``` avec `less.json` qui contient : ```js [ "stylesheets/common.less", "stylesheets/component-1.less", "stylesheets/component-2.less", "stylesheets/component-3.less" ] ``` Par défaut, les `@import` utilisés par Less seront capable de fouiller dans les sous dossier : `styles`, `stylesheets` ou `css`. Il est possible de changer cela avec : ```js { "enableLess": { "paths": [ "subdirectory/styles-files", ], "less": "less.json" }, "routes": { "/": "index.htm" } } ``` ### Générer les CSS avec Stylus ### Vous pouvez utiliser le préprocesseur Stylus pour créer vos CSS. Le fonctionnement est le suivant : à chaque fois qu'une requête CSS est effectuée, si un équivalent Stylus existe il est lu et celui-ci génère le CSS. Une fois l'opération effectuée, on renvoi le CSS demandée. Avec la structure suivante : ``` ├─ assets/ │ └─ stylesheets │ └─ common.styl ├─ templates/ │ └─ index.htm └─ webconfig.json ``` ainsi que le webconfig suivante : ```js { "enableStylus": true, "routes": { "/": "index.htm" } } ``` et le contenu suivant dans : *templates/index.htm* ```htmlThis line is red.
``` *assets/stylesheets/common.styl* ```css p color: #f00 ``` vous générerez le fichier `assets/stylesheets/common.css` en appelant l'url `http://localhost/` ou `http://localhost/stylesheets/common.css`. #### Source Map et Minification #### Par défaut, dans l'exemple ci-dessus un fichier `common.css.map` sera généré. Celui-ci permet à votre navigateur de vous indiquer qu'elle ligne du fichier `.styl` a générée la propriété CSS de l'élément que vous avez sélectionné dans votre débuggeur. Cela se désactive avec `enableStylus.sourceMap` à `false` : ``` "enableStylus": { "sourceMap": false }, "routes": { "/": "index.htm" } ``` Vous pouvez également générer des fichiers CSS déjà minifiés avec : ``` "enableStylus": { "compress": true }, "routes": { "/": "index.htm" } ``` *Note:* Plus d'options sur [la documentation du module stylus](https://www.npmjs.com/package/stylus). #### Compiler les Stylus avec `--generate` #### Comme les Stylus sont compilés a la volé, quand le fichier est demandé en http(s), toutes modifications dans le Stylus demandera de faire tourner le site pour la répercuter dans le CSS. Ensuite seulement vous pourrez minifier vos CSS. Il est possible d'automatiser cette tâche pour ne pas avoir à démarrer le site grâce à `enableStylus.stylus`. Avec le `webconfig.json` suivant : ```js { "enableStylus": { "stylus": [ "stylesheets/common.styl", "stylesheets/component-1.styl", "stylesheets/component-2.styl", "stylesheets/component-3.styl" ] }, "routes": { "/": "index.htm" } } ``` ou suivante : ```js { "enableLess": { "stylus": "stylus.json" }, "routes": { "/": "index.htm" } } ``` avec `stylus.json` qui contient : ```js [ "stylesheets/common.styl", "stylesheets/component-1.styl", "stylesheets/component-2.styl", "stylesheets/component-3.styl" ] ``` Par défaut, les `@import` utilisés par Less seront capable de fouiller dans les sous dossier : `styles`, `stylesheets` ou `css`. Il est possible de changer cela avec : ```js { "enableStylus": { "paths": [ "subdirectory/styles-files", ], "stylus": "stylus.json" }, "routes": { "/": "index.htm" } } ``` ### Optimiser les Images ### Vous pouvez automatiquement optimiser les images que vous allez utiliser dans votre site pour en limiter le poids de chargement en créant des Optimizations en référençant les fichiers d'entrés par leur chemin d'accès et le chemin du dossier de sortie. Vous pouvez bien entendu en faire autant que vous le souhaitez. L'optimisation des images se fait à chaque démarrage de NodeAtlas que ce soit en tant que serveur ou via la commande `--generate` pour peu que des Optimizations existe dans le Webconfig. #### Créer des Optimizations #### Avec la configuration suivante : ```js { "optimizations": { "images": { "media/images/example.png": "media/images/optimized/", "media/images/example.jpg": "media/images/optimized/", "media/images/example.gif": "media/images/optimized/", "media/images/example.svg": "media/images/optimized/" } }, "routes": { "/": { "template": "index.htm" } } } ``` et l'ensemble de fichier suivant : ``` ├─ assets/ │ └─ media/ │ └─ images/ │ ├─ example.png │ ├─ example.jpg │ ├─ example.gif │ └─ example.svg ├─ templates/ │ └─ index.htm └─ webconfig.json ``` vous obtiendrez les nouveaux fichiers suivant : ``` ├─ assets/ │ └─ media/ │ └─ images/ │ ├─ example.png │ ├─ example.jpg │ ├─ example.gif │ ├─ example.svg │ └─ optimized/ ⤆ nouveau dossier │ ├─ example.png ⤆ nouveau fichier │ ├─ example.jpg ⤆ nouveau fichier │ ├─ example.gif ⤆ nouveau fichier │ └─ example.svg ⤆ nouveau fichier ├─ templates/ │ └─ index.htm └─ webconfig.json ``` #### Créer des Optimizations par groupes de fichier #### Vous pouvez par exemple, plutôt que d'indiquer les fichiers un par un, les indiquer en groupe : ```js { "optimizations": { "images": { "media/images/*.{gif,jpg,png,svg}": "media/images/optimized/" } }, "routes": { "/": { "template": "index.htm" } } } ``` #### Ajouter des options aux Optimizations #### Il est possible de redéfinir les options par défaut pour l'optimisation via ses 4 objets : ```js { "optimizations": { "jpg": { "progressive": false }, "gif": { "interlaced": false }, "png": { "optimizationLevel": 1 }, "svg": { "multipass": false }, "images": { "media/images/*.{gif,jpg,png,svg}": "media/images/optimized/" } }, "routes": { "/": { "template": "index.htm" } } } ``` Pour connaître toutes les options c'est par ici : - [Options Jpeg](https://www.npmjs.com/package/imagemin-jpegtran) - [Options Gif](https://www.npmjs.com/package/imagemin-gifsicle) - [Options Png](https://www.npmjs.com/package/imagemin-optipng) - [Options Svg](https://www.npmjs.com/package/imagemin-svgo) #### Optimizations dans un fichier partagé #### Afin de ne pas réécrire une longue liste de configuration d'Optimizations dans un fichier `webconfig.json` à destination de votre environnement de développement et `webconfig.prod.json` à destination de votre environnement de production, vous pouvez mutualiser la déclaration des fichiers dans un fichier de votre choix. Par convention, c'est le fichier `optimizations.json`. Par exemple : L'ensemble de fichier suivant ``` ├─ assets/ │ └─ media/ │ └─ images/ │ ├─ example.png │ ├─ example.jpg │ ├─ example.gif │ └─ example.svg ├─ templates/ │ └─ index.htm ├─ webconfig.json └─ webconfig.prod.json ``` avec `webconfig.json` ```json { "httpPort": 7777, "optimizations": { "images": { "media/images/example.png": "media/images/optimized/", "media/images/example.jpg": "media/images/optimized/", "media/images/example.gif": "media/images/optimized/", "media/images/example.svg": "media/images/optimized/" } }, "routes": { "/": { "template": "index.htm" } } } ``` et avec `webconfig.prod.json` ```json { "httpPort": 7776, "httpHostname": "blog.lesieur.name", "urlPort": 80, "optimizations": { "images": { "media/images/example.png": "media/images/optimized/", "media/images/example.jpg": "media/images/optimized/", "media/images/example.gif": "media/images/optimized/", "media/images/example.svg": "media/images/optimized/" } }, "routes": { "/": { "template": "index.htm" } } } ``` pourrait devenir l'ensemble de fichier suivant ``` ├─ assets/ │ └─ media/ │ └─ images/ │ ├─ example.png │ ├─ example.jpg │ ├─ example.gif │ └─ example.svg ├─ templates/ │ └─ index.htm ├─ bundles.json ├─ webconfig.json └─ webconfig.prod.json ``` avec `webconfig.json` ```json { "httpPort": 7777, "optimizations": "optimizations.json", "routes": { "/": { "template": "index.htm" } } } ``` avec `webconfig.prod.json` ```json { "httpPort": 7776, "httpHostname": "blog.lesieur.name", "urlPort": 80, "optimizations": "optimizations.json", "routes": { "/": { "template": "index.htm" } } } ``` et `optimizations.json` ```json { "images": { "media/images/example.png": "media/images/optimized/", "media/images/example.jpg": "media/images/optimized/", "media/images/example.gif": "media/images/optimized/", "media/images/example.svg": "media/images/optimized/" } } ``` *Note : il est possible de désactiver les Optimizations en ne les incluant pas dans le `webconfig` en question.* #### Désactiver des Optimizations #### Il est également possible de ne pas exécuter l'optimisation au démarrage d'un site web avec NodeAtlas avec les propriétés `"imagesOptimizationsEnable": false`. ```js { "imagesOptimizationsEnable": false, "optimizations": { "images": { "media/images/example.png": "media/images/optimized/", "media/images/example.jpg": "media/images/optimized/", "media/images/example.gif": "media/images/optimized/", "media/images/example.svg": "media/images/optimized/" } }, "routes": { "/": { "template": "index.htm" } } } ``` *Note : si vos optimizations sont dans un fichier partagé, vous pouvez également les désactiver simplement en retirant la ligne `"optimizations": "optimizations.json"`.* #### Ré-générer les Optimizations avant chaque rendu de page #### Vous pouvez demander à ce que les fichiers soient régénérés avant chaque affichage de page avec les propriétés `"imagesOptimizationsBeforeResponse": true`. ```js { "imagesOptimizationsBeforeResponse": false, "optimizations": { "images": { "media/images/example.png": "media/images/optimized/", "media/images/example.jpg": "media/images/optimized/", "media/images/example.gif": "media/images/optimized/", "media/images/example.svg": "media/images/optimized/" } }, "routes": { "/": { "template": "index.htm" } } } ``` *Note : ceci n'est pas conseillé en production car cela ralenti les réponses des pages.* ### Injecter du CSS inline pour maintenir des assets Email ### Quand on créer des templates pour envoyer des Newsletters par email, ou même de simple message, on ne peut pas attacher de feuille de style. Le seul moyen à notre disposition est d'écrire les instructions CSS dans le template à l'intérieur de l'attribut `style` brisant ainsi la séparation du font et de la forme. #### Injection spécifique #### Avec `injectCss`, il vous suffit d'habiller votre template comme à votre habitude via une feuille de style et NodeAtlas injectera à chaque rendu les styles dans l'attribut `style`. Il ne vous restera plus qu'à générer vos templates. Avec par exemple la configuration suivante : ```json { "routes": { "/": { "template": "email.htm", "generate": "bienvenue.html", "injectCss": "stylesheets/email.css" } } } ``` et l'ensemble de fichiers suivant : ``` ├─ generates/ ├─ assets/ │ └─ stylesheets/ │ └─ email.css ├─ templates/ │ └─ email.htm └─ webconfig.json ``` dont les contenus sont : **stylesheets/common.css** ```css body { color: #f00; } ``` **templates/email.htm*** ```htmlThis is a template email.
``` vous obtiendrez en sortie avec la commande `node node-atlas/ --generate` l'ensemble de fichier suivant : ``` ├─ generates/ │ └─ bienvenue.html <= template email prêt à l'envoi ! ├─ assets/ │ └─ stylesheets/ │ └─ email.css ├─ templates/ │ └─ email.htm └─ webconfig.json ``` avec comme contenu pour `generates/bienvenue.html` ```htmlThis is a template email.
``` Ce mécanisme marche également si vous n'avez pas l'intention de générer quoi que ce soit mais sur un site qui tourne. Pratique pour modifier vos maquettes en live avant de les générer. > Test : Depuis `./tests/examples/css-injection` lancez `node "../../../" --generate`. Le résultat est dans `generates`. #### Injection globale #### Il existe également la même propriété globale impactant toutes les pages. ```json { "injectCss": "stylesheets/email.css", "routes": { "/bienvenue/": { "template": "email-a.htm", "generate": "bienvenue.html" }, "/au-revoir/": { "template": "email-b.htm", "generate": "au-revoir.html" } } } ``` ainsi les deux pages `bienvenue` et `au-revoir` contiendront chacune ``. #### Injection multiple #### Il est possible : - De préciser des feuilles spécifique et commune en même temps. - De préciser plus d'une feuille à la fois. ```json { "injectCss": ["stylesheets/reset.css", "stylesheets/email.css"], "routes": { "/bienvenue/": { "template": "email-a.htm", "generate": "bienvenue.html", "injectCss": "/stylesheets/welcome.css" }, "/au-revoir/": { "template": "email-b.htm", "generate": "au-revoir.html", "injectCss": ["stylesheets/good-bye.css", "/stylesheets/others.css"] } } } ``` > Test : Depuis `./tests/examples/css-injection` lancez `node "../../../" --generate --webconfig webconfig.multiple.json`. Le résultat est dans `generates`. ### Autoriser / Interdire les demandes GET / POST ### Vous pouvez également manager la manière dont le serveur va répondre aux demandes GET/POST pour une page donnée. Par exemple, nous allons autoriser l'accès aux pages uniquement en GET pour tout le site et autoriser un POST pour une page seulement (et même lui interdire le GET). ```js { "getSupport": true, "postSupport": false, "routes": { "/": { "template": "index.htm" }, "/liste-des-membres/": { "template": "members.htm" }, "/rediger-commentaire/": { "template": "write-com.htm" }, "/commentaire-sauvegarde/": { "template": "save-com.htm", "getSupport": false, "postSupport": true } } } ``` *Note : Si rien n'est précisé,* ***getSupport*** *et* ***postSupport*** *sont à* ***true*** *au niveau global et par page.* ### Autoriser / Interdire les demandes PUT / DELETE ### Fonctionnant exactement de la même manière que `getSupport` et `postSupport`, les deux actions HTTP PUT et DELETE qui part défaut ne sont pas activé peuvent être activé avec `putSupport` et `deleteSupport`. ```js { "getSupport": false, "postSupport": false, "putSupport": true, "routes": { "/read-all-entry/": { "template": "display-json.htm", "variation": "all-entry.json", "getSupport": true, "putSupport": false }, "/read-entry/:id/": { "template": "display-json.htm", "variation": "entry.json", "getSupport": true, "putSupport": false }, "/create-entry/:id/": { "template": "display-json.htm", "variation": "entry.json", "postSupport": true, "putSupport": false }, "/update-entry/:id/": { "template": "display-json.htm", "variation": "entry.json" }, "/delete-entry/:id/": { "template": "display-json.htm", "variation": "entry.json", "deleteSupport": true, "putSupport": false } } } ``` Avec la configuration ci-dessus, seulement une action HTTP n'est possible par entrée, cela permet de faire des APIs REST facilement avec NodeAtlas. ### Changer les paramètres des Sessions ### #### Clé et Secret #### NodeAtlas gère lui-même les sessions stockées sur le serveur avec comme paramètres initiaux : - Key : `nodeatlas.sid` - Secret : `1234567890bépo` qui permettent à un client de rester connecté à travers les pages à un même ensemble de variable personnelles côtés serveur. Il est possible de modifier ses paramètres par défaut (et même obligatoire pour des sites en productions) avec les paramètres de `webconfig.json` suivant : ```js { sessionKey: "clé personnelle", sessionSecret: "secret personnel" } ``` NodeAtlas utilise également un objet de stockage mémoire (MemoryStore) qui stocke les informations dans la RAM du serveur. #### Autres paramètres #### Il est possible de changer l'intégralité des paramètres des sessions (sauf le MemoryStore) en utilisant la configuration de `webconfig.json` suivante : ```js { "session": { "key": "clé personnelle", "secret": "secret personnel", "cookie": { "path": '/', "httpOnly": true, "secure": false, "maxAge": null }, ..., ..., ... } } ``` L'intégralité de la configuration possible se trouve sur la documentation du module [express-session](https://github.com/expressjs/session). ### Stockage externe des Sessions ### Par défaut, c'est NodeAtlas qui stocke les sessions serveurs dans la RAM du serveur par application. Cela ne permet pas de partager des sessions utilisateurs à travers plusieurs applications NodeAtlas (ou autre) et efface toutes les sessions en cours pour une application en cas de redémarrage de celle-ci. Pour résoudre ce souci, il convient de prendre en charge l'enregistrement des sessions via une base No SQL tel que `Redis` ou `MongoBD`. Pour cela il suffit d'utiliser la fonction `setSessions` dans le fichier `controllers/common.js` de la [partie Back-end](#partie-controller-et-model). #### Session gérées avec Redis #### Implémenter le code suivant dans `controllers/common.js` pour stocker vos sessions dans Redis en local. ``` var website = {}; (function (publics) { "use strict"; publics.loadModules = function () { var NA = this; NA.modules.RedisStore = require('connect-redis'); }; publics.setSessions = function (next) { var NA = this, session = NA.modules.session, RedisStore = NA.modules.RedisStore(session); NA.sessionStore = new RedisStore(); next(); }; }(website)); exports.loadModules = website.loadModules; exports.setSessions = website.setSessions; ``` Plus d'informations sur [connect-redis](https://www.npmjs.org/package/connect-redis). #### Session gérées avec MongoDB #### Implémenter le code suivant dans `controllers/common.js` pour stocker vos sessions dans la database `sessions` d'une MongoDB locale. ``` var website = {}; (function (publics) { "use strict"; publics.loadModules = function () { var NA = this; NA.modules.MongoStore = require('connect-mongo'); }; publics.setSessions = function (next) { var NA = this, session = NA.modules.session, MongoStore = NA.modules.MongoStore(session); NA.sessionStore = new MongoStore({ db: 'sessions' }); next(); }; }(website)); exports.loadModules = website.loadModules; exports.setSessions = website.setSessions; ``` Plus d'informations sur [connect-redis](https://www.npmjs.org/package/connect-mongo). ### Changer les chevrons <% %> du moteur de template ### Par exemple, pour inclure une partie de fichier on utilise l'instruction ***<%- include('head.htm') %>***. Il serait possible de le faire avec ***- include('head.htm') ?>*** avec la configuration ci-dessous : ```js { "templateEngineDelimiter": "?", "routes": { "/": { "template": "index.htm" } } } ``` Voyez l'exemple dans les fichiers ci-dessous : *components/head.htm* ```html