npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@adrenalin/razor-js

v1.8.1

Published

Razor like HTML template engine for NodeJS Express library based on ASP.NET MVC Razor syntax. Template your views by mixing HTML markup with JavaScript server-side code!

Downloads

6

Readme

Razor-Express (RAZ): a view template engine for NodeJS/ExpressJS

--> JavaScript (browser) version of this library.


Intro

When I just started to dive into the world of Node.js after years of working with ASP.NET MVC I couldn't find any view template engine that was as convenient, elegant, concise, and syntactically close to native HTML as ASP.NET MVC Razor syntax was. And when it comes to code it's also syntactically close to the original C# language. Actually, ASP.NET MVC Razor markup is a hybrid of HTML markup and C# programming language. This is exactly what I expected to see in the NodeJS world - a hybrid of HTML and JavaScript.

The closest to Razor currently supported library for NodeJs & Express I could find was Vash. But in some points, it was quite different from ASP.NET MVC Razor syntax which I was used to and it just looked much less concise and convenient to me (the syntax for rendering layouts and partial blocks, for example). In short, it did not suit me completely and what's more important I couldn't see its current development.

A brief comparison of syntax of Node.JS template engines

I may be exaggerating the merits of ASP.NET MVC Razor and maybe it's all just a matter of habit, but let's look at a few examples that I found on the web (the question on Quora):

This is our data model represented via a JavaScript object:

var model = {
    subject: "Template Engines",
    items: [
        {name: "Mustache"},
        {name: "HandleBar"},
        {name: "Dust"},
        {name: "Jade"},
        {name: "EJS"},
        {name: "Razor"},
    ]
};

Mustache / HandleBar

<h1>{{subject}}</h1>
<ul>
  {{#items}}
    <li>{{name}}</li>
  {{/items}}
</ul>

^ mustache live example


Pug (Jade)

h1=model.subject  
ul
  each item in model.items
      li=item.name 

^ pug live example


EJS

<h1><%= model.subject %></h1>
<ul>
  <% for(var i = 0; i < model.items.length; i++) {%>
     <li><%= model.items[i].name %></li>
  <% } %>
</ul>

^ ejs live example


Razor

<h1>@Model.subject</h1>
<ul>
  @for(var i = 0; i < Model.items.length; i++) {
     <li>@Model.items[i].name</li>
  }
</ul>

^ razor live example

Haml

Or let's consider an example from http://haml.info/tutorial.html

.item{:id => "item#{item.id}"}= item.body

Maybe I'm wrong and this kind of markup really simplifies the development and perception of the code, but to me it doesn't seem to be so. Let's just compare it to the equivalent Razor markup:

<div class='item' id='[email protected]'>
  @item.body
</div>

I won't go much into all the aspects I don't like in other engines syntax just say that "Mustache / HandleBar", "Pug", and Haml look like I have to learn a new syntax while with Razor I virtually know it if I'm already familiar with HTML and JavaScript languages. EJS is very close to Razor but it is too verbose that makes it difficult to write and read the code.

In these examples I don't compare logic constructions because some of the view engines have logic-less templating syntax. With Razor you can implement amost any logic that is available with normal JavaScript without learning a new syntax.

Given all the mentioned and unmentioned pros and cons, I decided not to part with Razor-syntax and create something similar for using with ExpressJS library (it can work with other frameworks as well). This library works quite fast since it does not use third-party HTML parsers and regular expressions.


Quick Start

Assuming that you are already familiar with the basic idea let's look at a simple example.

Our first component is a model:

{
    title: "Names of the Days of the Week",
    days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
};

which is just a JavaScript object. And we want to get some HTML displaying the data of this model in some simple way. Later we might want to change the model data and still get the same HTML structure to display it. So, we need our second component which is called view-template:

<h3>@Model.title</h3>
<ul>
@for(var i = 0; i < Model.days.length; i++){
    var day = Model.days[i];
    <li>@day</li>
}
</ul>

As you can see the view-template (or just view) is nothing more than HTML markup mixed with JavaScript syntax. This is exactly what Razor-Express syntax is.

Now we are going to take these two components and "compile" them into pure HTML.

Node.js example

First, we'll be doing this "compilation" without creating any web-server to keep things as simple as possible. It can be done either in Node.js environment or in just the browser JavaScript. To do this we will declare two variables model and template:

const model = {
    title: "Names of the Days of the Week",
    days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
};

const template = `
<h3>@Model.title</h3>
<ul>
@for(var i = 0; i < Model.days.length; i++){
    var day = Model.days[i];
    <li>@day</li>
}
</ul>`;

...which are pretty much self-explained as we remember what our two components are. Next, we have to render them together using Razor-Express library to get the expected HTML.

// This code is meant for Node.js 
const razor = require("raz");
var html = razor.render({ model, template });

Now let's display it in the console to make sure our expectations are fully met.

console.log(html);

Here's what we can see in the console:

<h3>Names of the Days of the Week</h3>
<ul>
    <li>Sunday</li>
    <li>Monday</li>
    <li>Tuesday</li>
    <li>Wednesday</li>
    <li>Thursday</li>
    <li>Friday</li>
    <li>Saturday</li>
</ul>

That's all! Isn't it simple?

* If you'd like to see all these parts working together here is the playground of it.

Express web-server example

  1. Create a new NodeJS project (set in server.js in as an entry point).
  2. Install Express & Razor-Express libraries:
  1. In the project folder create "server.js" file (read js-comments inside):

server.js file:

// Create Express web server app.
const app = require('express')();
// Register 'Razor' template engine and the default extesnion for the view-template files.
// 'Express' will automatically find the Razor module (within the `node-modules` folder) using this extension. 
// If you decide to skip registering the engine then you will have to explicitly specify the file extension in the route handler.
app.set('view engine', "raz");
// There is an alternative way to register Razor-Express engine (see more in Razor-Express API docs):
//const raz = require('raz');
//raz.register(app);

// Create the route for the "Index.raz" view-template.
// Note that we do not specify the file extension explicitly in this route because we already did it when registering the engine.
app.get('/', (req, res) => {
    const model = {
        title: "Names of the Days of the Week",
        days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
    };
    res.render("./index", model);
});

// Express-app default port number.
const port = process.env.PORT || 8080;

// Starting Express-app.
const server = app.listen(port, () => {
    console.log("Server is up on port " + port);
});

// Catch Express-app errors.
server.on('error', function (e) {
    if (e.code === 'EADDRINUSE') {
        console.error('Address is in use, stopped.');
    }
});

* The default 'raz' extesnion of view-template files can be changed via the register method.

  1. Create the "views" folder. This is the directory defined in Express by default where the template files are located. If you want to use another folder you can change it with app.set('views', './another-views-folder') method.
  2. Create a view-template file in that folder named "index.raz". It would have pretty much the same content as in the previous example except we have to add some basic HTML markup. Notice that the view-template file has '.raz' extension which every Razor-Express view file must have.

index.raz file:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>@Model.title</title>
  </head>
  <body>
    <h2>@Model.title</h2>
    <ul>
    @for(var i = 0; i < Model.days.length; i++){
        var day = Model.days[i];
        <li>@day</li>
    }
    </ul>
  </body>
</html>

Now you when run the 'node server.js' command in the console (or terminal) and open http://localhost:8080/ URL in your browser you will see the HTML page showing something like this:


Names of the Days of the Week

  • Sunday
  • Monday
  • Tuesday
  • Wednesday
  • Thursday
  • Friday
  • Saturday

The Express server app with Razor template engine works! :thumbsup:

* The source code of this example is available in RazorExpressExample repository.

For a more comprehensive understanding of how the Razor-Express Template Engine works and what Razor-Express syntax is, follow these links:

:warning: Common pitfalls

Missing semicolon

Some developers have a habit of not putting a semicolon at the end of JavaScript code lines. This is a personal matter of course, although not considered good form. As for JavaScript in the Razor-Express engine templates, a semicolon at the end of JavaScript expressions is strictly required! If you do not follow this requirement, there may be cases when Razor isn't able to understand your instructions and throws a pretty vague error. Let's take a look at this example.

// Semicolon is missed at the end of the line of code "var text = i * i".
const template = `
<ul>
@for(var i = 0; i < 10; i++){
    var text = i * i
    <li>@text</li>
}
</ul>`;

const razor = require("raz");

try{
    var html = razor.render({ template });
}
catch(err){
    console.log(err);
}

If you run this code you will get the error:

RazorError: The code or section block is missing a closing "}" character. Make sure you have a matching "}" character for all the "{" characters within this block, and that none of the "}" characters are being interpreted as markup. The block starts at line 3 with text: "@for(var i = 0; i < 10; i++){"

* Run this code with RunKit.

Expressions & code blocks confusion

Try to stick to simple classical language constructs to avoid ambiguous parser or runtime errors. Although almost all JavaScript syntax is correctly parsed by the engine, some language constructs may not work as you expect. For example, you might try to write a loop as follows:

<table>
  <tr>
    <th>Country</th>
    <th>Area sq.km</th>
  </tr>
  @countries.forEach((c)=>{
    <tr>
      <td>@c.name</td>
      <td>@c.area</td>
    </tr>
  });
</table>

There are no syntax errors in this example and the code intuitively looks quite decent. The parser also won't find any problems. However, a runtime error will occur during execution. The actual problem is that the parser considers this statement an expression. When an expression is detected it is evaluated and the return value of it is rendered in HTML. But the parser does look for HTML within expressions. Therefore the HTML tags you put in the expression will cause the runtime error because the whole expression with these tags (as part of JavaScript code) will be tried to be executed.

To make this code work you need to wrap it explicitly in a code block then it will be parsed as part of the code block and HTML within it will be rendered correctly. That is, you need to bring it to the following form:

...
@{
    countries.forEach((c)=>{
      <tr>
        <td>@c.name</td>
        <td>@c.area</td>
      </tr>
    });
}
...

Another solution would be to use a separate JavaScript function to output HTML. It will be understood by the parser as a block of code and there will be no problem with the transition to HTML:

...
@function tr(c) {
<tr>
  <td>@c.name</td>
  <td>@c.area</td>
</tr>
}
<table>
  <tr>
    <th>Country</th>
    <th>Area sq.km</th>
  </tr>
  @countries.forEach((c)=>{
    tr(c);
  })
</table>
...

^ run this example

However, the best way to avoid such ambiguities is to stick to a plain JavaScript syntax style while writing your view templates. See "Looping @for, @while, and @do while" section for examples of loop structures.

Misc

The source code examples

Here are the links to all repositories with examples used in the documentation:

TODO list (Ideas for the next version)

  1. Implement Razor-style @* *@ comments.
  2. ~~Make the library available for use on the client side (in the browser).~~(done)
  3. Implement caching compiled templates.
  4. Async partial views.
  5. Make HtmlString class public for making functions returnin raw-values as expessions.