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'url dynamiques !

Standard

Avec la configuration suivante :

{
    "routes": {
        "/liste-des-membres/:member/": {
            "template": "members.htm"
        },
        "/liste-des-membres/": {
            "template": "members.htm"
        },
        "/": {
            "template": "index.htm"
        }
    }
}

vous pourrez accéder à :

et récupérer les valeurs de :member dans le changeVariation (common et specific).

exports.changeVariation = function (params, next) {
    var variation = params.variation;

    console.log(variation.params.member);
    // \> 'toto', 'bob-eponge99', 'node-atlas' ou 'etc'.

    next(variation);
}

Les règles de création d'url dynamique sont celles de Express.js.

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]+)/?": {
            "template": "members.htm",
            "regExp": "g"
        },
        "/liste-des-membres/?": {
            "template": "members.htm",
            "regExp": true
        },
        "/": {
            "template": "index.htm"
        }
    }
}

vous pourrez accéder à :

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

exports.changeVariation = function (params, next) {
    var variation = params.variation;

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

    console.log(variation.params.member);
    // \> 'toto', 'bob-eponge99', 'node-atlas' ou 'etc'.

    next(variation);
}

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

Routage dans un fichier partagé

Afin de ne pas réécrire une longue liste de route 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 routes dans un fichier de votre choix. Par convention, c'est le fichier routes.json.

Par exemple :

L'ensemble de fichier suivant

├─ templates/
│  └─ index.htm
├─ webconfig.json
└─ webconfig.prod.json

avec webconfig.json

{
    "httpPort": 7777,
    "routes": {
        "/": {
            "template": "index.htm"
        }
    }
}

et avec webconfig.prod.json

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

pourrait devenir l'ensemble de fichier suivant

templates/
— index.htm
routes.json
webconfig.json
webconfig.prod.json

avec webconfig.json

{
    "httpPort": 7777,
    "routes": "routes.json"
}

avec webconfig.prod.json

{
    "httpPort": 7776,
    "httpHostname": "blog.lesieur.name",
    "urlPort": 80,
    "routes": "routes.json"
}

et routes.json

{
    "/": {
        "template": "index.htm"
    }
}

Note : Vous pouvez vous créer plusieurs fichier de route comme routes.en.json et routes.fr.json et associer chacun d'eux dans un ensemble de webconfig paramétrés pour faire tourner un site dans diverses langues.

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/": {
            "template": "members.htm"
        },
        "/": {
            "template": "index.htm"
        },
        "/pages-inexistantes/": {
            "template": "error.htm",
            "statusCode": 404
        }
    }
}

vous pourrez accéder à :

Page d'erreur multilingue

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/": {
            "template": "members.htm",
            "variation": "members.json"
        },
        "/": {
            "template": "index.htm",
            "variation": "index.json"
        },
        "/pages-inexistantes/": {
            "template": "error.htm",
            "variation": "error.json",
            "statusCode": 404
        },
        "/english/list-of-members/": {
            "template": "members.htm",
            "languageCode": "en-gb",
            "variation": "members.json"
        },
        "/english/": {
            "template": "index.htm",
            "languageCode": "en-gb",
            "variation": "index.json"
        },
        "/english/*": {
            "template": "error.htm",
            "languageCode": "en-gb",
            "variation": "error.json",
            "statusCode": 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/": {
            "template": "members.htm"
        },
        "/liste-des-membres": {
            "redirect": "/liste-des-membres/",
            "statusCode": 301
        },
        "/aller-sur-node-atlas/": {
            "redirect": "https://node-atlas.js.org/",
            "statusCode": 302
        },
        "/": {
            "template": "index.htm"
        }
    }
}

Vous serez redirigé :

En dynamique

Voyez l'exemple ci-dessous :

{
    "routes": {
        "/liste-des-membres/:member/": {
            "template": "members.htm"
        },
        "/liste-des-membres/:member": {
            "redirect": "/membres/:member/",
            "statusCode": 301
        },
        "/": {
            "template": "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]+)/": {
            "template": "members.htm",
            "regExp": true
        },
        "/liste-des-membres/([-a-z0-9]+)/": {
            "redirect": "/membres/$0/",
            "statusCode": 301,
            "regExp": true
        },
        "/liste-des-membres/": {
            "template": "members.htm"
        },
        "/": {
            "template": "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.

{
    "routes": {
        "/api/articles": {
            "template": "display-json.htm",
            "controller": "blog/list-of-articles.js",
            "mimeType": "application/json"
            "charset": "ISO-8859-1",
            "statusCode": 203
        }
    }
}

Il est également possible de modifier complètement les Headers, ce qui écrase toutes les autres valeurs à l'exception du statusCode.

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

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,
    "httpSecureRelativeKeyPath": "security/server.key",
    "httpSecureRelativeCertificatePath": "security/server.crt",
    "routes": {
        "/": {
            "template": "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": {
        "/": {
            "template": "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": {
        "/": {
            "template": "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": {
        "javascript": {
            "javascript/boot.min.js": [
                "javascript/modernizr.js",
                "javascript/yepnot.js",
                "javascript/html5Shiv.js"
            ],
            "javascript/framework.min.js": [
                "javascript/jquery.js",
                "javascript/jquery-ui.js",
                "javascript/prettify.js",
                "javascript/prettify/run_prettify.js"
            ],
            "javascript/common.min.js": [
                "javascript/components/extended-format-date.js",
                "javascript/common.js"
            ]
        },
        "stylesheets": {
            "stylesheets/common.min.css": [
                "stylesheets/common.css",
                "stylesheets/common-min780.css",
                "stylesheets/common-min1160.css"
            ]
        }
    },
    "routes": {
        "/": {
            "template": "index.htm"
        }
    }
}

et l'ensemble de fichier suivant :

├─ assets/
│  ├─ stylesheets/
│  │  ├─ common.css
│  │  ├─ common-min780.css
│  │  └─ common-min1160.css
│  └─ javascript/
│     ├─ modernizr.js
│     ├─ yepnot.js
│     ├─ html5Shiv.js
│     ├─ jquery.js
│     ├─ jquery-ui.js
│     ├─ prettify.js
│     ├─ prettify/
│     │  └─ run_prettify.js
│     ├─ components/
│     │  └─ extended-format-date.js
│     └─ common.js
├─ templates/
│  └─ 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
│  └─ javascript/
│     ├─ 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
├─ templates/
│  └─ 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
│  └─ javascript/
│     ├─ modernizr.js
│     ├─ yepnot.js
│     ├─ html5Shiv.js
│     ├─ jquery.js
│     ├─ jquery-ui.js
│     ├─ prettify.js
│     ├─ prettify/
│     │  └─ run_prettify.js
│     ├─ components/
│     │  └─ extended-format-date.js
│     └─ common.js
├─ templates/
│  └─ index.htm
├─ webconfig.json
└─ webconfig.prod.json

avec webconfig.json

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

et avec webconfig.prod.json

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

pourrait devenir l'ensemble de fichier suivant

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

avec webconfig.json

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

avec webconfig.prod.json

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

et bundles.json

{
    "javascript": {
        "javascript/boot.min.js": [
            "javascript/modernizr.js",
            "javascript/yepnot.js",
            "javascript/html5Shiv.js"
        ],
        "javascript/framework.min.js": [
            "javascript/jquery.js",
            "javascript/jquery-ui.js",
            "javascript/prettify.js",
            "javascript/prettify/run_prettify.js"
        ],
        "javascript/common.min.js": [
            "javascript/components/extended-format-date.js",
            "javascript/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 "stylesheetsBundlesEnable": false et "javascriptBundlesEnable": false pour chaque type de Bundle.

{
    "stylesheetsBundlesEnable": false,
    "javascriptBundlesEnable": false,
    "bundles": {
        "javascript": {
            "javascript/boot.min.js": [
                "javascript/modernizr.js",
                "javascript/yepnot.js",
                "javascript/html5Shiv.js"
            ],
            "javascript/framework.min.js": [
                "javascript/jquery.js",
                "javascript/jquery-ui.js",
                "javascript/prettify.js",
                "javascript/prettify/run_prettify.js"
            ],
            "javascript/common.min.js": [
                "javascript/components/extended-format-date.js",
                "javascript/common.js"
            ]
        },
        "stylesheets": {
            "stylesheets/common.min.css": [
                "stylesheets/common.css",
                "stylesheets/common-min780.css",
                "stylesheets/common-min1160.css"
            ]
        }
    },
    "routes": {
        "/": {
            "template": "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 "stylesheetsBundlesBeforeResponse": true et "javascriptBundlesBeforeResponse": true pour chaque type de Bundle.

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

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

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

ainsi que le webconfig suivante :

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

et le contenu suivant dans :

templates/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 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 :

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

ou suivante :

{
    "enableLess": {
        "less": "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 :

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

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

et le contenu suivant dans :

templates/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 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.

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 :

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

ou suivante :

{
    "enableLess": {
        "stylus": "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 Less seront capable de fouiller dans les sous dossier : styles, stylesheets ou css. Il est possible de changer cela avec :

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

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

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

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

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

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

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

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

avec webconfig.prod.json

{
    "httpPort": 7776,
    "httpHostname": "blog.lesieur.name",
    "urlPort": 80,
    "optimizations": "optimizations.json",
    "routes": {
        "/": {
            "template": "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 "imagesOptimizationsEnable": false.

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

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

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

body {
    color: #f00;
}

templates/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 </path/to/>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

<!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 generates.

Injection globale

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

{
    "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 <body style="color: #f00;">.

Injection multiple

Il est possible :

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

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

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

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 controllers/common.js de la partie Back-end.

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.

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.

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 :

{
    "templateEngineDelimiter": "?",
    "routes": {
        "/": {
            "template": "index.htm"
        }
    }
}

Voyez l'exemple dans les fichiers ci-dessous :

components/head.htm

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title><?- specific.titlePage ?></title>

        <link type="text/css" rel="stylesheet" href="stylesheets/<?= common.classCssCommon ?>.css" media="all" />
        <link type="text/css" rel="stylesheet" href="stylesheets/<?= specific.classPage ?>.css" media="all" />
    </head>
    <body class="<?= specific.classPage ?>">

components/foot.htm

        <script async type="text/javascript" src="javascript/<?= common.classJsCommon ?>.js"></script>
    </body>
</html>

templates/template.htm

    <?- include('head.htm') ?>

    <div class="title"><?- common.titleWebsite ?></div>

    <div>
        <h1><?- specific.titlePage ?></h1>
        <?- specific.content ?>
    </div>

    <?- include('foot.htm') ?>

Pour tout savoir sur les possibilités du moteur de template consulter la documentation ejs

Note : Si rien n'est précisé, templateEngineDelimiter vaut %.

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

Il est également possible de faire en sorte qu'aucune autre url ne puisse être tapé. Ainsi si on réclame www.localhost ou localhost:7777 c'est bien sur localhost que sera le visiteur :

{
    "enableForceDomain": true,
    "httpPort": 7777,
    "httpHostname": "127.0.0.1",
    "urlPort": 80,
    "urlHostname": "localhost",
    "routes": {
        "/": {
            "template": "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="javascript/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 %>javascript/common.js"></script>

À noter que dans le cas de la configuration suivante :

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

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

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

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

Les chemins des templates

En utilisant le webconfig suivant :

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

ainsi que le template index.htm correspondant

<!-- ... -->
<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": {
            "template": "index.htm"
        },
        "/contact-us.html": {
            "template": "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",
            "template": "index.htm"
        },
        "contact": {
            "url": "/contact.html",
            "template": "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": "/",
            "template": "/index.htm"
        },
        "index_en-us": {
            "url": "/english/",
            "template": "index.htm",
            "languageCode": "en-us"
        },
        "cv_fr-fr": {
            "url": "/cv/",
            "template": "cv.htm"
        },
        "cv_en-us": {
            "url": "/english/resume/",
            "template": "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="<%= urlBasePathSlice + webconfig.routes[currentRouteName.split('_')[0] + '_' + common.language[i].code].url %>"><%- common.language[i].name %></a></li>
    <% } %>
</ul>