mde / ejs

Embedded JavaScript templates -- http://ejs.co
Apache License 2.0
7.76k stars 842 forks source link

Variable Scope #582

Open jimbo8098 opened 3 years ago

jimbo8098 commented 3 years ago

Hi folks,

I've already posted this on StackOverflow but I figured I'd post it here since it doesn't seem there's anything related.

I have made a templater for Nginx configurations within Nodejs which uses EJS to template the configurations. Here is a simplified version of the code I'm using to generate the config:

const yaml = require('js-yaml');
const fs = require('fs');
const ejs = require('ejs');
let NBeautifier = require('nginxbeautify');

fs.readFile('params.yml',(err,ymldata) => {
    //data = JSON.parse(data);
    data = yaml.load(ymldata).sites[0];
    console.log(data.servers[0].locations)
    console.log(data.servers[0])
    //console.log(data)
    ejs.renderFile('./templates/site.conf.ejs',data,{root: './templates'})
        .then((result) => {
            var beautified = new NBeautifier().parse(result)
            console.log(beautified)
        })
        .catch(console.error)
})

And here's the site.conf.js file:

<% if(typeof(servers) !== 'undefined') { servers.forEach((server) => { %><%- include("/sections/server.ejs",server) %>
<% })}%>

server.conf.ejs

server {

    <% if(typeof(locations) === 'object') { locations.forEach((location) => { %>
    <%- include('/sections/location.ejs',location) _%>

    <% })} %>

    <% if(typeof(access_log) === 'object') { %><%- include('/prefabs/log.ejs',{type: 'access_log',location: access_log.location, format: access_log.format}) %><% } %>
    <% if(typeof(error_log) === 'object') { %><%- include('/prefabs/log.ejs',Object.assign(error_log,{type: 'error_log'})) %><% } %>
}

And finally log.ejs

<%- type %> <%- location %><% if(format) { %> <%- format %><%}%>;

The output here is:

[
  { location: '~* /.*.php$', deny_addresses: [ 'all' ] },
  { location: '/', proxy: { pass: 'http://upstreamref' } }
]
{
  server_name: 'test.com',
  listen: [
    { port: 443, ssl: 'on', arguments: [Array] },
    { port: 443, ssl: 'on', address: '[::]', arguments: [Array] }
  ],
  deny_addresses: [ 'all' ],
  locations: [
    { location: '~* /.*.php$', deny_addresses: [Array] },
    { location: '/', proxy: [Object] }
  ],
  access_log: { location: '/dev/null1', format: null },
  error_log: { location: '/dev/null2', format: 'warn' }
}

server
{

        location ~* /.*.php$
        {
                ...
                access_log /dev/null1;
                error_log /dev/null2 warn;
                ...
        }

        location /
        {
                ...
                access_log /dev/null1;
                error_log /dev/null2 warn;
                ...
        }

        access_log /dev/null1;
        error_log /dev/null2 warn;
}

Now what's confusing me is how the console.log output for data.servers[0].locations shows there is no access_log definition yet it is within the output configuration file. The value used is the one from data.servers[0] instead. I assume this is scope related since the access_log property has the same name in each case. I have tried using arrow functions (el) => {} and old-style function(el) {} to attempt to rule out scopes through that with no desirable effect and I've tried excluding Object.assign and replacing it with an anonymous object defined like:

{type: 'access_log',location: access_log.location, format: access_log.format}

instead. This resulted in the same so I think the scope is the problem here.

When I assign a value to access_log within location that overrides the value provided by the server block and works as expected but I shouldn't need to do that.

Our end goal is to output the formatted nginx config like so:

server
{

        location ~* /.*.php$
        {
                ...
                ...
        }

        location /
        {
                ...
                ...
        }

        access_log /dev/null1;
        error_log /dev/null2 warn;
}

Any ideas where I'm going wrong here?

jimbo8098 commented 3 years ago

Through my tests today, I've tried:

It would seem my hunch was right, that this is scope related, but I'm not sure exactly why. I only need access to the variables passed to the view from the include function, I don't need the locals passed to the render function. Is there a way I can even enforce that?