Saltar a contenido

template: Motor de Plantillas (HTML) para Lua y nginx-module-lua

Instalación

Si no has configurado la suscripción al repositorio RPM, regístrate. Luego puedes proceder con los siguientes pasos.

CentOS/RHEL 7 o Amazon Linux 2

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

Para usar esta biblioteca Lua con NGINX, asegúrate de que nginx-module-lua esté instalado.

Este documento describe lua-resty-template v2.0 lanzado el 24 de febrero de 2020.


lua-resty-template es un motor de plantillas (1) (HTML) para Lua y OpenResty.

(1) con compilación nos referimos a que las plantillas se traducen en funciones Lua que puedes llamar o string.dump como blobs de bytecode binario en disco que pueden ser utilizados más tarde con lua-resty-template o funciones estándar de Lua load y loadfile (ver también Precompilación de Plantillas). Aunque, generalmente no necesitas hacer eso ya que lua-resty-template maneja esto en segundo plano.

Hola Mundo con lua-resty-template

local template = require "resty.template"      -- O
local template = require "resty.template.safe" -- devuelve nil, err en caso de errores

-- Usando template.new
local view = template.new "view.html"
view.message = "¡Hola, Mundo!"
view:render()
-- Usando template.render
template.render("view.html", { message = "¡Hola, Mundo!" })
view.html
<!DOCTYPE html>
<html>
<body>
  <h1>{{message}}</h1>
</body>
</html>
Salida
<!DOCTYPE html>
<html>
<body>
  <h1>¡Hola, Mundo!</h1>
</body>
</html>

Lo mismo se puede hacer con una cadena de plantilla en línea:

-- Usando cadena de plantilla
template.render([[
<!DOCTYPE html>
<html>
<body>
  <h1>{{message}}</h1>
</body>
</html>]], { message = "¡Hola, Mundo!" })

Contenidos

Sintaxis de Plantillas

Puedes usar las siguientes etiquetas en las plantillas:

  • {{expresión}}, escribe el resultado de la expresión - escapado en html
  • {*expresión*}, escribe el resultado de la expresión
  • {% código lua %}, ejecuta código Lua
  • {(plantilla)}, incluye el archivo plantilla, también puedes proporcionar contexto para el archivo incluido {(file.html, { message = "¡Hola, Mundo!" })} (NOTA: no puedes usar coma (,) en file.html, en ese caso usa {["file,with,comma"]} en su lugar)
  • {[expresión]}, incluye el archivo expresión (el resultado de la expresión), también puedes proporcionar contexto para el archivo incluido {["file.html", { message = "¡Hola, Mundo!" }]}
  • {-bloque-}...{-bloque-}, envuelve dentro de un {-bloque-} a un valor almacenado en una tabla blocks con una clave bloque (en este caso), ver usando bloques. No uses nombres de bloques predefinidos verbatim y raw.
  • {-verbatim-}...{-verbatim-} y {-raw-}...{-raw-} son bloques predefinidos cuyo contenido no es procesado por el lua-resty-template pero el contenido se imprime tal cual.
  • {# comentarios #} todo lo que esté entre {# y #} se considera comentario (es decir, no se imprime ni se ejecuta)

Desde las plantillas puedes acceder a todo en la tabla context, y a todo en la tabla template. En las plantillas también puedes acceder a context y template prefijando las claves.

<h1>{{message}}</h1> == <h1>{{context.message}}</h1>
Sintaxis de Escape Corto

Si no deseas que una etiqueta de plantilla particular sea procesada, puedes escapar la etiqueta de inicio con una barra invertida \:

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

Esto imprimirá (en lugar de evaluar el mensaje):

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

Si deseas agregar un carácter de barra invertida justo antes de la etiqueta de plantilla, también necesitas escapar eso:

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

Esto imprimirá:

<h1>\[contenido-de-variables-message-aquí]</h1>
Una Palabra Sobre Claves Complejas en la Tabla de Contexto

Supongamos que tienes este tipo de tabla de contexto:

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

Y quieres renderizar el valor de ctx["foo:bar"] que es foobar en tu plantilla. Debes especificarlo explícitamente haciendo referencia al context en tu plantilla:

{# {*["foo:bar"]*} no funcionará, necesitas usar: #}
{*context["foo:bar"]*}

O en su totalidad:

template.render([[
{*context["foo:bar"]*}
]], {["foo:bar"] = "foobar"})
Una Palabra Sobre Escapado HTML

Solo se escapan cadenas, las funciones se llaman sin argumentos (recursivamente) y los resultados se devuelven tal cual, otros tipos se convierten a tostring. Los nils y ngx.nulls se convierten en cadenas vacías "".

Caracteres HTML escapados:

  • & se convierte en &amp;
  • < se convierte en &lt;
  • > se convierte en &gt;
  • " se convierte en &quot;
  • ' se convierte en &#39;
  • / se convierte en &#47;

Ejemplo

Lua
local template = require "resty.template"
template.render("view.html", {
  title   = "Prueba de lua-resty-template",
  message = "¡Hola, Mundo!",
  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>

Claves de Contexto Reservadas y Observaciones

Se recomienda que no uses estas claves en tus tablas de contexto:

  • ___, contiene la plantilla compilada, si se establece debes usar {{context.___}}
  • context, contiene el contexto actual, si se establece debes usar {{context.context}}
  • echo, contiene la función auxiliar echo, si se establece debes usar {{context.echo}}
  • include, contiene la función auxiliar include, si se establece debes usar {{context.include}}
  • layout, contiene el diseño con el cual se decorará la vista, si se establece debes usar {{context.layout}}
  • blocks, contiene los bloques, si se establece debes usar {{context.blocks}} (ver: usando bloques)
  • template, contiene la tabla de plantillas, si se establece debes usar {{context.template}}

Además de eso, con template.new no debes sobrescribir:

  • render, la función que renderiza una vista, ¡obviamente!

Tampoco debes {(view.html)} recursivamente:

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

También puedes cargar plantillas desde "subdirectorios" con {(sintaxis)}:

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

También ten en cuenta que puedes proporcionar la plantilla ya sea como una ruta de archivo o como una cadena. Si el archivo existe, se usará, de lo contrario se usará la cadena. Ver también template.load.

Configuración de Nginx / OpenResty

Cuando se usa lua-resty-template en el contexto de Nginx / OpenResty, hay algunas directivas de configuración que debes tener en cuenta:

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

Si ninguna de estas se establece en la configuración de Nginx, se usará el valor de ngx.var.document_root (también conocido como directiva root). Si template_location está establecido, se usará primero, y si la ubicación devuelve algo diferente a 200 como código de estado, volveremos a template_root (si está definido) o document_root.

Con lua-resty-template 2.0 es posible sobrescribir $template_root y $template_location con código Lua:

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

Este intenta cargar el contenido del archivo con código Lua desde el directorio html (relativo al prefijo de Nginx).

http {
  server {
    location / {
      root html;
      content_by_lua '
        local template = require "resty.template"
        template.render("view.html", { message = "¡Hola, Mundo!" })
      ';      
    }
  }
}
Usando template_root

Este intenta cargar el contenido del archivo con código Lua desde el directorio /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 = "¡Hola, Mundo!" })
      ';      
    }
  }
}
Usando template_location

Este intenta cargar contenido con ngx.location.capture desde la ubicación /templates (en este caso esto se sirve con el módulo ngx_static).

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

Ver también template.load.

API de Lua

template.root

Puedes configurar la raíz de la plantilla estableciendo esta variable que se buscará para archivos de plantilla:

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

Esta propiedad anula la que se establece en la configuración de Nginx (set $template_root /my-templates;)

template.location

Esto es lo que puedes usar con OpenResty ya que usará ngx.location.capture para obtener archivos de plantillas de manera no bloqueante.

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

Esta propiedad anula la que se establece en la configuración de Nginx (set $template_location /my-templates;)

table template.new(view, layout)

Crea una nueva instancia de plantilla que se usa como contexto (predeterminado) cuando se renderiza. Una tabla que se crea tiene solo un método render, pero la tabla también tiene una metatable con __tostring definida. Ver el ejemplo a continuación. Tanto los argumentos view como layout pueden ser cadenas o rutas de archivos, pero el diseño también puede ser una tabla creada previamente con template.new.

Con 2.0, new también se puede usar sin argumentos, lo que crea una nueva instancia de plantilla:

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

También puedes pasar una tabla que luego se modifica para ser una plantilla:

local config = {
  root = "/templates"
}

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

Esto es útil ya que la template creada por new no comparte la caché con la plantilla global devuelta por require "resty.template" (esto se informó con el problema #25).

También puedes pasar un booleano true o false como parámetro view, lo que significa que se devuelve la versión safe o un-safe de la plantilla:

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

También hay una implementación safe predeterminada disponible:

local safe = require "resty.template.safe"
-- también puedes crear una instancia de safe:
local safe_instance = safe.new()

La versión safe utiliza el patrón de manejo de errores de Lua return nil, err y unsafe simplemente lanza los errores, que puedes capturar con pcall, xpcall o coroutine.wrap.

Aquí hay ejemplos de uso de new con argumentos:

local view = template.new"template.html"              -- o
local view = template.new("view.html", "layout.html") -- o
local view = template.new[[<h1>{{message}}</h1>]]     -- o
local view = template.new([[<h1>{{message}}</h1>]], [[
<html>
<body>
  {*view*}
</body>
</html>
]])
Ejemplo
local template = require "resty.template"
local view = template.new"view.html"
view.message  = "¡Hola, Mundo!"
view:render()
-- También puedes reemplazar el contexto al renderizar
view:render{ title = "Prueba de lua-resty-template" }
-- Si deseas incluir el contexto de la vista en el contexto de reemplazo
view:render(setmetatable({ title = "Prueba de lua-resty-template" }, { __index = view }))
-- Para obtener la plantilla renderizada como una cadena, puedes usar tostring
local result = tostring(view)

boolean template.caching(boolean or nil)

Esta función habilita o deshabilita la caché de plantillas, o si no se pasan parámetros, devuelve el estado actual de la caché de plantillas. Por defecto, la caché de plantillas está habilitada, pero puede que desees deshabilitarla en situaciones de desarrollo o de baja memoria.

local template = require "resty.template"   
-- Obtener el estado actual de la caché de plantillas
local enabled = template.caching()
-- Deshabilitar la caché de plantillas
template.caching(false)
-- Habilitar la caché de plantillas
template.caching(true)

Ten en cuenta que si la plantilla ya estaba en caché al compilar una plantilla, se devolverá la versión en caché. Puede que desees vaciar la caché con template.cache = {} para asegurarte de que tu plantilla realmente se recompila.

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

Analiza, compila y almacena en caché (si la caché está habilitada) una plantilla y devuelve la plantilla compilada como una función que toma el contexto como parámetro y devuelve la plantilla renderizada como una cadena. Opcionalmente, puedes pasar cache_key que se usa como clave de caché. Si no se proporciona la clave de caché, se usará view como clave de caché. Si la clave de caché es no-cache, no se verificará la caché de plantillas y la función resultante no se almacenará en caché. También puedes pasar opcionalmente plain con un valor de true si el view es una cadena de texto simple (esto omitirá template.load y la detección de fragmentos binarios en la fase template.parse). Si plain es false, la plantilla se considera un archivo, y todos los problemas con la lectura de archivos se consideran errores. Si plain se establece en nil (el valor predeterminado), la plantilla no considera los errores de lectura de archivos como fatales y devuelve el view (generalmente la ruta de la plantilla).

local func = template.compile("template.html")          -- o
local func = template.compile([[<h1>{{message}}</h1>]])
Ejemplo
local template = require "resty.template"
local func     = template.compile("view.html")
local world    = func{ message = "¡Hola, Mundo!" }
local universe = func{ message = "¡Hola, Universo!" }
print(world, universe)

También ten en cuenta el segundo valor de retorno que es un booleano. Puedes descartarlo o usarlo para determinar si la función devuelta fue almacenada en caché.

function, boolean template.compile_string(view, cache_key)

Esto simplemente llama a template.compile(view, cache_key, true)

function, boolean template.compile_file(view, cache_key)

Esto simplemente llama a template.compile(view, cache_key, false)

template.visit(func)

Te permite registrar funciones visitantes del analizador de plantillas. Los visitantes se llaman en el orden en que se registran. Y una vez registrados, no se pueden eliminar del analizador. Quizás sea más fácil mostrar cómo funciona:

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([[
¿Cómo estás, {{user.name}}?

¡Aquí tienes una nueva receta de cocina para ti!

{% for i, ingredient in ipairs(ingredients) do %}
  {*i*}. {{ingredient}}
{% end %}
{-ad-}`lua-resty-template` ¡el motor de plantillas para OpenResty!{-ad-}
]])

local content = func{
  user = {
    name = "bungle"
  },
  ingredients = {
    "papas",
    "salchichas"
  }
}

print(content)

Esto producirá la siguiente salida:

  visit: 1
content: ¿Cómo estás,

  visit: 2
   type: {
content: user.name

  visit: 3
content: ?

¡Aquí tienes una nueva receta de cocina para ti!

  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` ¡el motor de plantillas para OpenResty!

  visit: 10
content: `lua-resty-template` ¡el motor de plantillas para OpenResty!

¿Cómo estás, bungle?

¡Aquí tienes una nueva receta de cocina para ti!

  1. papas
  2. salchichas

Las funciones visitantes deben tener esta firma:

string function(content, type, name)

Si la función no modifica el content, debe devolver el content de vuelta, como lo hace el visitante anterior.

Aquí hay un ejemplo de visitante un poco más avanzado que maneja errores en tiempo de ejecución en expresiones:

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

template.render "Cálculo: {{i*10}}"

Esto generará un error en tiempo de ejecución con:

ERROR: [string "context=... or {}..."]:7: intento de realizar aritmética en el global 'i' (un valor nil)
stack traceback:
    resty/template.lua:652: en la función 'render'
    a.lua:52: en la función 'file_gen'
    init_worker_by_lua:45: en la función <init_worker_by_lua:43>
    [C]: en la función 'xpcall'
    init_worker_by_lua:52: en la función <init_worker_by_lua:50>

Ahora agreguemos un visitante que maneje este error:

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 "Cálculo: {{i*10}}\n"
template.render("Cálculo: {{i*10}}\n", { i = 1 })

Esto producirá:

Cálculo: 
Cálculo: 10

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

Analiza, compila, almacena en caché (si la caché está habilitada) y devuelve la salida como cadena. También puedes pasar opcionalmente cache_key que se usa como clave de caché. Si plain evalúa como true, la view se considera una plantilla de cadena simple (template.load y la detección de fragmentos binarios se omiten en template.parse). Si plain es false, la plantilla se considera un archivo, y todos los problemas con la lectura de archivos se consideran errores. Si plain se establece en nil (el valor predeterminado), la plantilla no considera los errores de lectura de archivos como fatales y devuelve el view.

local output = template.process("template.html", { message = "¡Hola, Mundo!" })          -- o
local output = template.process([[<h1>{{message}}</h1>]], { message = "¡Hola, Mundo!" })

string template.process_string(view, context, cache_key)

Esto simplemente llama a template.process(view, context, cache_key, true)

string template.process_file(view, context, cache_key)

Esto simplemente llama a template.process(view, context, cache_key, false)

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

Analiza, compila, almacena en caché (si la caché está habilitada) y imprime la plantilla ya sea con ngx.print si está disponible, o print. También puedes pasar opcionalmente cache_key que se usa como clave de caché. Si plain evalúa como true, la view se considera una plantilla de cadena simple (template.load y la detección de fragmentos binarios se omiten en template.parse). Si plain es false, la plantilla se considera un archivo, y todos los problemas con la lectura de archivos se consideran errores. Si plain se establece en nil (el valor predeterminado), la plantilla no considera los errores de lectura de archivos como fatales y devuelve el view.

template.render("template.html", { message = "¡Hola, Mundo!" })          -- o
template.render([[<h1>{{message}}</h1>]], { message = "¡Hola, Mundo!" })

string template.render_string(view, context, cache_key)

Esto simplemente llama a template.render(view, context, cache_key, true)

string template.render_file(view, context, cache_key)

Esto simplemente llama a template.render(view, context, cache_key, false)

string template.parse(view, plain)

Analiza el archivo o cadena de plantilla, y genera una cadena de plantilla analizada. Esto puede ser útil al depurar plantillas. Debes tener en cuenta que si intentas analizar un fragmento binario (por ejemplo, uno devuelto con template.compile), template.parse devolverá ese fragmento binario tal cual. Si plain evalúa como true, la view se considera una plantilla de cadena simple (template.load y la detección de fragmentos binarios se omiten en template.parse). Si plain es false, la plantilla se considera un archivo, y todos los problemas con la lectura de archivos se consideran errores. Si plain se establece en nil (el valor predeterminado), la plantilla no considera los errores de lectura de archivos como fatales y devuelve el view.

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

string template.parse_string(view, plain)

Esto simplemente llama a template.parse(view, plain, true)

string template.parse_file(view, plain)

Esto simplemente llama a template.parse(view, plain, false)

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

Precompila la plantilla como un fragmento binario. Este fragmento binario puede escribirse como un archivo (y puedes usarlo directamente con load y loadfile de Lua). Para conveniencia, puedes opcionalmente especificar el argumento path para escribir el fragmento binario en un archivo. También puedes proporcionar el parámetro strip con un valor de false para que las plantillas precompiladas tengan información de depuración (el valor predeterminado es true). El último parámetro plain significa que la compilación debe tratar la view como cadena (plain = true) o como ruta de archivo (plain = false) o intentar primero como archivo, y volver a cadena (plain = nil). En caso de que plain=false (un archivo) y haya un error con file io, la función también generará un error con un fallo de aserción.

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

-- Alternativamente, podrías simplemente escribir (lo que hace lo mismo que arriba)
template.precompile(view, "precompiled-bin.html")

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

string template.precompile_string(view, path, strip)

Esto simplemente llama a template.precompile(view, path, strip, true).

string template.precompile_file(view, path, strip)

Esto simplemente llama a template.precompile(view, path, strip, false).

string template.load(view, plain)

Este campo se utiliza para cargar plantillas. template.parse llama a esta función antes de comenzar a analizar la plantilla (suponiendo que el argumento opcional plain en template.parse evalúa como false o nil (el valor predeterminado). Por defecto, hay dos cargadores en lua-resty-template: uno para Lua y el otro para Nginx / OpenResty. Los usuarios pueden sobrescribir este campo con su propia función. Por ejemplo, puedes querer escribir una función cargadora de plantillas que cargue plantillas desde una base de datos.

El template.load predeterminado para Lua (adjunto como template.load cuando se usa directamente con 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

El template.load predeterminado para Nginx / OpenResty (adjunto como template.load cuando se usa en el contexto 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

Como puedes ver, lua-resty-template siempre intenta (por defecto) cargar una plantilla desde un archivo (o con ngx.location.capture) incluso si proporcionaste la plantilla como una cadena. lua-resty-template. Pero si sabes que tus plantillas son siempre cadenas y no rutas de archivos, puedes usar el argumento plain en template.compile, template.render, y template.parse O reemplazar template.load con el cargador de plantillas más simple que existe (pero ten en cuenta que si tus plantillas usan inclusiones {(file.html)}, esas también se consideran cadenas, en este caso file.html será la cadena de plantilla que se analiza) - también podrías configurar un cargador que encuentre plantillas en algún sistema de base de datos, por ejemplo, Redis:

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

Si el parámetro plain es false (nil no se trata como false), todos los problemas con file io se consideran errores de aserción.

string template.load_string(view)

Esto simplemente llama a template.load(view, true)

string template.load_file(view)

Esto simplemente llama a template.load(view, false)

template.print

Este campo contiene una función que se utiliza en template.render() o template.new("example.html"):render() para imprimir los resultados. Por defecto, esto contiene ya sea ngx.print (si está disponible) o print. Puedes querer (y se te permite) sobrescribir este campo, si deseas usar tu propia función de salida en su lugar. Esto también es útil si estás usando algún otro marco, por ejemplo, Turbo.lua (http://turbolua.org/).

local template = require "resty.template"

template.print = function(s)
  print(s)
  print("<!-- Salida por Mi Función -->")
end

Precompilación de Plantillas

lua-resty-template admite la precompilación de plantillas. Esto puede ser útil cuando deseas omitir el análisis de plantillas (y la interpretación de Lua) en producción o si no deseas que tus plantillas se distribuyan como archivos de texto plano en servidores de producción. También al precompilar, puedes asegurarte de que tus plantillas no contengan algo que no se pueda compilar (son sintácticamente válidas en Lua). Aunque las plantillas están en caché (incluso sin precompilación), hay algunas ganancias de rendimiento (y memoria). Podrías integrar la precompilación de plantillas en tus scripts de construcción (o despliegue) (quizás como tareas de Gulp, Grunt o Ant).

Precompilando la plantilla y saliéndola como un archivo binario
local template = require "resty.template"
local compiled = template.precompile("example.html", "example-bin.html")
Cargar el archivo de plantilla precompilado y ejecutarlo con parámetros de contexto
local template = require "resty.template"
template.render("example-bin.html", { "Jack", "Mary" })

Ayudantes de Plantillas

Ayudantes Incorporados

echo(...)

Imprime salida. Esto es útil con {% .. %}:

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

Esto producirá:

comenzar
    linea: 1
    linea: 2
    linea: 3
    linea: 4
    linea: 5
    linea: 6
    linea: 7
    linea: 8
    linea: 9
    linea: 10
fin

Esto también se puede escribir así, pero echo puede ser útil en algunos casos:

require "resty.template".render[[
comenzar
{% for i=1, 10 do %}
  linea: {* i *}
{% end %}
fin
]]

include(view, context)

Esto se utiliza principalmente internamente con {(view.html)}, {["view.html"]} y con bloques {-nombre-bloque-}..{-nombre-bloque-}. Si no se proporciona context, se utiliza el contexto usado para compilar la vista principal. Esta función compilará la view y llamará a la función resultante con context (o el context de la vista principal si no se proporciona).

Otras Maneras de Extender

Si bien lua-resty-template no tiene mucha infraestructura o formas de extenderlo, aún tienes algunas posibilidades que puedes probar.

  • Agregar métodos a los tipos globales string y table (aunque no se recomienda)
  • Envolver tus valores con algo antes de agregarlos al contexto (por ejemplo, tabla proxy)
  • Crear funciones globales
  • Agregar funciones locales ya sea a la tabla template o a la tabla context
  • Usar metatablas en tus tablas

Si bien modificar tipos globales parece conveniente, puede tener efectos secundarios desagradables. Por eso te sugiero que mires estas bibliotecas y artículos primero:

Podrías, por ejemplo, agregar _ de Moses o Underscore a la tabla de plantilla o a la tabla de contexto.

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

Entonces puedes usar _ dentro de tus plantillas. Creé un ayudante de plantilla de ejemplo que se puede encontrar aquí: 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 }
})
Salida
<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>

Ejemplos de Uso

Inclusión de Plantillas

Puedes incluir plantillas dentro de plantillas con la sintaxis {(plantilla)} y {(plantilla, contexto)}. La primera usa el contexto actual como contexto para la plantilla incluida, y la segunda lo reemplaza con un nuevo contexto. Aquí hay un ejemplo de uso de inclusiones y pasando un contexto diferente al archivo incluido:

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>El usuario {{name}} tiene {{age}} años</li>
Salida
<html>
<body>
<ul>
    <li>El usuario Jane tiene 29 años</li>
    <li>El usuario John tiene 25 años</li>
</ul>
</body>
</html>

Vistas con Diseños

Los diseños (o Páginas Maestras) se pueden usar para envolver una vista dentro de otra vista (también conocida como diseño).

Lua
local template = require "resty.template"
local layout   = template.new "layout.html"
layout.title   = "Prueba de lua-resty-template"
layout.view    = template.compile "view.html" { message = "¡Hola, Mundo!" }
layout:render()
-- O así
template.render("layout.html", {
  title = "Prueba de lua-resty-template",
  view  = template.compile "view.html" { message = "¡Hola, Mundo!" }
})
-- O tal vez te guste más este estilo
-- (pero recuerda que context.view se sobrescribe al renderizar layout.html)
local view     = template.new("view.html", "layout.html")
view.title     = "Prueba de lua-resty-template"
view.message   = "¡Hola, Mundo!"
view:render()
-- Bueno, ¿quizás así?
local layout   = template.new "layout.html"
layout.title   = "Prueba de lua-resty-template"
local view     = template.new("view.html", layout)
view.message   = "¡Hola, Mundo!"
view:render()
view.html
<h1>{{message}}</h1>
layout.html
<!DOCTYPE html>
<html>
<head>
    <title>{{title}}</title>
</head>
<body>
    {*view*}
</body>
</html>
Alternativamente, también puedes definir el diseño en una vista:
Lua
local view     = template.new("view.html", "layout.html")
view.title     = "Prueba de lua-resty-template"
view.message   = "¡Hola, Mundo!"
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>
Salida
<!DOCTYPE html>
<html>
<head>
    <title>Prueba de lua-resty-template</title>
</head>
<body>
<div id="section">
    <h1>¡Hola, Mundo!</h1>
</div>
</body>
</html>

Usando Bloques

Los bloques se pueden usar para mover diferentes partes de las vistas a lugares específicos en los diseños. Los diseños tienen marcadores de posición para bloques.

Lua
local view     = template.new("view.html", "layout.html")
view.title     = "Prueba de bloques de lua-resty-template"
view.message   = "¡Hola, Mundo!"
view.keywords  = { "prueba", "lua", "plantilla", "bloques" }
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>
Salida
<!DOCTYPE html>
<html>
<head>
<title>Prueba de bloques de lua-resty-template</title>
</head>
<body>
<article>
    <h1>¡Hola, Mundo!</h1>
</article>
<aside>
    <ul>
        <li>prueba</li>
        <li>lua</li>
        <li>plantilla</li>
        <li>bloques</li>
    </ul>
</aside>
</body>
</html>

Herencia Abuelo-Padre-Hijo

Supongamos que tienes base.html, layout1.html, layout2.html y page.html. Quieres una herencia como esta: base.html ➡ layout1.html ➡ page.html o base.html ➡ layout2.html ➡ page.html (en realidad, este anidamiento no está limitado a tres niveles).

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>Soy diferente de layout1 </div>
{-main-}
page.html
{% layout = "layout1.html" %}
{-sidebar-}
  este es el sidebar
{-sidebar-}

{-content-}
  este es el contenido
{-content-}

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

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

O:

page.html
{% layout = "layout2.html" %}
{-sidebar-}
  este es el sidebar
{-sidebar-}

{-content-}
  este es el contenido
{-content-}

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

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

Macros

@DDarko mencionó en un issue #5 que tiene un caso de uso donde necesita tener macros o vistas parametrizadas. Esa es una buena característica que puedes usar con lua-resty-template.

Para usar macros, primero definamos algo de código Lua:

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

Y el macro-example.html:

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

Esto producirá:

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

Ahora agreguemos una macro de función, en macro-example.html (puedes omitir local si lo deseas):

{% 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) *}

Esto producirá:

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

Pero esto es aún más flexible, probemos otra macro de función:

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

Esto producirá:

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

Y aquí hay otra:

{% 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-de-nuevo-contexto" }) *}

Esto producirá:

<div>original</div>
<div>original-a</div>
<div>original-b</div>
<div>b-de-nuevo-contexto</div>

Las macros son realmente flexibles. Puedes tener renderizadores de formularios y otros macros de ayuda para tener una salida de plantilla reutilizable y parametrizada. Una cosa que debes saber es que dentro de los bloques de código (entre {% y %}) no puedes tener %}, pero puedes solucionar esto usando concatenación de cadenas "%" .. "}".

Llamando Métodos en Plantillas

También puedes llamar a métodos de cadenas (u otras funciones de tabla) en las plantillas.

Lua
local template = require "resty.template"
template.render([[
<h1>{{header:upper()}}</h1>
]], { header = "¡hola, mundo!" })
Salida
<h1>¡HOLA, MUNDO!</h1>

Incorporando Angular u otras etiquetas / plantillas dentro de las Plantillas

A veces necesitas mezclar y combinar otras plantillas (como plantillas de JavaScript del lado del cliente como Angular) con plantillas del lado del servidor lua-resty-templates. Supongamos que tienes este tipo de plantilla 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>

Ahora puedes ver que hay {{buttonText}} que realmente es para la plantilla de Angular, y no para lua-resty-template. Puedes solucionar esto envolviendo todo el código con {-verbatim-} o {-raw-} o solo las partes que deseas:

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

o (ver el {(head.html)} es procesado por 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>

También puedes usar la sintaxis de escape corta:

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

Incorporando Markdown dentro de las Plantillas

Si deseas incorporar sintaxis de Markdown (y SmartyPants) dentro de tus plantillas, puedes hacerlo usando, por ejemplo, lua-resty-hoedown (depende de LuaJIT). Aquí hay un ejemplo de uso de eso:

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

template.render[=[
<html>
<body>
{*markdown[[
#¡Hola, Mundo!

Probando Markdown.
]]*}
</body>
</html>
]=]
Salida
<html>
<body>
<h1>¡Hola, Mundo!</h1>

<p>Probando Markdown.</p>
</body>
</html>

También puedes agregar parámetros de configuración que están documentados en el proyecto lua-resty-hoedown. Supongamos que también deseas usar SmartyPants:

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

template.render[=[
<html>
<body>
{*markdown([[
#¡Hola, Mundo!

Probando Markdown con "SmartyPants"...
]], { smartypants = true })*}
</body>
</html>
]=]
Salida
<html>
<body>
<h1>¡Hola, Mundo!</h1>

<p>Probando Markdown con &ldquo;SmartyPants&rdquo;&hellip;</p>
</body>
</html>

También puedes querer agregar una capa de caché para tus Markdown, o funciones de ayuda en lugar de colocar la biblioteca Hoedown directamente como una función auxiliar en template.

Páginas de Servidor Lua (LSP) con OpenResty

Las Páginas de Servidor Lua o LSP son similares a las tradicionales PHP o Microsoft Active Server Pages (ASP) donde puedes simplemente colocar archivos de código fuente en tu raíz de documentos (de tu servidor web) y hacer que sean procesados por compiladores de los respectivos lenguajes (PHP, VBScript, JScript, etc.). Puedes emular bastante de cerca esto, a veces llamado estilo de desarrollo espagueti, fácilmente con lua-resty-template. Aquellos que han estado haciendo desarrollo con ASP.NET Web Forms, conocen el concepto de archivos Code Behind. Hay algo similar, pero esta vez lo llamamos Diseño en Frontal aquí (puedes incluir módulos Lua con llamadas normales require si lo deseas en LSPs). Para ayudarte a entender los conceptos, veamos un pequeño ejemplo:

nginx.conf:
http {
  init_by_lua '
    require "resty.core"
    template = require "resty.template"
    template.caching(false); -- puedes eliminar esto en producción
  ';
  server {
    location ~ \.lsp$ {
      default_type text/html;
      content_by_lua 'template.render(ngx.var.uri)';
    }
  }
}

La configuración anterior crea una variable global template en el entorno Lua (puede que no desees eso). También creamos una ubicación para coincidir con todos los archivos (o ubicaciones) .lsp, y luego simplemente renderizamos la plantilla.

Imaginemos que la solicitud es para index.lsp.

index.lsp
{%
layout = "layouts/default.lsp"
local title = "¡Hola, Mundo!"
%}
<h1>{{title}}</h1>

Aquí puedes ver que este archivo incluye un poco de vista (<h1>{{title}}</h1>) además de algo de código Lua que queremos ejecutar. Si deseas tener un archivo de código puro con Diseño en Frontal, entonces simplemente no escribas ningún código de vista en este archivo. La variable layout ya está definida en las vistas como se documenta en otra parte de esta documentación. Ahora veamos los otros archivos también.

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

Aquí tenemos un diseño para decorar el index.lsp, pero también tenemos una inclusión aquí, así que veamos.

include/header.lsp
<head>
  <title>Probando Páginas de Servidor Lua</title>
</head>

Solo cosas estáticas aquí.

Salida

La salida final se verá así:

<html>
<head>
  <title>Probando Páginas de Servidor Lua</title>
</head>
<body>
  <h1>¡Hola, Mundo!</h1>
</body>
</html>

Como puedes ver, lua-resty-template puede ser bastante flexible y fácil de comenzar. Simplemente coloca archivos bajo tu raíz de documentos y usa el estilo normal de guardar y refrescar para el desarrollo. El servidor automáticamente recogerá los nuevos archivos y recargará las plantillas (si la caché está desactivada) al guardar.

Si deseas pasar variables a diseños o inclusiones, puedes agregar cosas a la tabla de contexto (en el ejemplo a continuación ver context.title):

{%
layout = "layouts/default.lsp"
local title = "¡Hola, Mundo!"
context.title = 'Mi Aplicación - ' .. title
%}
<h1>{{title}}</h1>

FAQ

¿Cómo limpio la caché de la plantilla?

lua-resty-template almacena automáticamente en caché (si la caché está habilitada) las funciones de plantilla resultantes en la tabla template.cache. Puedes limpiar la caché emitiendo template.cache = {}.

¿Dónde se usa lua-resty-template?

  • jd.com – Jingdong Mall (chino: 京东商城; pinyin: Jīngdōng Shāngchéng), anteriormente 360Buy, es una empresa china de comercio electrónico.

Por favor, házmelo saber si hay errores o información antigua en esta lista.

Alternativas

También puedes mirar estas (como alternativas, o para mezclarlas con lua-resty-template):

lua-resty-template fue originalmente bifurcado de tirtemplate.lua de Tor Hveem que había extraído de Zed Shaw's Tir web framework (http://tir.mongrel2.org/). Gracias Tor y Zed por sus contribuciones anteriores.

Puntos de Referencia

Hay un pequeño microbenchmark ubicado aquí: https://github.com/bungle/lua-resty-template/blob/master/lib/resty/template/microbenchmark.lua

También hay una regresión en LuaJIT que afecta los resultados. Si deseas que tu LuaJIT esté parcheado contra esto, necesitas fusionar esta solicitud de extracción: https://github.com/LuaJIT/LuaJIT/pull/174.

Otros han informado que en benchmarks simples, ejecutar este motor de plantillas realmente supera a Nginx sirviendo archivos estáticos por un factor de tres. Así que supongo que este motor es bastante rápido.

Lua
local benchmark = require "resty.template.microbenchmark"
benchmark.run()
-- También puedes pasar el conteo de iteraciones (por defecto es 1,000)
benchmark.run(100)

Aquí hay algunos resultados de mi escritorio (viejo Mac Pro de 2010):

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

Ejecutando 1000 iteraciones en cada prueba
    Tiempo de Análisis: 0.010759
Tiempo de Compilación: 0.054640 (plantilla)
Tiempo de Compilación: 0.000213 (plantilla, en caché)
  Tiempo de Ejecución: 0.061851 (misma plantilla)
  Tiempo de Ejecución: 0.006722 (misma plantilla, en caché)
  Tiempo de Ejecución: 0.092698 (plantilla diferente)
  Tiempo de Ejecución: 0.009537 (plantilla diferente, en caché)
  Tiempo de Ejecución: 0.092452 (plantilla diferente, contexto diferente)
  Tiempo de Ejecución: 0.010106 (plantilla diferente, contexto diferente, en caché)
      Tiempo Total: 0.338978
Ejecutando 1000 iteraciones en cada prueba
    Tiempo de Análisis: 0.011633
Tiempo de Compilación: 0.060598 (plantilla)
Tiempo de Compilación: 0.000243 (plantilla, en caché)
  Tiempo de Ejecución: 0.068009 (misma plantilla)
  Tiempo de Ejecución: 0.007307 (misma plantilla, en caché)
  Tiempo de Ejecución: 0.071339 (plantilla diferente)
  Tiempo de Ejecución: 0.007150 (plantilla diferente, en caché)
  Tiempo de Ejecución: 0.066766 (plantilla diferente, contexto diferente)
  Tiempo de Ejecución: 0.006940 (plantilla diferente, contexto diferente, en caché)
      Tiempo Total: 0.299985
Ejecutando 1000 iteraciones en cada prueba
    Tiempo de Análisis: 0.012458
Tiempo de Compilación: 0.050013 (plantilla)
Tiempo de Compilación: 0.000249 (plantilla, en caché)
  Tiempo de Ejecución: 0.057579 (misma plantilla)
  Tiempo de Ejecución: 0.006959 (misma plantilla, en caché)
  Tiempo de Ejecución: 0.065352 (plantilla diferente)
  Tiempo de Ejecución: 0.007133 (plantilla diferente, en caché)
  Tiempo de Ejecución: 0.060965 (plantilla diferente, contexto diferente)
  Tiempo de Ejecución: 0.007726 (plantilla diferente, contexto diferente, en caché)
      Tiempo Total: 0.268434
Ejecutando 1000 iteraciones en cada prueba
    Tiempo de Análisis: 0.009466
Tiempo de Compilación: 0.053116 (plantilla)
Tiempo de Compilación: 0.000209 (plantilla, en caché)
  Tiempo de Ejecución: 0.059017 (misma plantilla)
  Tiempo de Ejecución: 0.006129 (misma plantilla, en caché)
  Tiempo de Ejecución: 0.061882 (plantilla diferente)
  Tiempo de Ejecución: 0.006613 (plantilla diferente, en caché)
  Tiempo de Ejecución: 0.059104 (plantilla diferente, contexto diferente)
  Tiempo de Ejecución: 0.005761 (plantilla diferente, contexto diferente, en caché)
      Tiempo Total: 0.261297
Ejecutando 1000 iteraciones en cada prueba
    Tiempo de Análisis: 0.005198
Tiempo de Compilación: 0.029687 (plantilla)
Tiempo de Compilación: 0.000082 (plantilla, en caché)
  Tiempo de Ejecución: 0.033824 (misma plantilla)
  Tiempo de Ejecución: 0.003130 (misma plantilla, en caché)
  Tiempo de Ejecución: 0.075899 (plantilla diferente)
  Tiempo de Ejecución: 0.007027 (plantilla diferente, en caché)
  Tiempo de Ejecución: 0.070269 (plantilla diferente, contexto diferente)
  Tiempo de Ejecución: 0.007456 (plantilla diferente, contexto diferente, en caché)
      Tiempo Total: 0.232572
Ejecutando 1000 iteraciones en cada prueba
    Tiempo de Análisis: 0.003647
Tiempo de Compilación: 0.027145 (plantilla)
Tiempo de Compilación: 0.000083 (plantilla, en caché)
  Tiempo de Ejecución: 0.034685 (misma plantilla)
  Tiempo de Ejecución: 0.002801 (misma plantilla, en caché)
  Tiempo de Ejecución: 0.073466 (plantilla diferente)
  Tiempo de Ejecución: 0.010836 (plantilla diferente, en caché)
  Tiempo de Ejecución: 0.068790 (plantilla diferente, contexto diferente)
  Tiempo de Ejecución: 0.009818 (plantilla diferente, contexto diferente, en caché)
      Tiempo Total: 0.231271
resty (resty 0.23, versión de nginx: openresty/1.15.8.2)
Ejecutando 1000 iteraciones en cada prueba
    Tiempo de Análisis: 0.003980
Tiempo de Compilación: 0.025983 (plantilla)
Tiempo de Compilación: 0.000066 (plantilla, en caché)
  Tiempo de Ejecución: 0.032752 (misma plantilla)
  Tiempo de Ejecución: 0.002740 (misma plantilla, en caché)
  Tiempo de Ejecución: 0.036111 (plantilla diferente)
  Tiempo de Ejecución: 0.005559 (plantilla diferente, en caché)
  Tiempo de Ejecución: 0.032453 (plantilla diferente, contexto diferente)
  Tiempo de Ejecución: 0.006057 (plantilla diferente, contexto diferente, en caché)
      Tiempo Total: 0.145701

Aún no he comparado los resultados con las alternativas.

Cambios

Los cambios de cada lanzamiento de este módulo se registran en el archivo Changes.md.

Ver También

Hoja de Ruta

Algunas cosas que yo y la comunidad deseamos agregar:

  • Mejores capacidades de dep