Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Separate template literals from logic with module exports

I'm experimenting with HTML String template literals (Electron which uses NodeJS).

It works well but it looks like a mess. What I miss is the separation between HTML and logic.

Here is what I've got

module.exports = {
  snippet: (value1, value2) => {
    return `
<div>
  <h1>Hello ${value1 + ' - ' + value2}</h1>
</div>
`;
  },
};

Suggested output

What I wish I could do is like below (which does not work):

module.exports = {
  snippet: (value1, value2) => {
    return require('snippet.html')(value1, value2);
  },
};
<div>
  <h1>Hello ${value1 + ' - ' + value2}</h1>
</div>

Why it does not work

A string is flat and can't take arguments

I could get the HTML from the HTML file as a String. The problem is that it will be "flat" and I can't use the logic inside the template literals anymore.

Eval is evil and slow

I've read it's possible to run HTML as a String through eval to make it work again. I've also read that it's not good for security and that it's slow.

Other notes

  • I use nodeJS so I need to use module.exports in the js file.
  • I use nested snippets so it's important that template literals still work as it should. It should be able to take variables, functions and so on.
  • I don't mind new ES6 syntax.
  • I'm aware of Vue, React and other frameworks. I want to keep it small and without the hassle to setup a server this time.

Update - A tiny bit better, but not enough

I've now split logic from the template. Still, both are in the same file which makes the html less readable.

module.exports = {
  snippet: (value1, value2) => {
    /* Possible to put whatever logic in here */
    return html(value1, value2);
  },
};

function html(value1, value2) {
  return `
<div>
  <h1>Hello ${value1 + ' - ' + value2}</h1>
</div>
`;
}
like image 344
Jens Törnell Avatar asked Jun 01 '20 07:06

Jens Törnell


People also ask

How do you use Backticks in template literals?

In that case, the template literal is passed to your tag function, where you can then perform whatever operations you want on the different parts of the template literal. To escape a backtick in a template literal, put a backslash ( \ ) before the backtick. Dollar signs can be escaped as well to prevent interpolation.

Can you concatenate template literals?

When you use regular template literals, your input is passed to a default function that concatenates it into a single string. An interesting thing is that you can change it by preceding the template literal with your function name that acts as a tag.

What is ${} in JS?

With template literals, an expression can be embedded in a placeholder. A placeholder is represented by ${} , with anything within the curly brackets treated as JavaScript and anything outside the brackets treated as a string: const method = 'interpolation' const dynamicString = `This string is using ${method}.

Can you use module exports with import?

The export declaration is used to export values from a JavaScript module. Exported values can then be imported into other programs with the import declaration or dynamic import.


1 Answers

  1. I'm aware of Vue, React and other frameworks. I want to keep it small and without the hassle to setup a server this time.

    Developing your own template framework is in no way a hassle free task. I don't know what do you mean by other frameworks, but as you put this phrase along with monsters like Vue and React, I must guess that you do not know about small and simple template libraries. I'm not sure about current state of affairs, but last time I checked libraries like ones from this list or this list were quite small and simple to use

  2. Or maybe you'll find this approach compelling https://jonsuh.com/blog/javascript-templating-without-a-library/

  3. You don't really have to use module.exports on latest NodeJS versions

  4. If you OK with your Update but the only issue is that template is in the same file as snippet, then what stops you from splitting files?

    // app.js
    console.log(require('./snippet.js').snippet(1,2))
    
    // snippet.js
    module.exports = {
      snippet: (value1, value2) => {
        /* Possible to put whatever logic in here */
        return require('./template.js')(value1, value2)
      },
    }
    
    // template.js
    module.exports = (value1, value2) => `
    <div>
      <h1>Hello ${value1 + ' - ' + value2}</h1>
    </div>
    `
    

As a side note:

  1. Eval is evil and slow

    From MDN:

    eval() is a dangerous function, which executes the code it's passed with the privileges of the caller. If you run eval() with a string that could be affected by a malicious party, you may end up running malicious code on the user's machine with the permissions of your webpage / extension.

    So if you only run your own code through eval() it is perfectly save... But, of course, as you implementing templates, that probably means that at some point you would want to allow user's input to the template, and that would call for troubles. So probably, yes, eval is unsafe in your case.

    I just wanted to point out that eval is extremely dangerous, but not an absolute evil in all situation.

    And it not necessary slow, either. When you say slow you must always ask yourself: "Compared to what? And in which situation?". Read the article and you will see why it can be slow. But can doesn't mean must.

  2. From the same paragraph:

    More importantly, a third-party code can see the scope in which eval() was invoked, which can lead to possible attacks in ways to which the similar Function is not susceptible.

    You may consider using Function. It is still dangerous. But less so.


On the second thougth - you should use Function, or just drop the idea of writing your template engine altogether. Because it's insecurity doesn't matter: the idea to use template literals is as insecure as Functionis:

const template = (value1, value2) => `
<div>
  <h1>Hello ${value1 + ' - ' + value2}</h1>
</div>
`
some_node.innerHTML = template("</h1><script>console.log("malicious injection")</script><h1>", "something else")

And by the way innerHTML is also insecure

like image 171
x00 Avatar answered Oct 09 '22 08:10

x00