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 de simple 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 JS au simple jQuery en passant par Vue, Angular ou React : utiliser vos outils clients favoris !

Partie Vue et Template

NodeAtlas fonctionne avec une configuration via l'utilisation d'un webconfig.json qui lui permet d'étendre les possibilités du site de manière évolutive tout au long de sa vie. Par exemple, pour créer un site sans JavaScript côté serveur (pas de contrôleur), il suffit de ne renseigner qu'un paramètre view pour chaque route.

Cependant, vous pourrez toujours utiliser du JavaScript dans les templates des vues grâce à l'utilisation du moteur de template EJS avec lequel fonctionne NodeAtlas par défaut.

Voyons les possibilités de nos sites par agrégat simple de fichiers de vue.

Plusieurs pages

Ci-dessous un exemple de configuration.

{
    "viewsRelativePath": "views",
    "routes": {
        "/": {
            "view": "index.htm"
        },
        "/membre.html": {
            "view": "member.htm",
            "post": false
        },
        "/membre-sans-extension/": {
            "view": "member.htm",
            "get": false
        },
        "a-propos.html": {
            "view": "about.htm"
        },
        "/erreur.html": {
            "view": "error.htm",
            "statusCode": 404,
            "mimeType": "text/plain"
        }
    }
}

Pour faire tourner cet ensemble de fichier :

├─ views/
│  ├─ about.htm
│  ├─ error.htm
│  ├─ index.htm
│  └─ member.htm
└─ webconfig.json

aux adresses :

Note : Si viewsRelativePath n'est pas présent dans webconfig.json, par défaut le dossier des vues est bien views. viewsRelativePath est donc utile seulement pour changer le nom/chemin du répertoire.

Référencer ses routes

La configuration ci-dessous est équivalente à la configuration de la section juste au-dessus

{
    "viewsRelativePath": "views",
    "routes": {
        "/": "index.htm",
        "/membre.html": {
            "view": "member.htm",
            "post": false
        },
        "/membre-sans-extension/": {
            "view": "member.htm",
            "get": false
        },
        "a-propos.html": "about.htm",
        "/erreur.html": {
            "view": "error.htm",
            "statusCode": 404,
            "mimeType": "text/plain"
        }
    }
}

car

"/": "index.htm",

est un raccourci de

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

Évidemment ce raccourci ne sert que si view est le seul paramètre à déclarer de la route.

Ordonner les routes

Il est également possible de placer ses routes dans un tableau, ce qui permettra de les prioriser lors de leur manipulation ultérieure dans la section des contrôleurs.

Dans ce cas le chemin devient le paramètre url.

{
    "viewsRelativePath": "views",
    "routes": [{
        "url": "/",
        "view": "index.htm"
    }, {
        "url": "/membre.html",
        "view": "member.htm",
        "post": false
    }, {
        "url": "/membre-sans-extension/",
        "view": "member.htm",
        "get": false
    }, {
        "url": "a-propos.html",
        "view": "about.htm"
    }, {
        "url": "/erreur.html",
        "view": "error.htm",
        "statusCode": 404,
        "mimeType": "text/plain"
    }]
}

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

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

avec webconfig.json

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

et avec webconfig.prod.json

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

pourrait devenir l'ensemble de fichier suivant

├─ views/
│  └─ 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

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

Note : Vous pouvez vous créer plusieurs fichiers de routes 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.

Héberger des images, polices, CSS, JS, etc.

Vous pouvez également héberger tout un tas de fichiers sur votre site dans un dossier public. Par exemple avec cette configuration :

{
    "assetsRelativePath": "assets",
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

et cet ensemble de fichiers :

├─ assets/
│  ├─ stylesheets/
│  │  └─ common.css
│  ├─ javascripts/
│  │  └─ common.js
│  └─ media/
│     └─ images/
│        └─ logo.png
├─ views/
│  └─ index.htm
└─ webconfig.json

vous aurez accès aux adresses :

Note : Si assetsRelativePath n'est pas présent dans webconfig.json, par défaut le dossier public est bien assets. assetsRelativePath est donc utile seulement pour changer le nom/chemin du répertoire.

maxAge, Etag, etc.

Il est possible de délivrer des en-tête HTTP personnalisées pour les ressources publiques (comme le maxAge, l'Etag, etc.) via la propriété staticOptions du webconfig. Pour connaître la totalité des possibilités, voir les options d'Express.

Gérer l'inclusion de fichiers partiels

Vous pouvez segmenter vos codes HTML afin de ne pas répéter le code redondant comme par exemple les parties « head » et « foot » ou tout autre fragment de code (pas d'inquiétude, nous verrons plus loin comment gérer un template unique composé des balises head et body avec fermeture dans le même fichier).

webconfig.json

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

avec les fichiers suivants :

├─ assets/
│  ├─ stylesheets/
│  │  └─ common.css
│  └─ javascripts/
│     └─ common.js
├─ views/
│  ├─ partials/
│  │  ├─ head.htm
│  │  └─ foot.htm
│  ├─ index.htm
│  └─ members.htm
└─ webconfig.json

views/partials/head.htm

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>Hello world</title>
        <link type="text/css" rel="stylesheet" href="stylesheets/common.css" media="all" />
    </head>
    <body>

views/partials/foot.htm

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

views/index.htm

    <?- include("partials/head.htm") ?>

    <div>
        <h1>Bienvenue</h1>
        <p>C'est la page d'accueil.</p>
    </div>

    <?- include("partials/foot.htm") ?>

views/members.htm

    <?- include("partials/head.htm") ?>

    <div>
        <h1>Liste des members</h1>
        <p>C'est la page des membres.</p>
    </div>

    <?- include("partials/foot.htm") ?>

vous aurez accès aux adresses :

Note : pour plus d'information sur la différence entre <?, <?-, <?=, etc. vous pouvez vous référez à la section moteur de template.

Gérer des variations au sein d'une même vue

Il est possible avec la même vue et les mêmes inclusions de générer des pages aux contenus différents (utile en mode génération de maquettes HTML). Activer les variations avec la configuration suivante :

{
    "variation": "common.json",
    "variationsRelativePath": "variations",
    "routes": {
        "/": {
            "view": "template.htm",
            "variation": "index.json",
        },
        "/liste-des-membres/": {
            "view": "template.htm",
            "variation": "members.json",
        }
    }
}

avec les fichiers suivants :

├─ assets/
│  ├─ stylesheets/
│  │  ├─ common.css
│  │  ├─ index.css
│  │  └─ members.css
│  └─ javascripts/
│     ├─ common.js
│     ├─ index.js
│     └─ members.js
├─ variations/
│  ├─ common.json
│  ├─ index.json
│  └─ members.json
├─ views/
│  ├─ partials/
│  │  ├─ head.htm
│  │  └─ foot.htm
│  └─ template.htm
└─ webconfig.json

variations/common.json

{
    "titleWebsite": "Titre du site",
    "classCssCommon": "common",
    "classJsCommon": "common"
}

variations/index.json

{
    "titlePage": "Bienvenue",
    "classPage": "index",
    "content": "<p>C'est la page d'accueil.</p>"
}

variations/members.json

{
    "titlePage": "Liste des membres",
    "classPage": "members",
    "content": "<p>C'est la page des membres.</p>"
}

views/partials/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 ?>">

views/partials/foot.htm

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

views/template.htm

    <?- include("partials/head.htm") ?>

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

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

    <?- include("partials/foot.htm") ?>

vous aurez accès aux adresses :

Note : Si variationsRelativePath n'est pas présent dans webconfig.json, par défaut le dossier des variations est bien variations. variationsRelativePath est donc utile seulement pour changer le nom/chemin de répertoire.

Gérer l'internationalisation (i18n)

Toutes les langues sur le même site

Sur le même principe, les variations peuvent être utilisées pour créer la même page, mais dans des langues différentes (et derrière des routes différentes) :

{
    "languageCode": "en-us",
    "variationsRelativePath": "l10n",
    "routes": {
        "/": {
            "view": "landing.htm",
            "variation": "landing.json"
        },
        "/home/": {
            "view": "home.htm",
            "variation": "home.json"
        },
        "/accueil/": {
            "view": "home.htm",
            "variation": "home.json",
            "languageCode": "fr-fr"
        }
    }
}

Note : Dans cet exemple je n'utilise pas la propriété variation commune, car je n'utilise pas de common.json . J'ai arbitrairement décidé de renommer mon dossier variations en l10n (localisation).

avec les fichiers suivants :

├─ l10n/
│  ├─ landing.json
│  ├─ en-us
│  │  └─ home.json
│  └─ fr-fr
│     └─ home.json
├─ views/
│  ├─ partials/
│  │  ├─ head.htm
│  │  └─ foot.htm
│  ├─ landing.htm
│  └─ home.htm
└─ webconfig.json

l10n/landing.json

{
    "titlePage": "Landing",
    "classPage": "landing",
    "selectLabel": [
        "English",
        "Français"
    ]
}

l10n/en-us/home.json

{
    "titlePage": "Welcome",
    "classPage": "home",
    "content": "<p>This is a home page.</p>"
}

l10n/fr-fr/home.json

{
    "titlePage": "Bienvenue",
    "classPage": "home",
    "content": "<p>C'est la page d'accueil.</p>"
}

views/partials/head.htm

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

views/partials/foot.htm

    </body>
</html>

views/landing.htm

    <?- include("partials/head.htm") ?>

    <select>
        <? for (var i = 0; i < specific.selectLabel.length; i++) { ?>
        <option><?= specific.selectLabel[i] ?></option>
        <? } ?>
    </select>

    <?- include("partials/foot.htm") ?>

views/home.htm

    <?- include("partials/head.htm") ?>

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

    <?- include("partials/foot.htm") ?>

vous aurez accès aux adresses :

Note : Par défaut c'est le languageCode racine qui conditionne la langue d'affichage du site. Il est aussi possible de changer la langue avec un languageCode par page. Il faut également savoir que dès que le site ou une page à un languageCode dans la configuration, ses fichiers de variations doivent être placées dans un sous répertoire portant le nom du languageCode.

Utiliser variations et localisations ensemble

Vous avez peut-être constaté dans l'exemple précédent que le fichier landing.json n'était pas dans le dossier en-us/ ou fr-fr/. Cela est tout à fait possible et signifie qu'il sera utilisé dans les langues qui ne le possèdent pas dans leur dossier.

Aussi, quand un languageCode est précisé, NodeAtlas part d'abord chercher la valeur dans le fichier du dossier correspondant. Si celle-ci n'y est pas, alors il part la chercher dans le dossier parent (celui utilisé en standard pour les variations sans localisation).

Cela va vous permettre par exemple de gérer la langue maître directement dans le dossier de variation. Ainsi avec l'exemple suivant :

┊┉
├─ variations/
│  ├─ common.json
│  ├─ home.json
│  └─ fr-fr
│     ├─ common.json
│     └─ home.json
┊┉

vous pouvez

  • gérer la version en-us directement à la racine de variations/ (comme NodeAtlas ne trouve rien dans en-us il utilise alors les valeurs des fichiers racines) et
  • gérer la version fr-fr dans le dossier fr-fr/,

ainsi, si une phrase n'est pas encore traduite dans un fichier fr-fr, au lieu de renvoyer une erreur, NodeAtlas renverra la version racine, soit la version en-us.

À chaque langue sa configuration

Vous pouvez également décider de faire tourner chaque langue dans un « webconfig.json » différent. Avec l'ensemble de fichier suivant :

├─ variations/
│  ├─ landing.json
│  ├─ en-us
│  │  ├─ home.json
│  │  └─ members.json
│  └─ fr-fr
│     ├─ home.json
│     └─ members.json
├─ views/
│  ├─ partials/
│  │  ├─ head.htm
│  │  └─ foot.htm
│  ├─ landing.htm
│  ├─ home.htm
│  └─ members.htm
├─ webconfig.json
├─ webconfig.en-us.json
└─ webconfig.fr-fr.json

vous pourriez avoir les « webconfig.json » suivant :

webconfig.json

{
    "routes": {
        "/": {
            "view": "landing.htm",
            "variation": "landing.json"
        }
    }
}

webconfig.en-us.json

{
    "httpPort": 81,
    "urlRelativeSubPath": "english",
    "languageCode": "en-us",
    "routes": {
        "/": {
            "view": "home.htm",
            "variation": "home.json"
        },
        "/members-list/": {
            "view": "members.htm",
            "variation": "members.json"
        }
    }
}

webconfig.fr-fr.json

{
    "httpPort": 82,
    "urlRelativeSubPath": "francais",
    "languageCode": "fr-fr",
    "routes": {
        "/": {
            "view": "home.htm",
            "variation": "home.json"
        },
        "/liste-des-membres/": {
            "view": "members.htm",
            "variation": "members.json"
        }
    }
}

et avoir accès aux adresses :

Il est ensuite possible de faire du reverse proxy avec pour ramener l'ensemble des URLs sur le port 80 afin d'obtenir :

Gérer l'anatomie des URLs

Par défaut, si vous utilisez la configuration suivante :

webconfig.json

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

avec la vue suivante :

views/index.htm

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>URLs</title>
    </head>
    <body>
        <div><?- urlRootPath ?></div>
        <div><?- urlSubPath ?></div>
        <div><?- urlBasePath ?></div>
        <div><?- urlFilePath ?></div>
        <div><?- urlQueryPath ?></div>
        <div><?- urlPath ?></div>
    </body>
</html>

cela est identique à utiliser celle-ci :

webconfig.json

{
    "httpHostname": "localhost",
    "httpPort": 80,
    "httpSecure": false,
    "urlRelativeSubPath": "",
    "routes": {
        "/": {
            "view": "index.htm"
        }
    }
}

Vous pourrez accéder à l'URL : http://localhost/ et au contenu :

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>URLs</title>
    </head>
    <body>
        <div>http://localhost</div>
        <div></div>
        <div>http://localhost</div>
        <div>/</div>
        <div></div>
        <div>http://localhost/</div>
    </body>
</html>

Changez alors la configuration en ceci :

{
    "httpHostname": "127.0.0.1",
    "httpPort": 7777,
    "httpSecure": "security/server",
    "urlRelativeSubPath": "sub/folder",
    "routes": {
        "/index.html": {
            "view": "index.htm"
        }
    }
}

Vous pourrez cette fois accéder à l'URL : https://127.0.0.1:7777/sub/folder/index.html?test=ok et au contenu :

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>URLs</title>
    </head>
    <body>
        <div>https://127.0.0.1:7777</div>
        <div>/sub/folder</div>
        <div>https://127.0.0.1:7777/sub/folder</div>
        <div>/index.html</div>
        <div>?test=ok</div>
        <div>https://127.0.0.1:7777/sub/folder/index.html</div>
    </body>
</html>

Note : Cette exemple ne fonctionnera que si vous avez des fichiers server.crt et server.key valide dans le dossier security/. Essayez le sans "httpSecure": "security/server" et il fonctionnera avec des URLs sans https.

Créer ses propres Variables de Webconfig

Imaginons deux webconfigs dans lesquels nous allons créer nos propres variables comme suit :

  1. « webconfig.json »
{
    "routes": {
        "/": {
            "view": "index.htm"
        }
    },
    "_minified": ""
}
  1. « webconfig.prod.json »
{
    "routes": {
        "/": {
            "view": "index.htm"
        }
    },
    "_minified": ".min"
}

avec cet ensemble de fichiers

├─ assets/
│  ├─ stylesheets/
│  │  ├─ common.css
│  │  └─ common.min.css
│  └─ javascripts/
│     ├─ common.js
│     └─ common.min.js
├─ views/
│  └─ index.htm
├─ webconfig.json
└─ webconfig.prod.json

et « index.htm » contenant :

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>Hello world</title>
        <link rel="stylesheet" type="text/css" href="stylesheets/common<?= webconfig._minified ?>.css" />
    </head>
    <body>
        <div>Ceci est un test de récupération de ressources minifiées/non-minifiées.</div>
        <script type="text/javascript" src="javascripts/common<?= webconfig._minified ?>.js"></script>
    </body>
</html>

En lançant (depuis le dossier du site) la commande :

$ node-atlas

Nous aurons à l'adresse « http://localhost/ » la sortie suivante avec les fichiers non minifiés :

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>Hello world</title>
        <link rel="stylesheet" type="text/css" href="stylesheets/common.css" />
    </head>
    <body>
        <div>Ceci est un test de récupération de ressources minifiées/non-minifiées.</div>
        <script type="text/javascript" src="javascripts/common.js"></script>
    </body>
</html>

Cependant en lançant la commande :

$ node-atlas --webconfig webconfig.prod.json

Nous aurons à l'adresse « http://localhost/ » la sortie suivante avec les fichiers minifiés :

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>Hello world</title>
        <link rel="stylesheet" type="text/css" href="stylesheets/common.min.css" />
    </head>
    <body>
        <div>Ceci est un test de récupération de ressources minifiées/non-minifiées.</div>
        <script type="text/javascript" src="javascripts/common.min.js"></script>
    </body>
</html>

Note : Il vaut mieux préfixer ses variables personnelles avec « _ » pour éviter des conflits avec des variables de configuration existantes ou futures.

Utiliser une vue globale

Plutôt que d'inclure une partie header et une partie footer en deux fichiers scindé dont les balises de l'un ne se ferme que dans les balises de l'autre : vous pouvez également les rassembler dans un seul fichier dans lequel vous indiquerez à quelle endroit les vues doivent se placer. Vous pourrez dans ce layout utiliser toutes les variations et tous les systèmes déjà vu.

avec cet ensemble de fichiers

├─ assets/
│  ├─ stylesheets/
│  │  ├─ common.css
│  │  └─ index.css
│  └─ javascripts/
│     └─ common.js
├─ variations/
│  ├─ common.json
│  └─ index.json
├─ views/
│  ├─ partials/
│  │  └─ header.htm
│  ├─ about.htm
│  ├─ common.htm
│  └─ index.htm
└─ webconfig.json

et avec le webconfig suivant :

webconfig.json

{
    "view": "common.htm",
    "variation": "common.json",
    "routes": {
        "/": {
            "view": "index.htm",
            "variation": "index.json"
        },
        "/a-propos/": {
            "view": "about.htm"
        }
    }
}

et ces deux fichiers de variations:

common.json

{
    "titleWebsite": "Titre du site",
    "classCssCommon": "common",
    "classJsCommon": "common"
}

index.json

{
    "titlePage": "Bienvenue",
    "classPage": "index",
    "content": "<p>C'est la page d'accueil.</p>"
}

vous pourez générer avec ces vues :

views/common.htm

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8">
        <title><?- specific.titlePage || "Pas de titre" ?></title>
        <link rel="stylesheet" href="stylesheets/<?= common.classCssCommon ?>.css"  media="all">
        <? if (specific.classPage) { ?>
        <link rel="stylesheet" href="stylesheets/<?= specific.classPage ?>.css"  media="all">
        <? } ?>
    </head>
    <body>
        <!-- Inclure un fichier normalement -->
        <?- include("partials/header.htm") ?>

        <!-- Inclure le fichier vu de `view` -->
        <?- include(routeParameters.view) ?>

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

views/partals/header.htm

    <header>
        <h1><?= specific.titlePage ?></h1>
    </header>

views/index.htm

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

views/about.htm

    <div>
        <h1>NodeAtlas © Haeresis</h1>
    </div>

et obtenir les URLs suivantes :

/

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8">
        <title>Bienvenue</title>
        <link rel="stylesheet" href="stylesheets/common.css"  media="all">
        <link rel="stylesheet" href="stylesheets/index.css"  media="all">
    </head>
    <body>
        <header>
            <h1>Bienvenue</h1>
        </header>
        <div>
            <p>C'est la page d'accueil.</p>
        </div>
        <script async="true" type="text/javascript" src="javascripts/common.js"></script>
    </body>
</html>

/about/

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8">
        <title>Pas de titre</title>
        <link rel="stylesheet" href="stylesheets/common.css"  media="all">
    </head>
    <body>
        <div>
            <h1>NodeAtlas © Haeresis</h1>
        </div>
        <script async="true" type="text/javascript" src="javascripts/common.js"></script>
    </body>
</html>

Partager des dossiers

Il est possible de partager plus de contenu que ce qu'il y a dans le dossier hébergé par assetsRelativePath. Il est tout à fait possible de partager des vues ou des modèles avec la partie cliente par exemple. On appel les fichiers de ses dossiers des fichiers statiques.

Voyons l'exemple suivant :

avec cet ensemble de fichiers

├─ models/
│  └─ user.js
├─ views/
│  └─ index.htm
└─ webconfig.json

webconfig.json

{
    "statics": {
        "/javascripts/models": "models"
    },
    "routes": {
        "/": "index.htm"
    }
}

views/index.htm

<!DOCTYPE html>
    <html lang="fr-fr">
    <head>
        <meta charset="utf-8">
        <title>Statics</title>
    </head>
    <body>
        <div id="user"></div>
        <script src="javascripts/models/user.js"></script>
        <script>
            var user = new User(),
                mount = document.getElementById("user");

            user
                .firstname("Bruno")
                .lastname("Lesieur");

            mount.innerHTML = user.firstname() + " " + user.lastname();
        </script>
    </body>
</html>

models/user.js

(function (expose, factory) {
    if (typeof module !== 'undefined' && module.exports) {
        module.exports = factory;
    } else {
        expose.User = factory;
    }
}(this, function User() {
    var privates = {},
        publics = this;

    publics.lastname = function (lastname) {
        if (typeof lastname === 'undefined') {
            return privates.lastname;
        } else {
            privates.lastname = lastname;
            return publics;
        }
    };

    publics.firstname = function (firstname) {
        if (typeof firstname === 'undefined') {
            return privates.firstname;
        } else {
            privates.firstname = firstname;
            return publics;
        }
    };
}));

Nous pourrons accéder aux fichiers HTML http://localhost/ et au JavaScript http://localhost/javascripts/user.js.

maxAge, Etag, etc.

Il est possible de configurer les informations livrées par NodeAtlas à la demande d'une ressource statique (comme le maxAge, l'Etag, etc.) via la propriété staticOptions. Dans ce cas, on ne fournit plus une string mais un Object et la propriété initiale devient le paramètre path. Par défaut les staticOptions sont celle global au webconfig (voir les options d'Express).

{
    "statics": {
        "/javascripts/models": {
            "path": "models",
            "staticOptions": {
                "index": false
            }
        }
    },
    "routes": {
        "/": "index.htm"
    }
}

Ordonner les routes

Il est également possible de placer ses routes dans un tableau, ce qui permettra de les prioriser lors de leur manipulation ultérieur dans la section des contrôleurs.

Dans ce cas le chemin devient le paramètre virtual.

{
    "statics": [{
        "virtual": "/javascripts/models",
        "path": "models",
        "staticOptions": {
            "index": false
        }
    }],
    "routes": {
        "/": "index.htm"
    }
}

Générer des maquettes HTML

Générer des designs HTML

Avec la configuration suivante il est possible de générer des aperçu HTML du rendu de chaque page dans un fichier associé. Le fichier sera (re)créé à chaque affichage de la page dans votre navigateur.

{
    "htmlGenerationBeforeResponse": true,
    "assetsRelativePath": "../HTML/",
    "serverlessRelativePath": "../HTML/",
    "routes": {
        "/": {
            "view": "index.htm",
            "output": "/index.html"
        },
        "/liste-des-membres/": {
            "view": "members.htm",
            "output": "/membres/liste.html"
        },
        "/liste-des-membres/?foo=bar": {
            "view": "members.htm",
            "output": false
        },
        "/aucun/parametre/output/": {
            "view": "members.htm"
        }
    }
}

et l'ensemble de fichiers suivant :

├─ HTML/
│  ├─ stylesheets/
│  │  ├─ common.css
│  └─ javascripts/
│     └─ common.js
├─ views/
│  ├─ index.htm
│  └─ members.htm
└─ webconfig.json

on peut créer physiquement la sortie suivante :

├─ HTML/
│  ├─ stylesheets/
│  │  └─ common.css
│  ├─ javascripts/
│  │  └─ common.js
│  ├─ index.html
│  ├─ members/
│  │  └─ list.html
│  └─ aucun/
│     └─ parametre/
│        └─ output ⤆ Ceci est un fichier
├─ views/
│  ┊┉
└─ webconfig.json

en se rendant aux adresses :

Note : Il n'y a pas de génération pour « /liste-des-membres/?foo=bar » car output est à false. Utilisez cette valeur pour ignorer des routes à la génération.

La génération s'enclenche quand on affiche la page uniquement parce que htmlGenerationBeforeResponse existe et est à true.

Générer un site sans partie serveur

Il est également possible de gérer la création d'un site en simple page HTML avec la commande --generate.

Si htmlGenerationBeforeResponse est passé à false (ou enlevé) le seul moyen de générer toutes les pages du site sera via la commande node-atlas --generate qui génèrera toutes les pages d'un coup dans le dossier serverlessRelativePath à la condition que le output global soit à true.

De plus avec --generate, l'intégralité du dossier assetsRelativePath (dossier des fichiers publics) sera copié dans le dossier serverlessRelativePath si les deux dossiers n'ont pas un chemin identique à condition que assetsCopy soit à true.

Cela vous permet réellement d'obtenir en sortie dans le dossier de génération des pages « stand-alone » avec l'intégralité des fichiers auxquels elles font appel (CSS / JS / Images, etc.).

Voyons cela avec la configuration suivante :

{
    "output": true,
    "assetsCopy": true,
    "languageCode": "fr-fr",
    "index": true,
    "serverlessRelativePath": "serverless",
    "routes": {
        "/cv.html": {
            "view": "index.htm",
            "variation": "index.json"
        },
        "/en/cv.html": {
            "view": "index.htm",
            "variation": "index.json",
            "languageCode": "en"
        }
    }
}

et l'ensemble de fichiers suivant :

├─ assets/
│  ├─ stylesheets/
│  │  └─ common.css
│  └─ javascripts/
│     └─ common.js
├─ variations/
│  ├─ fr-fr/
│  │  └─ index.json
│  └─ en/
│     └─ index.json
├─ views/
│  └─ index.htm
└─ webconfig.json

Avec node-atlas --browse, à l'adresse http://localhost/ s'affichera la liste des pages composants votre site (grâce à index à true).

Il ne restera plus qu'à, une fois --generate utilisé, admirer votre site HTML dans le dossier :

┊┉
├─ serverless/
│  ├─ stylesheets/
│  │  └─ common.css
│  ├─ javascripts/
│  │  └─ common.js
│  ├─ cv.html
│  └─ en/
│     └─ cv.html
┊┉

Note : Si serverlessRelativePath n'est pas présent dans « webconfig.js », par défaut le dossier des générations est bien serverless/. serverlessRelativePath est donc utile seulement pour changer le nom/chemin répertoire.

Générer les fichiers statics

Les fichiers défini dans statics sont également copiable dans le dossier serverlessRelativePath lors de l'appel à --generate. Pour permettre cela, vous pouvez utiliser pour chaque dossier statique le paramètre output mis à true.

{
    "statics": {
        "/javascripts/models": {
            "path": "models",
            "output": true
        }
    },
}

Moteur de Template EJS

Par défaut, NodeAtlas utilise déjà le moteur de template EJS, c'est ce qui vous permet d'utiliser du JavaScript dans les balises <? et ?>.

Les balises <? et ?> permettent d'inclure du JavaScript au sein même de vos templates. Il existe différentes variantes de la balise vous permettant d'afficher le résultat JavaScript dans votre template (comme vous le feriez avec un document.write). Les voici :

  • <? La balise « Scriptlet » par défaut, pour les structure de contrôle, pas de sortie.
  • <?= Affiché le résultat des expressions dans le template (échappement HTML)
  • <?- Affiché le résultat des expressions dans le template tel quel
  • <?# Balise commentaire, pas d'exécution, pas de sortie
  • <?% Affiche litéralement le contenu d'une <?
  • ?> La balise de fermeture
  • -?> La balise Mode trim, exécute un trim sur les nouvelles lignes

Cependant, EJS fonctionne normalement avec les balises <% et %>. Vous pouvez remettre ces valeurs ou même utiliser celles que vous souhaitez.

{
    "templateEngineDelimiter": "%",
    "routes": {
        "/": {
            "view": "index.ejs"
        }
    }
}

Par exemple, pour inclure une partie de fichier on utilise l'instruction <?- include("partials/head.htm") ?>. Il serait possible de le faire avec <%- include("partials/head") %> avec la configuration ci-dessous :

Voyez l'exemple dans les fichiers ci-dessous :

webconfig.json

{
    "templateEngineDelimiter": "%",
    "variation": "common.json",
    "routes": {
        "/": {
            "view": "index.ejs",
            "variation": "index.json"
        }
    }
}

variations/common.json

{
    "titleWebsite": "Titre du site",
    "classCssCommon": "common",
    "classJsCommon": "common"
}

variations/index.json

{
    "titlePage": "Bienvenue",
    "classPage": "index",
    "content": "<p>C'est la page d'accueil.</p>"
}

views/partials/head.ejs

<!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 %>">

views/partials/foot.ejs

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

views/index.ejs

    <%- include("partials/head") %>

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

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

    <%- include("partials/foot") %>

Pour tout savoir sur les possibilités du moteur de template consultez la documentation EJS

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

Moteur de Template PUG

Il est possible d'utiliser en lieu et place de EJS le moteur de template PUG (anciennement Jade) pour générer les pages et manipuler les variations. Cela est possible pour l'intégralité du site avec par exemple ce webconfig :

{
    "pug": true,
    "routes": {
        "/": {
            "view": "index.pug"
        },
        "/contenu/": {
            "view": "content.pug"
        }
    }
}

ou seulement pour une page précise :

{
    "routes": {
        "/": {
            "view": "index.pug"
        },
        "/contenu/": {
            "pug": true,
            "view": "content.pug"
        }
    }
}

Il est également possible pour un moteur complet en PUG de repasser une page spécifique en EJS.

{
    "pug": true,
    "routes": {
        "/": {
            "pug": false,
            "view": "index.pug"
        },
        "/contenu/": {
            "view": "content.pug"
        }
    }
}

Voyons ce que cela donnerait avec l'exemple suivant :

webconfig.json

{
    "pug": true,
    "view": "common.pug",
    "variation": "common.json",
    "routes": {
        "/": {
            "view": "index.pug",
            "variation": "index.json"
        }
    }
}

variations/common.json

{
    "titleWebsite": "Titre du site",
    "classCssCommon": "common",
    "classJsCommon": "common"
}

variations/index.json

{
    "titlePage": "Bienvenue",
    "classPage": "index",
    "content": "<p>C'est la page d'accueil.</p>"
}

views/common.pug

doctype html
html(lang="fr-fr")
    head
        meta(charset="utf-8")
        title #{specific.titlePage}
        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")
    body(class=specific.classPage)
        include partials/header
        include #{routeParameters.view}
        script(async, type="text/javascript", src="javascripts/" + common.classJsCommon + ".js")

views/partials/header.pug

h1 #{titleWebsite}

views/index.pug

div
    h2 #{specific.titlePage}
    | !{specific.content}

Pour tout savoir sur les possibilités du moteur de template consultez la documentation PUG

Note : Si rien n'est précisé, pug vaut false.