Aller au contenu

template

# *template*: Moteur de Templating (HTML) pour Lua et nginx-module-lua

## Installation

Si vous n'avez pas encore configuré l'abonnement au dépôt RPM, [inscrivez-vous](https://www.getpagespeed.com/repo-subscribe). Ensuite, vous pouvez procéder avec les étapes suivantes.

### CentOS/RHEL 7 ou Amazon Linux 2

```bash
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-template

CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-template

Pour utiliser cette bibliothèque Lua avec NGINX, assurez-vous que nginx-module-lua est installé.

Ce document décrit lua-resty-template v2.0 publié le 24 février 2020.


lua-resty-template est un moteur de templating (1) (HTML) pour Lua et OpenResty.

(1) par compilation, nous entendons que les modèles sont traduits en fonctions Lua que vous pouvez appeler ou string.dump en tant que blobs de bytecode binaire sur disque qui peuvent être utilisés plus tard avec lua-resty-template ou les fonctions standard load et loadfile de Lua (voir aussi Précompilation de Modèle). Bien que, généralement, vous n'ayez pas besoin de faire cela, car lua-resty-template gère cela en arrière-plan.

Bonjour le Monde avec lua-resty-template

local template = require "resty.template"      -- OU
local template = require "resty.template.safe" -- retourne nil, err en cas d'erreurs

-- Utilisation de template.new
local view = template.new "view.html"
view.message = "Bonjour, le Monde !"
view:render()
-- Utilisation de template.render
template.render("view.html", { message = "Bonjour, le Monde !" })
view.html
<!DOCTYPE html>
<html>
<body>
  <h1>{{message}}</h1>
</body>
</html>
Sortie
<!DOCTYPE html>
<html>
<body>
  <h1>Bonjour, le Monde !</h1>
</body>
</html>

La même chose peut être faite avec une chaîne de modèle inline :

-- Utilisation de la chaîne de modèle
template.render([[
<!DOCTYPE html>
<html>
<body>
  <h1>{{message}}</h1>
</body>
</html>]], { message = "Bonjour, le Monde !" })

Contenu

Syntaxe de Modèle

Vous pouvez utiliser les balises suivantes dans les modèles :

  • {{expression}}, écrit le résultat de l'expression - échappé en HTML
  • {*expression*}, écrit le résultat de l'expression
  • {% lua code %}, exécute le code Lua
  • {(template)}, inclut le fichier template, vous pouvez également fournir un contexte pour le fichier inclus {(file.html, { message = "Bonjour, le Monde" })} (REMARQUE : vous ne pouvez pas utiliser de virgule (,) dans file.html, dans ce cas utilisez {["file,with,comma"]} à la place)
  • {[expression]}, inclut le fichier expression (le résultat de l'expression), vous pouvez également fournir un contexte pour le fichier inclus {["file.html", { message = "Bonjour, le Monde" }]}
  • {-block-}...{-block-}, enveloppe à l'intérieur d'un {-block-} à une valeur stockée dans une table blocks avec une clé block (dans ce cas), voir utilisation de blocs. Ne pas utiliser les noms de blocs prédéfinis verbatim et raw.
  • {-verbatim-}...{-verbatim-} et {-raw-}...{-raw-} sont des blocs prédéfinis dont le contenu n'est pas traité par le lua-resty-template mais le contenu est sorti tel quel.
  • {# commentaires #} tout ce qui se trouve entre {# et #} est considéré comme commenté (c'est-à-dire non sorti ou exécuté)

À partir des modèles, vous pouvez accéder à tout dans la table context, et tout dans la table template. Dans les modèles, vous pouvez également accéder à context et template en préfixant les clés.

<h1>{{message}}</h1> == <h1>{{context.message}}</h1>
Syntaxe de Courte Échappement

Si vous ne voulez pas qu'une balise de modèle particulière soit traitée, vous pouvez échapper la balise de début avec un antislash \ :

<h1>\{{message}}</h1>

Cela sortira (au lieu d'évaluer le message) :

<h1>{{message}}</h1>

Si vous voulez ajouter un caractère antislash juste avant la balise de modèle, vous devez également échapper cela :

<h1>\\{{message}}</h1>

Cela sortira :

<h1>\[message-variables-content-here]</h1>
Un Mot sur les Clés Complexes dans la Table de Contexte

Disons que vous avez ce type de table de contexte :

local ctx = {["foo:bar"] = "foobar"}

Et vous voulez rendre la valeur foobar de ctx["foo:bar"] dans votre modèle. Vous devez le spécifier explicitement en référant le context dans votre modèle :

{# {*["foo:bar"]*} ne fonctionnera pas, vous devez utiliser : #}
{*context["foo:bar"]*}

Ou complètement :

template.render([[
{*context["foo:bar"]*}
]], {["foo:bar"] = "foobar"})
Un Mot sur l'Échappement HTML

Seules les chaînes sont échappées, les fonctions sont appelées sans arguments (récursivement) et les résultats sont retournés tels quels, d'autres types sont tostringifiés. Les nils et ngx.nulls sont convertis en chaînes vides "".

Caractères HTML échappés :

  • & devient &amp;
  • < devient &lt;
  • > devient &gt;
  • " devient &quot;
  • ' devient &#39;
  • / devient &#47;

Exemple

Lua
local template = require "resty.template"
template.render("view.html", {
  title   = "Test de lua-resty-template",
  message = "Bonjour, le Monde !",
  names   = { "James", "Jack", "Anne" },
  jquery  = '<script src="js/jquery.min.js"></script>' 
})
view.html
{(header.html)}
<h1>{{message}}</h1>
<ul>
{% for _, name in ipairs(names) do %}
    <li>{{name}}</li>
{% end %}
</ul>
{(footer.html)}
header.html
<!DOCTYPE html>
<html>
<head>
  <title>{{title}}</title>
  {*jquery*}
</head>
<body>
footer.html
</body>
</html>

Clés de Contexte Réservées et Remarques

Il est conseillé de ne pas utiliser ces clés dans vos tables de contexte :

  • ___, contient le modèle compilé, si défini vous devez utiliser {{context.___}}
  • context, contient le contexte actuel, si défini vous devez utiliser {{context.context}}
  • echo, contient la fonction d'aide echo, si définie vous devez utiliser {{context.echo}}
  • include, contient la fonction d'aide include, si définie vous devez utiliser {{context.include}}
  • layout, contient la mise en page par laquelle la vue sera décorée, si définie vous devez utiliser {{context.layout}}
  • blocks, contient les blocs, si définis vous devez utiliser {{context.blocks}} (voir : utilisation de blocs)
  • template, contient la table de modèle, si définie vous devez utiliser {{context.template}}

En plus de cela avec template.new, vous ne devez pas écraser :

  • render, la fonction qui rend une vue, évidemment ;-)

Vous ne devez également pas {(view.html)} de manière récursive :

Lua
template.render "view.html"
view.html
{(view.html)}

Vous pouvez également charger des modèles à partir de "sous-répertoires" avec {(syntax)} :

view.html
{(users/list.html)}

Notez également que vous pouvez fournir un modèle soit sous forme de chemin de fichier, soit sous forme de chaîne. Si le fichier existe, il sera utilisé, sinon la chaîne est utilisée. Voir aussi template.load.

Configuration Nginx / OpenResty

Lorsque lua-resty-template est utilisé dans le contexte de Nginx / OpenResty, il y a quelques directives de configuration dont vous devez être conscient :

  • template_root (set $template_root /var/www/site/templates)
  • template_location (set $template_location /templates)

Si aucune de ces directives n'est définie dans la configuration Nginx, la valeur de ngx.var.document_root (alias directive root) est utilisée. Si template_location est défini, il sera utilisé en premier, et si la localisation retourne autre chose que 200 comme code d'état, nous revenons soit à template_root (si défini) ou à document_root.

Avec lua-resty-template 2.0, il est possible de remplacer $template_root et $template_location avec du code Lua :

local template = require "resty.template".new({
  root     = "/templates",
  location = "/templates" 
})
Utilisation de document_root

Celle-ci essaie de charger le contenu du fichier avec du code Lua à partir du répertoire html (relatif au préfixe Nginx).

http {
  server {
    location / {
      root html;
      content_by_lua '
        local template = require "resty.template"
        template.render("view.html", { message = "Bonjour, le Monde !" })
      ';      
    }
  }
}
Utilisation de template_root

Celle-ci essaie de charger le contenu du fichier avec du code Lua à partir du répertoire /usr/local/openresty/nginx/html/templates.

http {
  server {
    set $template_root /usr/local/openresty/nginx/html/templates;
    location / {
      root html;
      content_by_lua '
        local template = require "resty.template"
        template.render("view.html", { message = "Bonjour, le Monde !" })
      ';      
    }
  }
}
Utilisation de template_location

Celle-ci essaie de charger le contenu avec ngx.location.capture à partir de la localisation /templates (dans ce cas, cela est servi avec le module ngx_static).

http {
  server {
    set $template_location /templates;
    location / {
      root html;
      content_by_lua '
        local template = require "resty.template"
        template.render("view.html", { message = "Bonjour, le Monde !" })
      ';      
    }
    location /templates {
      internal;
      alias html/templates/;
    }    
  }
}

Voir aussi template.load.

API Lua

template.root

Vous pouvez configurer la racine du modèle en définissant cette variable qui sera recherchée pour les fichiers de modèle :

local template = require "resty.template".new({
  root = "/templates"
})
template.render_file("test.html")

Cette propriété remplace celle définie dans la configuration Nginx (set $template_root /my-templates;)

template.location

C'est ce que vous pouvez utiliser avec OpenResty car cela utilisera ngx.location.capture pour récupérer les fichiers de modèles de manière non bloquante.

local template = require "resty.template".new({
  location = "/templates"
})
template.render_file("test.html")

Cette propriété remplace celle définie dans la configuration Nginx (set $template_location /my-templates;)

table template.new(view, layout)

Crée une nouvelle instance de modèle qui est utilisée comme contexte (par défaut) lors de l'appel à render. Une table qui est créée a seulement une méthode render, mais la table a également une métatable avec __tostring définie. Voir l'exemple ci-dessous. Les arguments view et layout peuvent être soit des chaînes, soit des chemins de fichiers, mais la mise en page peut également être une table créée précédemment avec template.new.

Avec 2.0, le nouveau peut également être utilisé sans arguments, ce qui crée une nouvelle instance de modèle :

local template = require "resty.template".new()

Vous pouvez également passer une table qui est ensuite modifiée pour être un modèle :

local config = {
  root = "/templates"
}

local template = require "resty.template".new(config)

Ceci est pratique car le template créé par new ne partage pas le cache avec le modèle global retourné par require "resty.template" (cela a été signalé avec le problème #25).

Vous pouvez également passer un booléen true ou false comme paramètre view, ce qui signifie que soit une version safe soit une version un-safe du modèle est retournée :

local unsafe = require "resty.template"
local safe   = unsafe.new(true)

Il existe également une implémentation safe par défaut disponible :

local safe = require "resty.template.safe"
-- vous pouvez également créer une instance de safe :
local safe_instance = safe.new()

La version safe utilise le modèle de gestion des erreurs return nil, err de Lua et unsafe lance simplement les erreurs, que vous pouvez attraper avec pcall, xpcall ou coroutine.wrap.

Voici des exemples d'utilisation de new avec des arguments :

local view = template.new"template.html"              -- ou
local view = template.new("view.html", "layout.html") -- ou
local view = template.new[[<h1>{{message}}</h1>]]     -- ou
local view = template.new([[<h1>{{message}}</h1>]], [[
<html>
<body>
  {*view*}
</body>
</html>
]])
Exemple
local template = require "resty.template"
local view = template.new"view.html"
view.message  = "Bonjour, le Monde !"
view:render()
-- Vous pouvez également remplacer le contexte lors du rendu
view:render{ title = "Test de lua-resty-template" }
-- Si vous voulez inclure le contexte de la vue dans le contexte de remplacement
view:render(setmetatable({ title = "Test de lua-resty-template" }, { __index = view }))
-- Pour obtenir le modèle rendu sous forme de chaîne, vous pouvez utiliser tostring
local result = tostring(view)

boolean template.caching(boolean ou nil)

Cette fonction active ou désactive la mise en cache des modèles, ou si aucun paramètre n'est passé, retourne l'état actuel de la mise en cache des modèles. Par défaut, la mise en cache des modèles est activée, mais vous voudrez peut-être la désactiver en développement ou dans des situations à faible mémoire.

local template = require "resty.template"   
-- Obtenir l'état actuel de la mise en cache des modèles
local enabled = template.caching()
-- Désactiver la mise en cache des modèles
template.caching(false)
-- Activer la mise en cache des modèles
template.caching(true)

Veuillez noter que si le modèle a déjà été mis en cache lors de la compilation d'un modèle, la version mise en cache sera retournée. Vous voudrez peut-être vider le cache avec template.cache = {} pour vous assurer que votre modèle est vraiment recompilé.

function, boolean template.compile(view, cache_key, plain)

Analyse, compile et met en cache (si la mise en cache est activée) un modèle et retourne le modèle compilé sous forme de fonction qui prend le contexte comme paramètre et retourne le modèle rendu sous forme de chaîne. En option, vous pouvez passer cache_key qui est utilisé comme clé de cache. Si la clé de cache n'est pas fournie, view sera utilisée comme clé de cache. Si la clé de cache est no-cache, le cache du modèle ne sera pas vérifié et la fonction résultante ne sera pas mise en cache. Vous pouvez également passer optionnellement plain avec une valeur de true si le view est une chaîne de texte brut (cela sautera template.load et la détection de chunks binaires dans la phase template.parse). Si plain est false, le modèle est considéré comme un fichier, et tous les problèmes de lecture de fichiers sont considérés comme des erreurs. Si plain est défini sur nil (par défaut), le modèle ne considère pas les erreurs de lecture de fichiers comme fatales, et retourne le view (généralement le chemin du modèle).

local func = template.compile("template.html")          -- ou
local func = template.compile([[<h1>{{message}}</h1>]])
Exemple
local template = require "resty.template"
local func     = template.compile("view.html")
local world    = func{ message = "Bonjour, le Monde !" }
local universe = func{ message = "Bonjour, Univers !" }
print(world, universe)

Notez également la seconde valeur de retour qui est un booléen. Vous pouvez l'ignorer, ou l'utiliser pour déterminer si la fonction retournée a été mise en cache.

function, boolean template.compile_string(view, cache_key)

Cela appelle simplement template.compile(view, cache_key, true)

function, boolean template.compile_file(view, cache_key)

Cela appelle simplement template.compile(view, cache_key, false)

template.visit(func)

Vous permet d'enregistrer des fonctions de visiteur pour le parseur de modèles. Les visiteurs sont appelés dans l'ordre dans lequel ils sont enregistrés. Et une fois enregistrés, ils ne peuvent pas être supprimés du parseur. Peut-être qu'il est plus facile de montrer comment cela fonctionne :

local template = require "resty.template.safe".new()

local i = 0

template.visit(function(content, type, name)
  local trimmed = content:gsub("^%s+", ""):gsub("%s+$", "")
  if trimmed == "" then return content end
  i = i + 1
  print("  visit: ", i)
  if type then print("   type: ", type) end
  if name then print("   name: ", name) end
  print("content: ", trimmed)
  print()
  return content
end)

local func = template.compile([[
Comment ça va, {{user.name}} ?

Voici une nouvelle recette de cuisine pour vous !

{% for i, ingredient in ipairs(ingredients) do %}
  {*i*}. {{ingredient}}
{% end %}
{-ad-}`lua-resty-template` le moteur de templating pour OpenResty !{-ad-}
]])

local content = func{
  user = {
    name = "bungle"
  },
  ingredients = {
    "pommes de terre",
    "saucisses"
  }
}

print(content)

Cela produira la sortie suivante :

  visit: 1
content: Comment ça va,

  visit: 2
   type: {
content: user.name

  visit: 3
content: ?

Voici une nouvelle recette de cuisine pour vous !

  visit: 4
   type: %
content: for i, ingredient in ipairs(ingredients) do

  visit: 5
   type: *
content: i

  visit: 6
content: .

  visit: 7
   type: {
content: ingredient

  visit: 8
   type: %
content: end

  visit: 9
   type: -
   name: ad
content: `lua-resty-template` le moteur de templating pour OpenResty !

  visit: 10
content: `lua-resty-template` le moteur de templating pour OpenResty !

Comment ça va, bungle ?

Voici une nouvelle recette de cuisine pour vous !

  1. pommes de terre
  2. saucisses

Les fonctions de visiteur doivent avoir cette signature :

string function(content, type, name)

Si la fonction ne modifie pas le content, elle doit retourner le content tel quel, comme le visiteur ci-dessus le fait.

Voici un exemple de visiteur un peu plus avancé qui gère les erreurs d'exécution sur les expressions :

local template = require "resty.template".new()

template.render "Calcul : {{i*10}}"

Cela générera une erreur d'exécution avec :

ERREUR : [string "context=... or {}..."]:7: tentative d'effectuer une opération arithmétique sur la variable globale 'i' (une valeur nulle)
traceback :
    resty/template.lua:652: dans la fonction 'render'
    a.lua:52: dans la fonction 'file_gen'
    init_worker_by_lua:45: dans la fonction <init_worker_by_lua:43>
    [C]: dans la fonction 'xpcall'
    init_worker_by_lua:52: dans la fonction 'init_worker_by_lua:50'

Ajoutons maintenant un visiteur qui gère cette erreur :

local template = require "resty.template".new()

template.visit(function(content, type)
  if type == "*" or type == "{" then
    return "select(3, pcall(function() return nil, " .. content .. " end)) or ''"
  end

  return content
end)

template.render "Calcul : {{i*10}}\n"
template.render("Calcul : {{i*10}}\n", { i = 1 })

Cela produira :

Calcul : 
Calcul : 10

string template.process(view, context, cache_key, plain)

Analyse, compile, met en cache (si la mise en cache est activée) et retourne la sortie sous forme de chaîne. Vous pouvez également passer optionnellement cache_key qui est utilisé comme clé de cache. Si plain évalue à true, le view est considéré comme un modèle de chaîne brute (template.load et la détection de chunks binaires sont sautées dans template.parse). Si plain est false, le modèle est considéré comme un fichier, et tous les problèmes de lecture de fichiers sont considérés comme des erreurs. Si plain est défini sur nil (par défaut), le modèle ne considère pas les erreurs de lecture de fichiers comme fatales, et retourne le view.

local output = template.process("template.html", { message = "Bonjour, le Monde !" })          -- ou
local output = template.process([[<h1>{{message}}</h1>]], { message = "Bonjour, le Monde !" })

string template.process_string(view, context, cache_key)

Cela appelle simplement template.process(view, context, cache_key, true)

string template.process_file(view, context, cache_key)

Cela appelle simplement template.process(view, context, cache_key, false)

template.render(view, context, cache_key, plain)

Analyse, compile, met en cache (si la mise en cache est activée) et sort le modèle soit avec ngx.print si disponible, ou print. Vous pouvez également passer optionnellement cache_key qui est utilisé comme clé de cache. Si plain évalue à true, le view est considéré comme un modèle de chaîne brute (template.load et la détection de chunks binaires sont sautées dans template.parse). Si plain est false, le modèle est considéré comme un fichier, et tous les problèmes de lecture de fichiers sont considérés comme des erreurs. Si plain est défini sur nil (par défaut), le modèle ne considère pas les erreurs de lecture de fichiers comme fatales, et retourne le view.

template.render("template.html", { message = "Bonjour, le Monde !" })          -- ou
template.render([[<h1>{{message}}</h1>]], { message = "Bonjour, le Monde !" })

string template.render_string(view, context, cache_key)

Cela appelle simplement template.render(view, context, cache_key, true)

string template.render_file(view, context, cache_key)

Cela appelle simplement template.render(view, context, cache_key, false)

string template.parse(view, plain)

Analyse le fichier ou la chaîne de modèle, et génère une chaîne de modèle analysée. Cela peut être utile lors du débogage des modèles. Vous devez noter que si vous essayez d'analyser un chunk binaire (par exemple, celui retourné avec template.compile), template.parse retournera ce chunk binaire tel quel. Si plain évalue à true, le view est considéré comme un modèle de chaîne brute (template.load et la détection de chunks binaires sont sautées dans template.parse). Si plain est false, le modèle est considéré comme un fichier, et tous les problèmes de lecture de fichiers sont considérés comme des erreurs. Si plain est défini sur nil (par défaut), le modèle ne considère pas les erreurs de lecture de fichiers comme fatales, et retourne le view.

local t1 = template.parse("template.html")
local t2 = template.parse([[<h1>{{message}}</h1>]])

string template.parse_string(view, plain)

Cela appelle simplement template.parse(view, plain, true)

string template.parse_file(view, plain)

Cela appelle simplement template.parse(view, plain, false)

string template.precompile(view, path, strip, plain)

Précompile le modèle en tant que chunk binaire. Ce chunk binaire peut être écrit en tant que fichier (et vous pouvez l'utiliser directement avec load et loadfile de Lua). Pour plus de commodité, vous pouvez également spécifier en option l'argument path pour sortir le chunk binaire dans un fichier. Vous pouvez également fournir le paramètre strip avec la valeur false pour que les modèles précompilés aient également des informations de débogage (par défaut, c'est true). Le dernier paramètre plain signifie que la compilation doit traiter le view comme une chaîne (plain = true) ou comme un chemin de fichier (plain = false) ou essayer d'abord comme un fichier, et revenir à string (plain = nil). Dans le cas où plain=false (un fichier) et qu'il y a une erreur avec file io, la fonction générera également une erreur avec un échec d'assertion.

local view = [[
<h1>{{title}}</h1>
<ul>
{% for _, v in ipairs(context) do %}
    <li>{{v}}</li>
{% end %}
</ul>]]

local compiled = template.precompile(view)

local file = io.open("precompiled-bin.html", "wb")
file:write(compiled)
file:close()

-- Alternativement, vous pourriez simplement écrire (ce qui fait la même chose que ci-dessus)
template.precompile(view, "precompiled-bin.html")

template.render("precompiled-bin.html", {
    title = "Noms",
    "Emma", "James", "Nicholas", "Mary"
})

string template.precompile_string(view, path, strip)

Cela appelle simplement template.precompile(view, path, strip, true).

string template.precompile_file(view, path, strip)

Cela appelle simplement template.precompile(view, path, strip, false).

string template.load(view, plain)

Ce champ est utilisé pour charger des modèles. template.parse appelle cette fonction avant de commencer à analyser le modèle (en supposant que l'argument optionnel plain dans template.parse évalue à false ou nil (par défaut). Par défaut, il y a deux chargeurs dans lua-resty-template : un pour Lua et l'autre pour Nginx / OpenResty. Les utilisateurs peuvent écraser ce champ avec leur propre fonction. Par exemple, vous pouvez vouloir écrire une fonction de chargeur de modèle qui charge des modèles à partir d'une base de données.

Le template.load par défaut pour Lua (attaché comme template.load lorsqu'il est utilisé directement avec Lua) :

function(view, plain)
    if plain == true then return view end
    local path, root = view, template.root
    if root and root ~= EMPTY then
        if byte(root, -1) == SOL then root = sub(root, 1, -2) end
        if byte(view,  1) == SOL then path = sub(view, 2) end
        path = root .. "/" .. path
    end
    return plain == false and assert(read_file(path)) or read_file(path) or view
end

Le template.load par défaut pour Nginx / OpenResty (attaché comme template.load lorsqu'il est utilisé dans le contexte de Nginx / OpenResty) :

function(view, plain)
    if plain == true then return view end
    local vars = VAR_PHASES[phase()]
    local path = view
    local root = template.location
    if (not root or root == EMPTY) and vars then
        root = var.template_location
    end
    if root and root ~= EMPTY then
        if byte(root, -1) == SOL then root = sub(root, 1, -2) end
        if byte(path,  1) == SOL then path = sub(path, 2) end
        path = root .. "/" .. path
        local res = capture(path)
        if res.status == 200 then return res.body end
    end
    path = view
    root = template.root
    if (not root or root == EMPTY) and vars then
        root = var.template_root
        if not root or root == EMPTY then root = var.document_root or prefix end
    end
    if root and root ~= EMPTY then
        if byte(root, -1) == SOL then root = sub(root, 1, -2) end
        if byte(path,  1) == SOL then path = sub(path, 2) end
        path = root .. "/" .. path
    end
    return plain == false and assert(read_file(path)) or read_file(path) or view
end

Comme vous pouvez le voir, lua-resty-template essaie toujours (par défaut) de charger un modèle à partir d'un fichier (ou avec ngx.location.capture) même si vous avez fourni un modèle sous forme de chaîne. lua-resty-template. Mais si vous savez que vos modèles sont toujours des chaînes, et non des chemins de fichiers, vous pouvez utiliser l'argument plain dans template.compile, template.render, et template.parse OU remplacer template.load par le chargeur de modèle le plus simple qui soit (mais soyez conscient que si vos modèles utilisent des inclusions {(file.html)}, celles-ci sont également considérées comme des chaînes, dans ce cas file.html sera la chaîne de modèle qui est analysée) - vous pourriez également configurer un chargeur qui trouve des modèles dans un système de base de données, par exemple Redis :

local template = require "resty.template"
template.load = function(view, plain) return view end

Si le paramètre plain est false (nil n'est pas traité comme false), tous les problèmes de file io sont considérés comme des erreurs d'assertion.

string template.load_string(view)

Cela appelle simplement template.load(view, true)

string template.load_file(view)

Cela appelle simplement template.load(view, false)

template.print

Ce champ contient une fonction qui est utilisée sur template.render() ou template.new("example.html"):render() pour sortir les résultats. Par défaut, cela contient soit ngx.print (si disponible) ou print. Vous pouvez vouloir (et êtes autorisé à) écraser ce champ, si vous voulez utiliser votre propre fonction de sortie à la place. Cela est également utile si vous utilisez un autre framework, par exemple Turbo.lua (http://turbolua.org/).

local template = require "resty.template"

template.print = function(s)
  print(s)
  print("<!-- Sortie par Ma Fonction -->")
end

Précompilation de Modèle

lua-resty-template prend en charge la précompilation des modèles. Cela peut être utile lorsque vous souhaitez sauter l'analyse des modèles (et l'interprétation Lua) en production ou si vous ne voulez pas que vos modèles soient distribués sous forme de fichiers texte brut sur les serveurs de production. De plus, en précompilant, vous pouvez vous assurer que vos modèles ne contiennent rien qui ne puisse pas être compilé (ils sont syntaxiquement valides en Lua). Bien que les modèles soient mis en cache (même sans précompilation), il y a des gains de performance (et de mémoire). Vous pourriez intégrer la précompilation de modèles dans vos scripts de construction (ou de déploiement) (peut-être en tant que tâches Gulp, Grunt ou Ant).

Précompilation du modèle, et sortie en tant que fichier binaire
local template = require "resty.template"
local compiled = template.precompile("example.html", "example-bin.html")
Charger le fichier de modèle précompilé, et l'exécuter avec des paramètres de contexte
local template = require "resty.template"
template.render("example-bin.html", { "Jack", "Mary" })

Aides de Modèle

Aides Intégrées

echo(...)

Écho de la sortie. Cela est utile avec {% .. %} :

require "resty.template".render[[
début
{%
for i=1, 10 do
  echo("\tligne : ", i, "\n")
end
%}
fin
]]

Cela produira :

début
    ligne : 1
    ligne : 2
    ligne : 3
    ligne : 4
    ligne : 5
    ligne : 6
    ligne : 7
    ligne : 8
    ligne : 9
    ligne : 10
fin

Cela peut également être écrit comme mais echo peut être utile dans certains cas :

require "resty.template".render[[
début
{% for i=1, 10 do %}
  ligne : {* i *}
{% end %}
fin
]]

include(view, context)

Ceci est principalement utilisé en interne avec {(view.hmtl)}, {["view.hmtl"]} et avec des blocs {-block-name-}..{-block-name-}. Si context n'est pas donné, le contexte utilisé pour compiler la vue parente est utilisé. Cette fonction compilera le view et appellera la fonction résultante avec context (ou le context de la vue parente si non donné).

Autres Façons d'Étendre

Bien que lua-resty-template n'ait pas beaucoup d'infrastructure ou de moyens de l'étendre, vous avez encore quelques possibilités que vous pouvez essayer.

  • Ajouter des méthodes aux types globaux string et table (ce qui n'est pas encouragé, cependant)
  • Envelopper vos valeurs avec quelque chose avant de les ajouter dans le contexte (par exemple, une table proxy)
  • Créer des fonctions globales
  • Ajouter des fonctions locales soit à la table template soit à la table context
  • Utiliser des métaméthodes dans vos tables

Bien que modifier des types globaux semble pratique, cela peut avoir des effets secondaires désagréables. C'est pourquoi je vous suggère de consulter d'abord ces bibliothèques et articles :

Vous pourriez par exemple ajouter Moses' ou Underscore's _ à la table template ou à la table context.

Exemple
local _ = require "moses"
local template = require "resty.template"
template._ = _

Ensuite, vous pouvez utiliser _ à l'intérieur de vos modèles. J'ai créé un exemple d'aide de modèle qui peut être trouvé ici : https://github.com/bungle/lua-resty-template/blob/master/lib/resty/template/html.lua

Lua
local template = require "resty.template"
local html = require "resty.template.html"

template.render([[
<ul>
{% for _, person in ipairs(context) do %}
    {*html.li(person.name)*}
{% end %}
</ul>
<table>
{% for _, person in ipairs(context) do %}
    <tr data-sort="{{(person.name or ""):lower()}}">
        {*html.td{ id = person.id }(person.name)*}
    </tr>
{% end %}
</table>]], {
    { id = 1, name = "Emma"},
    { id = 2, name = "James" },
    { id = 3, name = "Nicholas" },
    { id = 4 }
})
Sortie
<ul>
    <li>Emma</li>
    <li>James</li>
    <li>Nicholas</li>
    <li />
</ul>
<table>
    <tr data-sort="emma">
        <td id="1">Emma</td>
    </tr>
    <tr data-sort="james">
        <td id="2">James</td>
    </tr>
    <tr data-sort="nicholas">
        <td id="3">Nicholas</td>
    </tr>
    <tr data-sort="">
        <td id="4" />
    </tr>
</table>

Exemples d'Utilisation

Inclusion de Modèle

Vous pouvez inclure des modèles à l'intérieur de modèles avec la syntaxe {(template)} et {(template, context)}. La première utilise le contexte actuel comme contexte pour le modèle inclus, et la seconde le remplace par un nouveau contexte. Voici un exemple d'utilisation des inclusions et du passage d'un contexte différent au fichier inclus :

Lua
local template = require "resty.template"
template.render("include.html", { users = {
    { name = "Jane", age = 29 },
    { name = "John", age = 25 }
}})
include.html
<html>
<body>
<ul>
{% for _, user in ipairs(users) do %}
    {(user.html, user)}
{% end %}
</ul>
</body>
</html>
user.html
<li>L'utilisateur {{name}} a {{age}} ans</li>
Sortie
<html>
<body>
<ul>
    <li>L'utilisateur Jane a 29 ans</li>
    <li>L'utilisateur John a 25 ans</li>
</ul>
</body>
</html>

Vues avec Agencements

Les agencements (ou Pages Maîtres) peuvent être utilisés pour envelopper une vue à l'intérieur d'une autre vue (alias agencement).

Lua
local template = require "resty.template"
local layout   = template.new "layout.html"
layout.title   = "Test de lua-resty-template"
layout.view    = template.compile "view.html" { message = "Bonjour, le Monde !" }
layout:render()
-- Ou comme ceci
template.render("layout.html", {
  title = "Test de lua-resty-template",
  view  = template.compile "view.html" { message = "Bonjour, le Monde !" }
})
-- Ou peut-être que vous aimez plus ce style
-- (mais veuillez vous rappeler que context.view est écrasé lors du rendu de layout.html)
local view     = template.new("view.html", "layout.html")
view.title     = "Test de lua-resty-template"
view.message   = "Bonjour, le Monde !"
view:render()
-- Eh bien, peut-être comme ceci alors ?
local layout   = template.new "layout.html"
layout.title   = "Test de lua-resty-template"
local view     = template.new("view.html", layout)
view.message   = "Bonjour, le Monde !"
view:render()
view.html
<h1>{{message}}</h1>
layout.html
<!DOCTYPE html>
<html>
<head>
    <title>{{title}}</title>
</head>
<body>
    {*view*}
</body>
</html>
Alternativement, vous pouvez également définir l'agencement dans une vue :
Lua
local view     = template.new("view.html", "layout.html")
view.title     = "Test de lua-resty-template"
view.message   = "Bonjour, le Monde !"
view:render()
view.html
{% layout="section.html" %}
<h1>{{message}}</h1>
section.html
<div id="section">
    {*view*}
</div>
layout.html
<!DOCTYPE html>
<html>
<head>
    <title>{{title}}</title>
</head>
<body>
    {*view*}
</body>
</html>
Sortie
<!DOCTYPE html>
<html>
<head>
    <title>Test de lua-resty-template</title>
</head>
<body>
<div id="section">
    <h1>Bonjour, le Monde !</h1>
</div>
</body>
</html>

Utilisation de Blocs

Les blocs peuvent être utilisés pour déplacer différentes parties des vues vers des endroits spécifiques dans les agencements. Les agencements ont des espaces réservés pour les blocs.

Lua
local view     = template.new("view.html", "layout.html")
view.title     = "Test de lua-resty-template blocks"
view.message   = "Bonjour, le Monde !"
view.keywords  = { "test", "lua", "template", "blocks" }
view:render()
view.html
<h1>{{message}}</h1>
{-aside-}
<ul>
    {% for _, keyword in ipairs(keywords) do %}
    <li>{{keyword}}</li>
    {% end %}
</ul>
{-aside-}
layout.html
<!DOCTYPE html>
<html>
<head>
<title>{*title*}</title>
</head>
<body>
<article>
    {*view*}
</article>
{% if blocks.aside then %}
<aside>
    {*blocks.aside*}
</aside>
{% end %}
</body>
</html>
Sortie
<!DOCTYPE html>
<html>
<head>
<title>Test de lua-resty-template blocks</title>
</head>
<body>
<article>
    <h1>Bonjour, le Monde !</h1>
</article>
<aside>
    <ul>
        <li>test</li>
        <li>lua</li>
        <li>template</li>
        <li>blocks</li>
    </ul>
</aside>
</body>
</html>

Héritage Grand-Père-Père-Fils

Disons que vous avez base.html, layout1.html, layout2.html et page.html. Vous voulez un héritage comme ceci : base.html ➡ layout1.html ➡ page.html ou base.html ➡ layout2.html ➡ page.html (en réalité, ce nesting n'est pas limité à trois niveaux).

Lua
local res = require"resty.template".compile("page.html"){} 
base.html
<html lang='zh'>
   <head>
   <link href="css/bootstrap.min.css" rel="stylesheet">
   {* blocks.page_css *}
   </head>
   <body>
   {* blocks.main *}
   <script src="js/jquery.js"></script>
   <script src="js/bootstrap.min.js"></script>
   {* blocks.page_js *}
   </body>
</html>
layout1.html
{% layout = "base.html" %}
{-main-}
    <div class="sidebar-1">
      {* blocks.sidebar *}
    </div>
    <div class="content-1">
      {* blocks.content *}
    </div>
{-main-}
layout2.html
{% layout = "base.html" %}
{-main-}
    <div class="sidebar-2">
      {* blocks.sidebar *}
    </div>
    <div class="content-2">
      {* blocks.content *}
    </div>
    <div>Je suis différent de layout1 </div>
{-main-}
page.html
{% layout = "layout1.html" %}
{-sidebar-}
  ceci est la barre latérale
{-sidebar-}

{-content-}
  ceci est le contenu
{-content-}

{-page_css-}
  <link href="css/page.css" rel="stylesheet">
{-page_css-}

{-page_js-}
  <script src="js/page.js"></script>
{-page_js-}

Ou :

page.html
{% layout = "layout2.html" %}
{-sidebar-}
  ceci est la barre latérale
{-sidebar-}

{-content-}
  ceci est le contenu
{-content-}

{-page_css-}
  <link href="css/page.css" rel="stylesheet">
{-page_css-}

{-page_js-}
  <script src="js/page.js"></script>
{-page_js-}

Macros

@DDarko a mentionné dans un problème #5 qu'il a un cas d'utilisation où il a besoin d'avoir des macros ou des vues paramétrées. C'est une belle fonctionnalité que vous pouvez utiliser avec lua-resty-template.

Pour utiliser des macros, définissons d'abord un peu de code Lua :

template.render("macro.html", {
    item = "original",
    items = { a = "original-a", b = "original-b" } 
})

Et le macro-example.html :

{% local string_macro = [[
<div>{{item}}</div>
]] %}
{* template.compile(string_macro)(context) *}
{* template.compile(string_macro){ item = "string-macro-context" } *}

Cela produira :

<div>original</div>
<div>string-macro-context</div>

Ajoutons maintenant une macro de fonction, dans macro-example.html (vous pouvez omettre local si vous le souhaitez) :

{% local function_macro = function(var, el)
    el = el or "div"
    return "<" .. el .. ">{{" .. var .. "}}</" .. el .. ">\n"
end %}

{* template.compile(function_macro("item"))(context) *}
{* template.compile(function_macro("a", "span"))(items) *}

Cela produira :

<div>original</div>
<span>original-a</span>

Mais c'est encore plus flexible, essayons une autre macro de fonction :

{% local function function_macro2(var)
    return template.compile("<div>{{" .. var .. "}}</div>\n")
end %}
{* function_macro2 "item" (context) *}
{* function_macro2 "b" (items) *}

Cela produira :

<div>original</div>
<div>original-b</div>

Et voici une autre :

{% function function_macro3(var, ctx)
    return template.compile("<div>{{" .. var .. "}}</div>\n")(ctx or context)
end %}
{* function_macro3("item") *}
{* function_macro3("a", items) *}
{* function_macro3("b", items) *}
{* function_macro3("b", { b = "b-from-new-context" }) *}

Cela produira :

<div>original</div>
<div>original-a</div>
<div>original-b</div>
<div>b-from-new-context</div>

Les macros sont vraiment flexibles. Vous pouvez avoir des générateurs de formulaires et d'autres macros d'aide pour avoir une sortie de modèle réutilisable et paramétrée. Une chose que vous devez savoir est qu'à l'intérieur des blocs de code (entre {% et %}), vous ne pouvez pas avoir %}, mais vous pouvez contourner cela en utilisant la concaténation de chaînes "%" .. "}".

Appel de Méthodes dans les Modèles

Vous pouvez également appeler des méthodes de chaînes (ou d'autres fonctions de table) dans les modèles.

Lua
local template = require "resty.template"
template.render([[
<h1>{{header:upper()}}</h1>
]], { header = "bonjour, le monde !" })
Sortie
<h1>BONJOUR, LE MONDE !</h1>

Intégration d'Angular ou d'autres balises / templating à l'intérieur des Modèles

Parfois, vous devez mélanger et assortir d'autres modèles (par exemple, des modèles JavaScript côté client comme Angular) avec des modèles lua-resty. Disons que vous avez ce type de modèle Angular :

<html ng-app>
 <body ng-controller="MyController">
   <input ng-model="foo" value="bar">
   <button ng-click="changeFoo()">{{buttonText}}</button>
   <script src="angular.js">
 </body>
</html>

Maintenant, vous pouvez voir qu'il y a {{buttonText}} qui est vraiment pour le templating Angular, et non pour lua-resty-template. Vous pouvez corriger cela en enveloppant soit tout le code avec {-verbatim-} ou {-raw-} ou seulement les parties que vous voulez :

{-raw-}
<html ng-app>
 <body ng-controller="MyController">
   <input ng-model="foo" value="bar">
   <button ng-click="changeFoo()">{{buttonText}}</button>
   <script src="angular.js">
 </body>
</html>
{-raw-}

ou (voir le {(head.html)} est traité par lua-resty-template) :

<html ng-app>
 {(head.html)}
 <body ng-controller="MyController">
   <input ng-model="foo" value="bar">
   <button ng-click="changeFoo()">{-raw-}{{buttonText}}{-raw-}</button>
   <script src="angular.js">
 </body>
</html>

Vous pouvez également utiliser la syntaxe de courte échappement :

...
<button ng-click="changeFoo()">\{{buttonText}}</button>
...

Intégration de Markdown à l'intérieur des Modèles

Si vous souhaitez intégrer la syntaxe Markdown (et SmartyPants) à l'intérieur de vos modèles, vous pouvez le faire en utilisant par exemple lua-resty-hoedown (cela dépend de LuaJIT). Voici un exemple d'utilisation :

Lua
local template = require "resty.template"
template.markdown = require "resty.hoedown"

template.render[=[
<html>
<body>
{*markdown[[
#Bonjour, le Monde

Test de Markdown.
]]*}
</body>
</html>
]=]
Sortie
<html>
<body>
<h1>Bonjour, le Monde</h1>

<p>Test de Markdown.</p>
</body>
</html>

Vous pouvez également ajouter des paramètres de configuration qui sont documentés dans le projet lua-resty-hoedown. Disons que vous souhaitez également utiliser SmartyPants :

Lua
local template = require "resty.template"
template.markdown = require "resty.hoedown"

template.render[=[
<html>
<body>
{*markdown([[
#Bonjour, le Monde

Test de Markdown avec "SmartyPants"...
]], { smartypants = true })*}
</body>
</html>
]=]
Sortie
<html>
<body>
<h1>Bonjour, le Monde</h1>

<p>Test de Markdown avec &ldquo;SmartyPants&rdquo;&hellip;</p>
</body>
</html>

Vous pouvez également vouloir ajouter une couche de mise en cache pour vos Markdown, ou des fonctions d'aide au lieu de placer directement la bibliothèque Hoedown comme fonction d'aide dans template.

Pages Serveur Lua (LSP) avec OpenResty

Les Pages Serveur Lua ou LSP sont similaires aux traditionnelles PHP ou Microsoft Active Server Pages (ASP) où vous pouvez simplement placer des fichiers de code source dans votre racine de document (de votre serveur web) et les faire traiter par les compilateurs des langages respectifs (PHP, VBScript, JScript, etc.). Vous pouvez émuler assez étroitement ce qui est parfois appelé un style de développement spaghetti, facilement avec lua-resty-template. Ceux qui ont fait du développement ASP.NET Web Forms connaissent le concept de fichiers Code Behind. Il y a quelque chose de similaire, mais cette fois nous l'appelons Layout en Front ici (vous pouvez inclure des modules Lua avec des appels require normaux si vous le souhaitez dans les LSP). Pour vous aider à comprendre les concepts, prenons un petit exemple :

nginx.conf :
http {
  init_by_lua '
    require "resty.core"
    template = require "resty.template"
    template.caching(false); -- vous pouvez supprimer cela en production
  ';
  server {
    location ~ \.lsp$ {
      default_type text/html;
      content_by_lua 'template.render(ngx.var.uri)';
    }
  }
}

La configuration ci-dessus crée une variable globale template dans l'environnement Lua (vous ne voudrez peut-être pas cela). Nous avons également créé une localisation pour correspondre à tous les fichiers (ou localisations) .lsp, puis nous rendons simplement le modèle.

Imaginons que la requête soit pour index.lsp.

index.lsp
{%
layout = "layouts/default.lsp"
local title = "Bonjour, le Monde !"
%}
<h1>{{title}}</h1>

Ici, vous pouvez voir que ce fichier inclut un peu de vue (<h1>{{title}}</h1>) en plus de quelques codes Lua que nous voulons exécuter. Si vous souhaitez avoir un fichier de code pur avec Layout en Front, alors n'écrivez simplement aucun code de vue dans ce fichier. La variable layout est déjà définie dans les vues comme documenté ailleurs dans cette documentation. Maintenant, voyons les autres fichiers aussi.

layouts/default.lsp
<html>
{(include/header.lsp)}
<body>
{*view*}
</body>
</html>

Ici, nous avons un agencement pour décorer le index.lsp, mais nous avons également une inclusion ici, alors regardons-la.

include/header.lsp
<head>
  <title>Test des Pages Serveur Lua</title>
</head>

Des choses statiques ici seulement.

Sortie

La sortie finale ressemblera à ceci :

<html>
<head>
  <title>Test des Pages Serveur Lua</title>
</head>
<body>
  <h1>Bonjour, le Monde !</h1>
</body>
</html>

Comme vous pouvez le voir, lua-resty-template peut être assez flexible et facile à démarrer. Il suffit de placer des fichiers sous votre racine de document et d'utiliser le style de développement normal de sauvegarde et de rafraîchissement. Le serveur choisira automatiquement les nouveaux fichiers et rechargera les modèles (si la mise en cache est désactivée) lors de la sauvegarde.

Si vous souhaitez passer des variables aux agencements ou aux inclusions, vous pouvez ajouter des éléments à la table de contexte (dans l'exemple ci-dessous, voir context.title) :

{%
layout = "layouts/default.lsp"
local title = "Bonjour, le Monde !"
context.title = 'Mon Application - ' .. title
%}
<h1>{{title}}</h1>

FAQ

Comment puis-je vider le cache des modèles

lua-resty-template met automatiquement en cache (si la mise en cache est activée) les fonctions de modèle résultantes dans la table template.cache. Vous pouvez vider le cache en émettant template.cache = {}.

Où est utilisé lua-resty-template

  • jd.com – Jingdong Mall (Chinois : 京东商城; pinyin : Jīngdōng Shāngchéng), anciennement 360Buy, est une entreprise chinoise de commerce électronique

Veuillez me faire savoir s'il y a des erreurs ou des informations obsolètes dans cette liste.

Alternatives

Vous pouvez également consulter ces (comme alternatives, ou pour les mélanger avec lua-resty-template) :

lua-resty-template a été initialement forké à partir de tirtemplate.lua de Tor Hveem qu'il avait extrait du framework web Tir de Zed Shaw (http://tir.mongrel2.org/). Merci Tor, et Zed pour vos contributions antérieures.

Benchmarks

Il y a un petit microbenchmark situé ici : https://github.com/bungle/lua-resty-template/blob/master/lib/resty/template/microbenchmark.lua

Il y a également une régression dans LuaJIT qui affecte les résultats. Si vous voulez que votre LuaJIT soit patché contre cela, vous devez fusionner cette demande de tirage : https://github.com/LuaJIT/LuaJIT/pull/174.

D'autres ont signalé que dans des benchmarks simples, l'exécution de ce moteur de modèle bat en fait Nginx servant des fichiers statiques par un facteur de trois. Donc je suppose que ce moteur est assez rapide.

Lua
local benchmark = require "resty.template.microbenchmark"
benchmark.run()
-- Vous pouvez également passer le nombre d'itérations (par défaut, c'est 1 000)
benchmark.run(100)

Voici quelques résultats de mon bureau (vieux Mac Pro 2010) :

<lua|luajit|resty> -e 'require "resty.template.microbenchmark".run()'
`

Exécution de 1000 itérations dans chaque test
    Temps d'Analyse : 0.010759
Temps de Compilation : 0.054640 (modèle)
Temps de Compilation : 0.000213 (modèle, mis en cache)
  Temps d'Exécution : 0.061851 (même modèle)
  Temps d'Exécution : 0.006722 (même modèle, mis en cache)
  Temps d'Exécution : 0.092698 (modèle différent)
  Temps d'Exécution : 0.009537 (modèle différent, mis en cache)
  Temps d'Exécution : 0.092452 (modèle différent, contexte différent)
  Temps d'Exécution : 0.010106 (modèle différent, contexte différent, mis en cache)
      Temps Total : 0.338978
Exécution de 1000 itérations dans chaque test
    Temps d'Analyse : 0.011633
Temps de Compilation : 0.060598 (modèle)
Temps de Compilation : 0.000243 (modèle, mis en cache)
  Temps d'Exécution : 0.068009 (même modèle)
  Temps d'Exécution : 0.007307 (même modèle, mis en cache)
  Temps d'Exécution : 0.071339 (modèle différent)
  Temps d'Exécution : 0.007150 (modèle différent, mis en cache)
  Temps d'Exécution : 0.066766 (modèle différent, contexte différent)
  Temps d'Exécution : 0.006940 (modèle différent, contexte différent, mis en cache)
      Temps Total : 0.299985
Exécution de 1000 itérations dans chaque test
    Temps d'Analyse : 0.012458
Temps de Compilation : 0.050013 (modèle)
Temps de Compilation : 0.000249 (modèle, mis en cache)
  Temps d'Exécution : 0.057579 (même modèle)
  Temps d'Exécution : 0.006959 (même modèle, mis en cache)
  Temps d'Exécution : 0.065352 (modèle différent)
  Temps d'Exécution : 0.007133 (modèle différent, mis en cache)
  Temps d'Exécution : 0.060965 (modèle différent, contexte différent)
  Temps d'Exécution : 0.007726 (modèle différent, contexte différent, mis en cache)
      Temps Total : 0.268434
Exécution de 1000 itérations dans chaque test
    Temps d'Analyse : 0.009466
Temps de Compilation : 0.053116 (modèle)
Temps de Compilation : 0.000209 (modèle, mis en cache)
  Temps d'Exécution : 0.059017 (même modèle)
  Temps d'Exécution : 0.006129 (même modèle, mis en cache)
  Temps d'Exécution : 0.061882 (modèle différent)
  Temps d'Exécution : 0.006613 (modèle différent, mis en cache)
  Temps d'Exécution : 0.059104 (modèle différent, contexte différent)
  Temps d'Exécution : 0.005761 (modèle différent, contexte différent, mis en cache)
      Temps Total : 0.261297
Exécution de 1000 itérations dans chaque test
    Temps d'Analyse : 0.005198
Temps de Compilation : 0.029687 (modèle)
Temps de Compilation : 0.000082 (modèle, mis en cache)
  Temps d'Exécution : 0.033824 (même modèle)
  Temps d'Exécution : 0.003130 (même modèle, mis en cache)
  Temps d'Exécution : 0.075899 (modèle différent)
  Temps d'Exécution : 0.007027 (modèle différent, mis en cache)
  Temps d'Exécution : 0.070269 (modèle différent, contexte différent)
  Temps d'Exécution : 0.007456 (modèle différent, contexte différent, mis en cache)
      Temps Total : 0.232572
Exécution de 1000 itérations dans chaque test
    Temps d'Analyse : 0.003647
Temps de Compilation : 0.027145 (modèle)
Temps de Compilation : 0.000083 (modèle, mis en cache)
  Temps d'Exécution : 0.034685 (même modèle)
  Temps d'Exécution : 0.002801 (même modèle, mis en cache)
  Temps d'Exécution : 0.073466 (modèle différent)
  Temps d'Exécution : 0.010836 (modèle différent, mis en cache)
  Temps d'Exécution : 0.068790 (modèle différent, contexte différent)
  Temps d'Exécution : 0.009818 (modèle différent, contexte différent, mis en cache)
      Temps Total : 0.231271
resty (resty 0.23, version nginx : openresty/1.15.8.2)

``` Exécution de 1000 itérations dans chaque test Temps d'Analyse : 0.003980 Temps de Compilation : 0.025983 (modèle) Temps de Compilation : 0.000066 (modèle, mis en cache) Temps d'Exécution : 0.032752 (même modèle) Temps d'Exécution :