Node Atlas NodeAtlas

Le Framework JavaScript Serveur Évolutif

  • Mise en place Simple

    Au point avec HTML & CSS ?
    Débutant en JavaScript ?

    Réalisez rapidement des sites vitrines multilingues sans effort avec l'utilisation de routes, vues ou variations.

  • Site vivant et Évolutif

    Expert en JavaScript client ?
    Prêt à embrasser Node.js ?

    Améliorer progressivement votre base à mesure de vos besoins en utilisant des contrôleurs, modèles ou modules.

  • Partie Cliente Agnostique

    Déjà vos habitudes Front-end ?
    Habitué(e) du Data Binding ?

    Du léger Vanilla au simple jQuery en passant par Vue, Angular ou React : utiliser vos bibliothèques clientes favorites !

Pour aller plus loin

NodeAtlas offre également tout un système de fonctionnalités de développement et de packaging à travers son sytème de configuration. Voyons cela.

Gérer le routage (Url Rewriting)

Bien que vous puissiez paramétrer des URLs statiques, vous pouvez également paramétrer une écoute d'URLs dynamiques !

Paramètres

Il est possible de récupérer des paramètres de l'URL pour afficher un contenu différent en fonctions de leurs contenus.

Avec la configuration suivante :

{
    "routes": {
        "/liste-des-membres/:member/:action/": {
            "view": "members.htm",
            "controller": "members.js"
        },
        "/liste-des-membres/:member/:action": {
            "view": "members.htm",
            "controller": "members.js"
        },
        "/liste-des-membres/:member/": {
            "view": "members.htm",
            "controller": "members.js"
        },
        "/liste-des-membres/:member": {
            "view": "members.htm",
            "controller": "members.js"
        },
        "/liste-des-membres/": {
            "view": "members.htm",
            "controller": "members.js"
        },
        "/liste-des-membres": {
            "view": "members.htm",
            "controller": "members.js"
        },
        "/": {
            "view": "index.htm"
        }
    }
}

vous pourrez accéder à :

et récupérer les valeurs de :member, :action, example et test dans le changeVariations (common et specific).

exports.changeVariations = function (next, locals, request, response) {

    console.log("param request:", request.params.member);
    // $ undefined, 'toto', 'bob-eponge99', 'node-atlas' or 'etc'.
    console.log("param locals:", locals.params.member);
    // $ undefined, 'toto', 'bob-eponge99', 'node-atlas' or 'etc'.

    console.log("param request", request.params.action);
    // $ undefined, 'show' or 'lolol'.
    console.log("param locals", locals.params.action);
    // $ undefined, 'show' or 'lolol'.

    console.log("query request", request.query.example);
    // $ undefined or 'test'
    console.log("query locals", locals.query.example);
    // $ undefined or 'test'

    console.log("body request", request.body.test);
    // $ undefined or 'This is a test'.
    console.log("body locals", locals.body.test);
    // $ undefined or 'This is a test'.

    next();
};

Paramètres avancés

Nous voyons que nous utilisons une configuration identique pour trois routes dans l'exemple précédent. Vous pouvez également vous aider d'expressions régulières pour définir ce qui peut varier pour accéder à votre URL ou préciser quels sont les paramètres valides dans l'URL. Ce sytème est simplifié étant donné que beaucoup de caractère ne se trouve pas dans l'url. Il n'est donc pas nécéssaire par exemple d'échaper les / par exemple.

Avec la configuration suivante :

{
    "routes": {
        "/liste-des-membres/?(:member([-a-zA-Z0-9]+)/?(:action(afficher|editer)/?)?)?": {
            "view": "members.htm"
        },
        "/": {
            "view": "index.htm"
        }
    }
}

vous pourrez accéder à :

et récupérer les valeurs de :member, :action, example et test dans une vue.

<!DOCTYPE html>
<html lang="fr-fr">
  <head>
    <meta charset="utf-8">
    <title>URL Rewriting Test</title>
  </head>
  <body>
    Member: <strong><?- params.member ?></strong><br>
    Action: <strong><?- params.action ?></strong><br>
    Example: <strong><?- query.example ?></strong><br>
    Test: <strong><?- body.test ?></strong>
  </body>
</html>

vous ne pourrez pas accéder à :

Expressions Régulières

Vous pouvez également activer les expressions régulières pour un chemin précis avec regExp. Si celui-ci vaut true, le précédent mode ne fonctionne plus et vous passez en mode Expression Régulière. Si regExp est une chaine de caractère, celle-ci fait office de flag (g, i, m ou y).

Voyez la configuration suivante :

{
    "routes": {
        "/liste-des-membres/([-a-z0-9]+)/?": {
            "view": "members.htm",
            "regExp": "i"
        },
        "/liste-des-membres/?": {
            "view": "members.htm",
            "regExp": true
        },
        "/": {
            "view": "index.htm"
        }
    }
}

vous pourrez accéder à :

et récupérer les valeurs de ([-a-z0-9]+) dans le changeVariations (common et specific).

exports.changeVariation = function (next, locals) {

    if (locals.params && locals.params[0]) { locals.params.member = locals.params[0]; }
    // locals.params[1] pour le deuxième match, etc...

    console.log(locals.params.member);
    // $ 'toto', 'bob-eponge99', 'node-atlas' ou 'etc'.

    next();
}

Les règles de création d'url dynamique avec regExp sont celles des RegExp JavaScript.

Gérer les pages inexistantes

Écouter toutes les urls, même les adresses du dossier assetsRelativePath

Pour afficher une page personnalisée quand une ressource n'est pas trouvée il faut :

  1. Préparer une page 404.
  2. Remplir le paramètre pageNotFound avec comme value la key de la page 404 préparée.

Voyez l'exemple ci-dessous :

{
    "pageNotFound": "/pages-inexistantes/",
    "routes": {
        "/liste-des-membres/": {
            "view": "members.htm"
        },
        "/": {
            "view": "index.htm"
        },
        "/pages-inexistantes/": {
            "view": "error.htm",
            "statusCode": 404
        }
    }
}

vous pourrez accéder à :

Page d'erreur localisée

Il vous suffit de créer une nouvelle route finissant par * dans la langue souhaitée.

Voyez l'exemple ci-dessous :

{
    "pageNotFound": "/pages-inexistantes/",
    "languageCode": "fr-fr",
    "routes": {
        "/liste-des-membres/": {
            "view": "members.htm",
            "variation": "members.json"
        },
        "/": {
            "view": "index.htm",
            "variation": "index.json"
        },
        "/pages-inexistantes/": {
            "view": "error.htm",
            "variation": "error.json",
            "statusCode": 404
        },
        "/english/list-of-members/": {
            "view": "members.htm",
            "languageCode": "en-gb",
            "variation": "members.json"
        },
        "/english/": {
            "view": "index.htm",
            "languageCode": "en-gb",
            "variation": "index.json"
        },
        "/english/*": {
            "view": "error.htm",
            "languageCode": "en-gb",
            "variation": "error.json",
            "statusCode": 404
        }
    }
}

Injecter des routes dynamiquement

Nous avons pu voir qu'avec setRoutes il était possible d'injecter dynamiquement des routes. Cependant, l'injection de route ne se fait qu'à la fin car NA.webconfig.routes est un objet. Il n'y a donc pas de moyen d'ordonner les routes, ce qui est génant car les routes sont résolu dans l'ordre dans lesquels elles ont été injectées.

Nous allons résoudre ça en changeant la manière de créer les routes de routes: { <key>: { ... } } à routes: [{ "key": <key>, ... }].

Voici l'ensemble de fichier suivant :

├─ controllers/
│  └─ common.js
├─ views/
│  ├─ index.htm
│  ├─ content.htm
│  └─ error.htm
└─ webconfig.json

Avec le webconfig.json initialement comme ceci avec routes: <Object> :

{
    "controller": "common.js",
    "routes": {
        "/doc/index.html": {
            "view": "index.htm"
        },
        "/doc/*": {
            "view": "error.htm",
            "statusCode": 404
        }
    }
}

se transformant en cela avec routes: <Array> :

{
    "controller": "common.js",
    "routes": [{
        "url": "/doc/index.html
        "view": "index.htm"
    }, {
        "url": "/doc/*",
        "view": "error.htm",
        "statusCode": 404
    }]
}

Avec le fichier « common.js » nous pouvons maintenant injecter les routes à des positions précise. Nous allons les ajoutés au début.

// On intervient au niveau des routes pendant qu'elles sont ajoutées.
// Ce code sera exécuté au lancement de NodeAtlas.
exports.setRoutes = function (next) {

    // On récupère l'instance de NodeAtlas en cours.
    var NA = this,

        // Et nous récupérons les routes en provenance du webconfig...
        route = NA.webconfig.routes;

    // ...pour ajouter la route "/content.html" au débuts de nos routes.
    route.unshift({
        "url": "/doc/content.html",
        "view": "content.htm"
    });

    // On redonne la main à NodeAtlas pour la suite.
    next();
};

De cette manière l'adresse http://localhost/doc/content.html renverra la vue content.htm et non la vue error.htm en 404.

Gérer les redirections

Pour aller à une autre adresse (redirection 301 ou 302) quand vous arrivez à une url il faut utiliser le paramètre redirect.

Note : si vous ne précisez pas un statusCode, la redirection ne se fera pas. Le statusCode est obligatoire.

En statique

Voyez l'exemple ci-dessous :

{
    "routes": {
        "/liste-des-membres/": {
            "view": "members.htm"
        },
        "/liste-des-membres": {
            "redirect": "/liste-des-membres/",
            "statusCode": 301
        },
        "/aller-sur-node-atlas/": {
            "redirect": "https://node-atlas.js.org/",
            "statusCode": 302
        },
        "/": {
            "view": "index.htm"
        }
    }
}

Vous serez redirigé :

  • sur http://localhost/liste-des-membres/ quand vous accéderez à http://localhost/liste-des-membres avec une entête redirection permanente.
  • sur https://node-atlas.js.org/ quand vous accéderez à http://localhost/aller-sur-node-atlas/ avec une entête redirection temporaire.

En dynamique

Voyez l'exemple ci-dessous :

{
    "routes": {
        "/liste-des-membres/:member/": {
            "view": "members.htm"
        },
        "/liste-des-membres/:member": {
            "redirect": "/liste-des-membres/:member/",
            "statusCode": 301
        },
        "/": {
            "view": "index.htm"
        }
    }
}

Vous serez redirigé sur http://localhost/liste-des-membres/haeresis/ quand vous accéderez à http://localhost/liste-des-membres/haeresis avec une entête redirection permanente.

Avec expressions régulières

Voyez l'exemple ci-dessous :

{
    "routes": {
        "/membres/([-a-z0-9]+)/": {
            "view": "members.htm",
            "regExp": true
        },
        "/liste-des-membres/([-a-z0-9]+)/": {
            "redirect": "/membres/$0/",
            "statusCode": 301,
            "regExp": true
        },
        "/liste-des-membres/": {
            "view": "members.htm"
        },
        "/": {
            "view": "index.htm"
        }
    }
}

Vous serez redirigé sur http://localhost/membres/haeresis/ quand vous accéderez à http://localhost/liste-des-membres/haeresis/ avec une entête redirection permanente.

Pour le second match utilisez $1, pour le troisième $2, etc.

Gérer les Headers de page

Par défaut, les Headers envoyé par NodeAtlas sont les suivants : Content-Type:text/html; charset=utf-8 avec un statusCode à 200.

Il est tout à fait possible de modifier ses valeurs pour une entrée de route pour des APIs local au site.

{
    "mimeType": "application/json",
    "charset": "utf-16",
    "routes": {
        "/": {
            "view": "index.htm",
            "mimeType": "text/html"
        },
        "/api/articles": {
            "view": "display-json.htm",
            "controller": "blog/list-of-articles.js",
            "charset": "utf-8",
            "statusCode": 203
        }
    }
}

Il est également possible de modifier complètement les Headers, ce qui écrase toutes les autres valeurs de headers (à l'exception du statusCode donc). Mettre une valeur à false retire le Headers précédemment mis en place.

{
    "headers": {
        "Content-Type": "application/json; charset=utf-8",
        "Access-Control-Allow-Origin": "*"
    },
    "routes": {
        "/api/articles": {
            "view": "display-json.htm",
            "controller": "blog/list-of-articles.js",
            "statusCode": 203,
            "headers": {
                "Access-Control-Allow-Origin": false
            }
        }
    }
}

Configuration dynamique

Plutôt que d'utiliser plusieurs configurations statique .json, il est tout a fait possible d'utiliser des configurations dynamiques .js. Dans ce cas, ce que votre fichier .js devra retourner avec module.exports sera un objet JSON valide.

Nous pouvons ainsi aisément remplacer les six fichiers suivants :

webconfig.json

{
    "languageCode": "fr-fr",
    "statics": "statics.fr-fr.json"
    "routes": {
        "/": "index.htm"
    }
}

webconfig.prod.json

{
    "cache": true,
    "languageCode": "fr-fr",
    "statics": "statics.fr-fr.json"
    "routes": {
        "/": "index.htm"
    }
}

webconfig.en-us.json

{
    "languageCode": "fr-fr",
    "statics": "statics.fr-fr.json"
    "routes": {
        "/": "index.htm"
    }
}

webconfig.en-us.prod.json

{
    "cache": true,
    "languageCode": "en-us",
    "statics": "statics.en-us.json"
    "routes": {
        "/": "index.htm"
    }
}

statics.fr-fr.json

{
    "/variations/": "variations/fr-fr/",
}

statics.en-us.json

{
    "/variations/": "variations/en-us/",
}

par les deux fichiers suivants :

webconfig.json

module.export = (function () {
    var webconfig = {
        "cache": false,
        "languageCode": "fr-fr",
        "statics": "statics.json"
        "routes": {
            "/": "index.htm"
        }
    };

    if (process.env.NODE_ENV === 'production') {
        webconfig["cache"] = true;
    }

    if (process.env.LANG) {
        webconfig["languageCode"] = process.env.LANG;
    }

    return webconfig;
}());

statics.json

module.export = (function () {
    var NA = this.NA,
        languageCode = NA.webconfig.languageCode

    return {
        "/variations/": "variations/" + languageCode + "/",
    };
}());

en supposant les variables d'environnements suivantes pour les quatre environnements suivants :

Local FR

NODE_ENV=DEVELOPMENT
LANG=fr-fr

Local EN

NODE_ENV=DEVELOPMENT
LANG=en-us

Prod FR

NODE_ENV=PRODUCTION
LANG=fr-fr

Prod EN

NODE_ENV=PRODUCTION
LANG=en-us

Faire tourner le site en HTTPs

Il est très simple de faire tourner une instance de NodeAtlas avec le protocol HTTPs. Pour cela il suffit de créer, par exemple un dossier security dans lequel vous allez placer vos fichiers server.key et server.crt afin d'alimenter le protocol.

Il ne vous reste plus qu'à utiliser la configuration suivante :

{
    "httpSecure": true,
    "httpSecureKeyRelativePath": "security/server.key",
    "httpSecureCertificateRelativePath": "security/server.crt",
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

Vous pouvez également, si —comme c'est le cas ici— vos deux fichiers Key et Certificate portent le même nom, utiliser cette configuration :

{
    "httpSecure": "security/server",
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

Pour finir, il est également possible de seulement laisser la valeur de httpSecure à true pour obtenir un https dans vos chemins comme urlBasePath ou urlBase. Cependant le serveur ce lancera en HTTP, il vous faudra un proxy qui gère pour vous la lecture du certificat.

{
    "httpSecure": true,
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

Note : en production, si vous redirigez un proxy vers votre instance de NodeAtlas, n'oubliez pas qu'en HTTPs ce n'est pas urlPort: 80 mais urlPort: 443

Minifier les CSS / JS

Vous pouvez automatiquement générer des fichiers CSS et JS minifiés et offusqués en créant des Bundles en référençant les groupes de fichiers d'entré par leur chemin d'accès et le chemin du fichier de sortie. Vous pouvez bien entendu en faire autant que vous le souhaitez. La génération des fichiers se fait à chaque démarrage de NodeAtlas que ce soit en tant que serveur ou via la commande --generate pour peu qu'un Bundle existe dans le webconfig.

Créer des Bundles

Avec la configuration suivante :

{
    "bundles": {
        "javascripts": {
            "javascripts/boot.min.js": [
                "javascripts/modernizr.js",
                "javascripts/yepnot.js",
                "javascripts/html5Shiv.js"
            ],
            "javascripts/framework.min.js": [
                "javascripts/jquery.js",
                "javascripts/jquery-ui.js",
                "javascripts/prettify.js",
                "javascripts/prettify/run_prettify.js"
            ],
            "javascripts/common.min.js": [
                "javascripts/components/extended-format-date.js",
                "javascripts/common.js"
            ]
        },
        "stylesheets": {
            "stylesheets/common.min.css": [
                "stylesheets/common.css",
                "stylesheets/common-min780.css",
                "stylesheets/common-min1160.css"
            ]
        }
    },
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

et l'ensemble de fichier suivant :

├─ assets/
│  ├─ stylesheets/
│  │  ├─ common.css
│  │  ├─ common-min780.css
│  │  └─ common-min1160.css
│  └─ javascripts/
│     ├─ modernizr.js
│     ├─ yepnot.js
│     ├─ html5Shiv.js
│     ├─ jquery.js
│     ├─ jquery-ui.js
│     ├─ prettify.js
│     ├─ prettify/
│     │  └─ run_prettify.js
│     ├─ components/
│     │  └─ extended-format-date.js
│     └─ common.js
├─ views/
│  └─ index.htm
└─ webconfig.json

vous obtiendrez les nouveaux fichiers suivant :

├─ assets/
│  ├─ stylesheets/
│  │  ├─ common.css
│  │  ├─ common-min780.css
│  │  ├─ common-min1160.css
│  │  └─ common.min.css     ⤆ nouveau fichier
│  └─ javascripts/
│     ├─ modernizr.js
│     ├─ yepnot.js
│     ├─ html5Shiv.js
│     ├─ jquery.js
│     ├─ jquery-ui.js
│     ├─ prettify.js
│     ├─ prettify/
│     │  └─ run_prettify.js
│     ├─ components/
│     │  └─ extended-format-date.js
│     ├─ common.js
│     ├─ boot.min.js        ⤆ nouveau fichier
│     ├─ framework.min.js   ⤆ nouveau fichier
│     └─ common.min.js      ⤆ nouveau fichier
├─ views/
│  └─ index.htm
└─ webconfig.json

Bundles dans un fichier partagé

Afin de ne pas réécrire une longue liste de configuration de Bundles 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 bundles.json.

Par exemple :

L'ensemble de fichier suivant

├─ assets/
│  ├─ stylesheets/
│  │  ├─ common.css
│  │  ├─ common-min780.css
│  │  └─ common-min1160.css
│  └─ javascripts/
│     ├─ modernizr.js
│     ├─ yepnot.js
│     ├─ html5Shiv.js
│     ├─ jquery.js
│     ├─ jquery-ui.js
│     ├─ prettify.js
│     ├─ prettify/
│     │  └─ run_prettify.js
│     ├─ components/
│     │  └─ extended-format-date.js
│     └─ common.js
├─ views/
│  └─ index.htm
├─ webconfig.json
└─ webconfig.prod.json

avec webconfig.json

{
    "httpPort": 7777,
    "bundles": {
        "javascripts": {
            "javascripts/boot.min.js": [
                "javascripts/modernizr.js",
                "javascripts/yepnot.js",
                "javascripts/html5Shiv.js"
            ],
            "javascripts/framework.min.js": [
                "javascripts/jquery.js",
                "javascripts/jquery-ui.js",
                "javascripts/prettify.js",
                "javascripts/prettify/run_prettify.js"
            ],
            "javascripts/common.min.js": [
                "javascripts/components/extended-format-date.js",
                "javascripts/common.js"
            ]
        },
        "stylesheets": {
            "stylesheets/common.min.css": [
                "stylesheets/common.css",
                "stylesheets/common-min780.css",
                "stylesheets/common-min1160.css"
            ]
        }
    },
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

et avec webconfig.prod.json

{
    "httpPort": 7776,
    "httpHostname": "blog.lesieur.name",
    "urlPort": 80,
    "bundles": {
        "javascripts": {
            "javascripts/boot.min.js": [
                "javascripts/modernizr.js",
                "javascripts/yepnot.js",
                "javascripts/html5Shiv.js"
            ],
            "javascripts/framework.min.js": [
                "javascripts/jquery.js",
                "javascripts/jquery-ui.js",
                "javascripts/prettify.js",
                "javascripts/prettify/run_prettify.js"
            ],
            "javascripts/common.min.js": [
                "javascripts/components/extended-format-date.js",
                "javascripts/common.js"
            ]
        },
        "stylesheets": {
            "stylesheets/common.min.css": [
                "stylesheets/common.css",
                "stylesheets/common-min780.css",
                "stylesheets/common-min1160.css"
            ]
        }
    },
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

pourrait devenir l'ensemble de fichier suivant

├─ assets/
│  ├─ stylesheets/
│  │  ├─ common.css
│  │  ├─ common-min780.css
│  │  └─ common-min1160.css
│  └─ javascripts/
│     ├─ modernizr.js
│     ├─ yepnot.js
│     ├─ html5Shiv.js
│     ├─ jquery.js
│     ├─ jquery-ui.js
│     ├─ prettify.js
│     ├─ prettify/
│     │  └─ run_prettify.js
│     ├─ components/
│     │  └─ extended-format-date.js
│     └─ common.js
├─ views/
│  └─ index.htm
├─ bundles.json              ⤆ nouveau fichier
├─ webconfig.json
└─ webconfig.prod.json

avec webconfig.json

{
    "httpPort": 7777,
    "bundles": "bundles.json",
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

avec webconfig.prod.json

{
    "httpPort": 7776,
    "httpHostname": "blog.lesieur.name",
    "urlPort": 80,
    "bundles": "bundles.json",
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

et bundles.json

{
    "javascripts": {
        "javascripts/boot.min.js": [
            "javascripts/modernizr.js",
            "javascripts/yepnot.js",
            "javascripts/html5Shiv.js"
        ],
        "javascripts/framework.min.js": [
            "javascripts/jquery.js",
            "javascripts/jquery-ui.js",
            "javascripts/prettify.js",
            "javascripts/prettify/run_prettify.js"
        ],
        "javascripts/common.min.js": [
            "javascripts/components/extended-format-date.js",
            "javascripts/common.js"
        ]
    },
    "stylesheets": {
        "stylesheets/common.min.css": [
            "stylesheets/common.css",
            "stylesheets/common-min780.css",
            "stylesheets/common-min1160.css"
        ]
    }
}

Note : il est possible de désactiver les Bundles en ne les incluant pas dans le webconfig en question.

Désactiver des Bundles

Il est également possible de ne pas exécuter la minification au démarrage d'un site web avec NodeAtlas avec les propriétés "cssBundlingEnable": false et "jsBundlingEnable": false pour chaque type de Bundle.

{
    "cssBundlingEnable": false,
    "jsBundlingEnable": false,
    "bundles": {
        "javascripts": {
            "javascripts/boot.min.js": [
                "javascripts/modernizr.js",
                "javascripts/yepnot.js",
                "javascripts/html5Shiv.js"
            ],
            "javascripts/framework.min.js": [
                "javascripts/jquery.js",
                "javascripts/jquery-ui.js",
                "javascripts/prettify.js",
                "javascripts/prettify/run_prettify.js"
            ],
            "javascripts/common.min.js": [
                "javascripts/components/extended-format-date.js",
                "javascripts/common.js"
            ]
        },
        "stylesheets": {
            "stylesheets/common.min.css": [
                "stylesheets/common.css",
                "stylesheets/common-min780.css",
                "stylesheets/common-min1160.css"
            ]
        }
    },
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

Note : si vos bundles sont dans un fichier partagé, vous pouvez également les désactiver simplement en retirand la ligne "bundles": "bundles.json".

Ré-générer les Bundles avant chaque rendu de page

De manière à toujours tester vos page avec les fichiers minifiés, vous pouvez demander à ce qu'ils soient régénérés avant chaque affichage de page avec les propriétés "cssBundlingBeforeResponse": true et "jsBundlingBeforeResponse": true pour chaque type de Bundle.

{
    "cssBundlingBeforeResponse": false,
    "jsBundlingBeforeResponse": false,
    "bundles": {
        "javascripts": {
            "javascripts/boot.min.js": [
                "javascripts/modernizr.js",
                "javascripts/yepnot.js",
                "javascripts/html5Shiv.js"
            ],
            "javascripts/framework.min.js": [
                "javascripts/jquery.js",
                "javascripts/jquery-ui.js",
                "javascripts/prettify.js",
                "javascripts/prettify/run_prettify.js"
            ],
            "javascripts/common.min.js": [
                "javascripts/components/extended-format-date.js",
                "javascripts/common.js"
            ]
        },
        "stylesheets": {
            "stylesheets/common.min.css": [
                "stylesheets/common.css",
                "stylesheets/common-min780.css",
                "stylesheets/common-min1160.css"
            ]
        }
    },
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

Note : ceci n'est pas conseillé en production car cela ralenti les réponses des pages.

Version dans noms de fichiers générés

Afin de forcer le navigateur à charger de nouveau vos fichiers en cache il est intéressant de changer leur nom pour chaque version. Ainsi avec l'occurence {version} sera remplacé par le numéro de version de votre site actuel (par défaut 0.0.0).

Ainsi, si vous avez un fichier package.json ou un webconfig.json valide avec un numéro de version indiqué sous la propriété version, ce numéro remplacera la valeur {version}. Ainsi avec le webconfig suivant :

webconfig

{
    "version": "1.0.0",
    "bundles": "bundles.json"
    "routes": "routes.json"
}

et les bundles suivants :

bundles.json

{
    "javascripts": {
        "javascripts/boot.{version}.min.js": [
            "javascripts/modernizr.js",
            "javascripts/yepnot.js",
            "javascripts/html5Shiv.js"
        ]
    },
    "stylesheets": {
        "stylesheets/common.{version}.min.css": [
            "stylesheets/common.css",
            "stylesheets/common-min780.css",
            "stylesheets/common-min1160.css"
        ]
    }
}

vous obtiendrez les fichiers assets/javascripts/boot.1.0.0.min.js et assets/javascripts/common.1.0.0.min.css.

que vous pourrez appeler ainsi :

views/*.htm

<!-- ... -->

<link rel="stylesheet" href="stylesheets/common.<?= webconfig.version ?>.min.css">

<!-- ... -->

<script src="javascripts/boot.<?= webconfig.version ?>.min.js"></script>

<!-- ... -->

Bundles avec Sockets

Il est possible de minifier le fichier défini par NA.webconfig.socketClientFile même si celui-ci n'existe pas physiquement. Il suffit pour cela de le glisser dans les bundles souhaité.

Dans l'exemple suivant, le fichier virtuel node-atlas/socket.io.js sera ajouté aux sources avec la bonne configuration pour faire le lien client/serveur.

{
    "bundles": {
        "javascripts": {
            "javascripts/common.min.js": [
                "javascripts/socket.io.js",
                "node-atlas/socket.io.js",
                "javascripts/common.js"
            ]
        }
    },
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

Générer les CSS avec Less

Vous pouvez utiliser le préprocesseur Less pour créer vos CSS. Le fonctionnement est le suivant : à chaque fois qu'une requête CSS est effectuée, si un équivalent Less 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.less
├─ views/
│  └─ index.htm
└─ webconfig.json

ainsi que le webconfig suivante :

{
    "less": true,
    "routes": {
        "/": "index.htm"
    }
}

et le contenu suivant dans :

views/index.htm

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Less Test</title>
        <link rel="stylesheet" href="stylesheets/common.css">
    </head>
    <body>
        <p>This line is red.</p>
    </body>
</html>

assets/stylesheets/common.less

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, Minification et Autoprefix

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 less.sourceMap à false :

    "less": {
        "sourceMap": false
    },
    "routes": {
        "/": "index.htm"
    }

Vous pouvez également générer des fichiers CSS déjà minifiés avec :

    "less": {
        "compress": true
    },
    "routes": {
        "/": "index.htm"
    }

Pour finir, vous pouvez également ajouter automatiquement les prefix vendeur comme --webkit, --moz, --ms, --o lors de la génération sans vous en préoccuper dans vos sources !

    "less": {
        "autoprefix": 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 à less.files.

Avec le webconfig.json suivant :

{
    "less": {
        "files": [
            "stylesheets/common.less",
            "stylesheets/component-1.less",
            "stylesheets/component-2.less",
            "stylesheets/component-3.less"
        ]
    },
    "routes": {
        "/": "index.htm"
    }
}

ou suivante :

{
    "less": {
        "files": "less.json"
    },
    "routes": {
        "/": "index.htm"
    }
}

avec less.json qui contient :

[
    "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 :

{
    "less": {
        "paths": [
            "subdirectory/styles-files",
        ],
        "files": "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
├─ views/
│  └─ index.htm
└─ webconfig.json

ainsi que le webconfig suivante :

{
    "stylus": true,
    "routes": {
        "/": "index.htm"
    }
}

et le contenu suivant dans :

views/index.htm

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Stylus Test</title>
        <link rel="stylesheet" href="stylesheets/common.css">
    </head>
    <body>
        <p>This line is red.</p>
    </body>
</html>

assets/stylesheets/common.styl

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, Minification et Autoprefix

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 stylus.sourceMap à false :

    "stylus": {
        "sourceMap": false
    },
    "routes": {
        "/": "index.htm"
    }

Vous pouvez également générer des fichiers CSS déjà minifiés avec :

    "stylus": {
        "compress": true
    },
    "routes": {
        "/": "index.htm"
    }

Pour finir, vous pouvez également ajouter automatiquement les prefix vendeur comme --webkit, --moz, --ms, --o lors de la génération sans vous en préoccuper dans vos sources !

    "stylus": {
        "autoprefix": true
    },
    "routes": {
        "/": "index.htm"
    }

Note: Plus d'options sur la documentation du module 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 à stylus.files.

Avec le webconfig.json suivant :

{
    "stylus": {
        "files": [
            "stylesheets/common.styl",
            "stylesheets/component-1.styl",
            "stylesheets/component-2.styl",
            "stylesheets/component-3.styl"
        ]
    },
    "routes": {
        "/": "index.htm"
    }
}

ou suivante :

{
    "stylus": {
        "files": "stylus.json"
    },
    "routes": {
        "/": "index.htm"
    }
}

avec stylus.json qui contient :

[
    "stylesheets/common.styl",
    "stylesheets/component-1.styl",
    "stylesheets/component-2.styl",
    "stylesheets/component-3.styl"
]

Par défaut, les @import utilisés par Stylus seront capable de fouiller dans les sous dossier : styles, stylesheets ou css. Il est possible de changer cela avec :

{
    "stylus": {
        "paths": [
            "subdirectory/styles-files",
        ],
        "files": "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 :

{
    "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": {
        "/": {
            "view": "index.htm"
        }
    }
}

et l'ensemble de fichier suivant :

├─ assets/
│  └─ media/
│     └─ images/
│        ├─ example.png
│        ├─ example.jpg
│        ├─ example.gif
│        └─ example.svg
├─ views/
│  └─ 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
├─ views/
│  └─ 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 :

{
    "optimizations": {
        "images": {
            "media/images/*.{gif,jpg,png,svg}": "media/images/optimized/"
        }
    },
    "routes": {
        "/": {
            "view": "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 :

{
    "optimizations": {
        "jpg": { "progressive": false },
        "gif": { "interlaced": false },
        "png": { "optimizationLevel": 1 },
        "svg": { "multipass": false },
        "images": {
            "media/images/*.{gif,jpg,png,svg}": "media/images/optimized/"
        }
    },
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

Pour connaître toutes les options c'est par ici :

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
├─ views/
│  └─ index.htm
├─ webconfig.json
└─ webconfig.prod.json

avec webconfig.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": {
        "/": {
            "view": "index.htm"
        }
    }
}

et avec webconfig.prod.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": {
        "/": {
            "view": "index.htm"
        }
    }
}

pourrait devenir l'ensemble de fichier suivant

├─ assets/
│  └─ media/
│     └─ images/
│        ├─ example.png
│        ├─ example.jpg
│        ├─ example.gif
│        └─ example.svg
├─ views/
│  └─ index.htm
├─ bundles.json
├─ webconfig.json
└─ webconfig.prod.json

avec webconfig.json

{
    "httpPort": 7777,
    "optimizations": "optimizations.json",
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

avec webconfig.prod.json

{
    "httpPort": 7776,
    "httpHostname": "blog.lesieur.name",
    "urlPort": 80,
    "optimizations": "optimizations.json",
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

et optimizations.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 "imgOptimizationsEnable": false.

{
    "imgOptimizationsEnable": 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": {
        "/": {
            "view": "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 "imgOptimizationsBeforeResponse": true.

{
    "imgOptimizationsBeforeResponse": 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": {
        "/": {
            "view": "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 :

{
    "routes": {
        "/": {
            "view": "email.htm",
            "output": "bienvenue.html",
            "injectCss": "stylesheets/email.css"
        }
    }
}

et l'ensemble de fichiers suivant :

├─ serverless/
├─ assets/
│  └─ stylesheets/
│     └─ email.css
├─ views/
│  └─ email.htm
└─ webconfig.json

dont les contenus sont :

stylesheets/common.css

body {
    color: #f00;
}

views/email.htm*

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Email</title>
    </head>
    <body>
        <p>This is a template email.</p>
    </body>
</html>

vous obtiendrez en sortie avec la commande node-atlas --generate l'ensemble de fichier suivant :

├─ serverless/
│  └─ bienvenue.html    <= template email prêt à l'envoi !
├─ assets/
│  └─ stylesheets/
│     └─ email.css
├─ views/
│  └─ email.htm
└─ webconfig.json

avec comme contenu pour serverless/bienvenue.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Email</title>
    </head>
    <body style="color: #f00;">
        <p>This is a template email.</p>
    </body>
</html>

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 serverless.

Injection globale

Il existe également la même propriété globale impactant toutes les pages.

{
    "injectCss": "stylesheets/email.css",
    "routes": {
        "/bienvenue/": {
            "view": "email-a.htm",
            "generate": "bienvenue.html"
        },
        "/au-revoir/": {
            "view": "email-b.htm",
            "generate": "au-revoir.html"
        }
    }
}

ainsi les deux pages bienvenue et au-revoir contiendront chacune <body style="color: #f00;">.

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.
{
    "injectCss": ["stylesheets/reset.css", "stylesheets/email.css"],
    "routes": {
        "/bienvenue/": {
            "view": "email-a.htm",
            "generate": "bienvenue.html",
            "injectCss": "/stylesheets/welcome.css"
        },
        "/au-revoir/": {
            "view": "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 serverless.

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).

{
    "get": true,
    "post": false,
    "routes": {
        "/": {
            "view": "index.htm"
        },
        "/liste-des-membres/": {
            "view": "members.htm"
        },
        "/rediger-commentaire/": {
            "view": "write-com.htm"
        },
        "/commentaire-sauvegarde/": {
            "view": "save-com.htm",
            "get": false,
            "post": true
        }
    }
}

Note : Si rien n'est précisé, get et post sont à true au niveau global et par page.

Autoriser / Interdire les demandes PUT / DELETE

Fonctionnant exactement de la même manière que get et post, les deux actions HTTP PUT et DELETE qui part défaut ne sont pas activé peuvent être activé avec put et delete.

{
    "get": false,
    "post": false,
    "put": true,
    "routes": {
        "/read-all-entry/": {
            "view": "display-json.htm",
            "variation": "all-entry.json",
            "get": true,
            "put": false
        },
        "/read-entry/:id/": {
            "view": "display-json.htm",
            "variation": "entry.json",
            "get": true,
            "put": false
        },
        "/create-entry/:id/": {
            "view": "display-json.htm",
            "variation": "entry.json",
            "post": true,
            "put": false
        },
        "/update-entry/:id/": {
            "view": "display-json.htm",
            "variation": "entry.json"
        },
        "/delete-entry/:id/": {
            "view": "display-json.htm",
            "variation": "entry.json",
            "delete": true,
            "put": 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.

Gérér CORS et les demandes OPTIONS

Par défaut, les requêtes pré-vérifiées ("preflighted requests") ne sont pas activées. Vous allez en avoir besoin pour, par exemple, effectuer des requêtes Cross-Domain ou CORS. Les requêtes pré-vérifiées se font avec la méthode HTTP OPTIONS.

Pour activer OPTIONS sur une route, utilisez la propriété options sur une route dans le webconfig. Pour activer OPTIONS sur toutes les routes, utilisez alors la propriété options du webconfig sur la configuration global.

{
    "options": true,
    "routes": {
        "/read-all-entry/": {
            "view": "display-json.htm",
            "variation": "all-entry.json",
            "options": false
        },
        "/create-entry/:id/": {
            "view": "display-json.htm",
            "variation": "entry.json",
            "post": true
        },
        "/delete-entry/:id/": {
            "view": "display-json.htm",
            "variation": "entry.json",
            "delete": true
        }
    }
}

Demande Cross-Domain

Si vous souhaitez authoriser une ressource du serveur NodeAtlas au requête en provenance de www.domain-a.com pour une page précise, vous pouvez le faire ainsi :

{
    "routes": {
        "/api/random-quote": {
            "controller": "get-quote.js",
            "headers": {
                "Access-Control-Allow-Origin": "http://www.domain-a.com"
            }
        }
    }
}

Ainsi vous pourrez par exemple accepter la requête suivante qui a pour Origin, http://www.domain-a.com qui est donc une valeur de Access-Control-Allow-Origin :

GET /api/random-quote HTTP/1.1
Host: www.domain-a.com
...
Origin: http://www.domain-a.com
...

Demande Cross-Domain avec jeton

Si vous souhaitez authoriser des ressources du serveur NodeAtlas aux requêtes en provenance de n'importe quel domaine externe pour la page /api/random-quote et une page qui attend un jeton d'authentification pour la page /api/protected/random-quote, vous pouvez le faire ainsi :

{
    "mimeType": "application/json",
    "headers": {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Authorization"
    },
    "routes": {
        "/api/random-quote": {
            "controller": "get-quote.js"
        },
        "/api/protected/random-quote": {
            "controller": "get-quote.js",
            "middlewares": "is-authenticated.js",
            "options": true
        }
    }
}

Faire lire un jeton à NodeAtlas depuis un domaine externe nécessite de lui passer l'en-tête HTML Authorization. Pour que celle-ci soit accepté par NodeAtlas il faut le définir avec Access-Control-Allow-Headers acceptant Authorization. L'envoi d'un jeton nécessitant une requête pré-vérifiée, il faut également mettre options à true pour autoriser les requêtes HTTP avec la méthode OPTIONS.

Ainsi vous pourrez par exemple accepter la requête suivante qui passe un jeton d'authentification à notre serveur pour la ressource /api/protected/random-quote :

GET /api/protected/random-quote HTTP/1.1
Host: localhost:1337
...
Origin: http://localhost
Authorization: Bearer CODE_DU_JETON
...

Autre demande Cross-Domain

Toutes les entêtes prévues pour faire fonctionner CORS sont accepté via le mécanisme d'ajout d'en-tête de 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 :

{
    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 :

{
    "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.

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 de controller commun.

Session gérées avec Redis

Implémenter le code suivant dans le controller commun pour stocker vos sessions dans Redis en local.

exports.setModules = function () {
    var NA = this;

    NA.modules.RedisStore = require("connect-redis");
};

exports.setSessions = function (njsext) {
    var NA = this,
        session = NA.modules.session,
        RedisStore = NA.modules.RedisStore(session);

    NA.sessionStore = new RedisStore();

    next();
};

Plus d'informations sur 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.

exports.setModules = function () {
    var NA = this;

    NA.modules.MongoStore = require("connect-mongo");
};

exports.setSessions = function (next) {
    var NA = this,
        session = NA.modules.session,
        MongoStore = NA.modules.MongoStore(session);

    NA.sessionStore = new MongoStore({
        db: "sessions"
    });

    next();
};

Plus d'informations sur connect-redis.

Changer l'url final des hostname et port d'écoute

Il est possible de générer une url de visite différente des paramètres d'écoutes demandés avec urlHostname et urlPort. Par exemple on écoute la boucle local sur le port 80 car un script fait du Reverse Proxy depuis le port 7777 sur le 80 avec le module « http-proxy » comme ci-dessous :

{
    "httpPort": 7777,
    "httpHostname": "127.0.0.1",
    "urlPort": 80,
    "urlHostname": "localhost",
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

Générer les urls dynamiquement

Les chemins relatifs en absolue

Il est possible que les chemins créés à partir de votre url soient interprétés comme des sous-dossiers qui n'ont en réalité aucune existence réelle. Cela a pour conséquence de rendre l'adresse media/images/example.jpg initialement accessible depuis un template affiché à http://localhost impossible à récupérer quand le template est affiché à http://localhost/sub-directory/ (puisqu'il faudrait alors que notre chemin soit plutôt ../media/images/example.jpg).

Pour ne plus avoir à se soucier de l'accès aux ressources peu importe l'url qui est demandée, il suffit de transformer toutes les urls relatives telles que :

<link rel="stylesheet" type="text/css" href="stylesheets/common.css" />
<!-- ... -->
<img src="media/images/example.jpg" />
<!-- ... -->
<script type="text/javascript" src="javascripts/common.js"></script>

en urls absolues avec la variable urlBasePath comme ci-dessous :

<link rel="stylesheet" type="text/css" href="<?= urlBasePath ?>stylesheets/common.css" />
<!-- ... -->
<img src="<?= urlBasePath ?>media/images/example.jpg" />
<!-- ... -->
<script type="text/javascript" src="<?= urlBasePath ?>javascripts/common.js"></script>

À noter que dans le cas de la configuration suivante :

{
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

urlBasePath retourne http://localhost/ alors que dans celle-ci :

{
    "httpPort": 7777,
    "urlRelativeSubPath": "sub/folder",
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

urlBasePath retourne http://localhost:7777/sub/folder/.

Les chemins des templates

En utilisant le webconfig suivant :

{
    "routes": {
        "/index.html": {
            "view": "index.htm"
        },
        "/contact.html": {
            "view": "contact.htm"
        }
    }
}

ainsi que la view index.htm correspondante

<!-- ... -->
<a href="http://localhost/index.html">Lien vers l'accueil</a>
<a href="http://localhost/contact.html">Lien pour nous contacter</a>
<!-- ... -->

je serais obligé de changer mon lien dans le template si je change le port d'écoute ou si je change le chemin de l'url. Le changement de configuration suivant :

{
    "httpPort": 7777,
    "routes": {
        "/home.html": {
            "view": "index.htm"
        },
        "/contact-us.html": {
            "view": "contact.htm"
        }
    }
}

me contraindrait à modifier le template précédent comme suit :

<!-- ... -->
<a href="http://localhost:7777/home.html">Lien vers l'accueil</a>
<a href="http://localhost:7777/contact-us.html">Lien pour nous contacter</a>
<!-- ... -->

Il est possible de solutionner ce problème en donnant une clé à un chemin précis et en déportant son chemin dans la propriété url.

Avec le webconfig suivant :

{
    "routes": {
        "index": {
            "url": "/index.html",
            "view": "index.htm"
        },
        "contact": {
            "url": "/contact.html",
            "view": "contact.htm"
        }
    }
}

je peux à présent écrire le lien dans le template de manière dynamique :

  1. comme suit

    <!-- ... -->
    <a href="<?= urlBasePath ?><?= webconfig.routes.home.url.slice(1) ?>">Lien vers l'accueil</a>
    <a href="<?= urlBasePath ?><?= webconfig.routes.contact.url.slice(1) ?>">Lien pour nous contacter</a>
    <!-- ... -->
    

    Note : .slice(1) permet de supprimer facilement le double / pour une url fonctionnelle.

  2. ou comme suit

    <!-- ... -->
    <a href="<?= urlBasePath ?>.<?= webconfig.routes.home.url ?>">Lien vers l'accueil</a>
    <a href="<?= urlBasePath ?>.<?= webconfig.routes.contact.url ?>">Lien pour nous contacter</a>
    <!-- ... -->
    

    Note : Cela donnerait par exemple http://localhost/./home.html, ce qui est une url fonctionnelle.

  3. ou comme suit

    <!-- ... -->
    <a href="<?= urlBasePathSlice + webconfig.routes.home.url ?>">Lien vers l'accueil</a>
    <a href="<?= urlBasePathSlice + webconfig.routes.contact.url ?>">Lien pour nous contacter</a>
    <!-- ... -->
    

    Note : urlBasePathSlice renvoyant http://localhost au lieu de http://localhost/ ou encore http://localhost:7777/sub/folder au lieu de http://localhost:7777/sub/folder/.

Utilisation de la clé pour mapper les pages

Il est parfois utile de connaître la clé utilisé pour la page courante afin de trouver une équivalence dans une autre langue par exemple.

Avec le webconfig suivant :

{
    "languageCode": "fr-fr",
    "routes": {
        "index_fr-fr": {
            "url": "/",
            "view": "/index.htm"
        },
        "index_en-us": {
            "url": "/english/",
            "view": "index.htm",
            "languageCode": "en-us"
        },
        "cv_fr-fr": {
            "url": "/cv/",
            "view": "cv.htm"
        },
        "cv_en-us": {
            "url": "/english/resume/",
            "view": "index.htm",
            "languageCode": "en-us"
        }
    }
}

et les fichiers de variation commun suivant en fr :

{
    "language": [{
        "name": "Anglais",
        "code": "en-us"
    }, {
        "name": "Français",
        "code": "fr-fr"
    }]
}

et en en :

{
    "language": [{
        "name": "English",
        "code": "en-us"
    }, {
        "name": "French",
        "code": "fr-fr"
    }]
}

on peut alors créer un lien entre chaque page multilingue comme ceci :

<ul>
    <? for (var i = 0; i < common.language.length; i++) { ?>
    <li><a href="<?= urlBasePath + webconfig.routes[routeKey.split('_')[0] + '_' + common.language[i].code].url ?>"><?- common.language[i].name ?></a></li>
    <? } ?>
</ul>

Moteur de template personnalisé

Il est possible de laisser l'implémentation de Express prendre la main sur le moteur de rendu des vues. Pour cela il faut utiliser le paramètre engine. Un exemple en utilisant le moteur Handlebars :

Tout d'abord, ajouter le middleware Express Handlebars à vos modules :

npm install express-handlebars

puis utiliser engine avec la valeur arbitraire hbs

{
    "engine": "hbs",
    "controller": "common.js",
    "variation": "common.json",
    "routes": {
        "/": {
            "view": "index.hbs",
            "variation": "index.json"
        }
    }
}

puis expliquer au moteur Express de NodeAtlas comment rendre les vues :

exports.setModules = function () {
    var NA = this;

    NA.modules.exphbs = require("express-handlebars");
};

exports.setConfigurations = function (next) {
  var NA = this,
    exphbs = NA.modules.exphbs;

    NA.express.engine("hbs", exphbs());

    next();
};

enfin voyons rapidement ce que le fichier index.hbs pourrait contenir :

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8">
        <title>{{specific.titlePage}}</title>
        <link rel="stylesheet" href="stylesheets/{{common.classCssCommon}}.css" media="all">
        <link rel="stylesheet" href="stylesheets/{{specific.classPage}}.css" media="all">
    </head>
    <body class="{{specific.classPage}}">
        <div>
            <h1>{{specific.titlePage}}</h1>
            {{{specific.content}}}
        </div>
        <script async="true" type="text/javascript" src="javascripts/{{common.classJsCommon}}.js"></script>
    </body>
</html>

Ce que fait engine, c'est abandonner le système de NodeAtlas et passer par celui d'Express. Comme Express a besoin d'un objet response pour rendre une vue, il est impossible d'utiliser ce mécanisme via l'utilisation de la fonction NA.view de l'API NodeAtlas, celle-ci ne supportant que le moteur NodeAtlas, EJS et Pug.

Différence entre engine, templateEngineDelimiter et pug

Il est tout a fait possible de passer par Express pour rendre EJS et Pug. Dans ce cas, puisque node-atlas embarque les modules ejs et pug en tant que dépendance, il n'est pas nécéssaire de passer par le controller commun et l'utilisation de npm pour les mettre en place. Il suffit juste d'utiliser engine: "ejs" ou engine: "pug".

Cependant, faire cela retire les bénéfices apporter par NodeAtlas pour l'utilisation de ces deux moteurs comme par exemple le support des inclusions dynamique pour Pug dans la view avec #{routeParameters.view}.

Pas de vue

Il est possible de ne pas utiliser de vue et de seulement faire appel au contrôleur. Dans ce cas le point d'ancrage changeVariations est inutile. Il va falloir alimenter vous même locals.dom dans le point d'ancrage changeDom.

webconfig.json

{
    "routes": {
        "/(:member/)?": {
            "controller": "index.js",
            "mimeType": "application/json"
        }
    }
}

controllers/index.js

exports.changeDom = function (next, locals) {
    locals.dom = `{
  "params": ${locals.params.member},
  "query": ${locals.query.member},
  "body": ${locals.body.member}
}`;

    next();
};

Ainsi à l'adresse http://localhost/riri/?query=fifi demandé en POST avec le body member=loulou vous obtiendrez la sortie :

{
  "params": "riri",
  "query": "fifi",
  "body": "loulou"
}

Pas de routes

Pas un seul webconfig présenté dans la documentation ne se passe du paramètre routes. Pourtant il est facultatif au même titre que tous les autres. Aussi avec le webconfig suivant :

webconfig.json

{
    "controller": "common.js"
}

et le contrôleur suivant :

controllers/common.js

exports.setRoutes = function (next) {
    var NA = this,
        route = NA.webconfig.routes = {};

    route["/"] = {
        "mimeType": "text/plain"
    };

    next();
};

exports.changeDom = function (next, locals) {

    locals.dom = "Hello World";

    next();
};

Il est tout à fait possible d'obtenir à l'adresse http://localhost/ le simple message « Hello World ».

Activer le cache

C'est une bonne chose de ne pas reservir des fichiers qui n'ont pas bougé pour la production. Vous pouvez mettre à true la valeur du webconfig cache pour ça:

{
    cache: true,
    route: {
      "/": "index.htm"
    }
}

Vous pouvez également lancer node-atlas avec l'option --cache :

node-atlas --cache

ou mettre votre variable d'environnement NODE_ENV à production :

si vous êtes sous Unix/MacOS

export NODE_ENV=production

ou si vous êtes sur windows

SET NODE_ENV=production

ou vous pouvez démarrer NodeAtlas comme suit

NODE_ENV=production node-atlas

ou vous pouvez la définir dans votre fichier JavaScript :

process.env.NODE_ENV = "production";