jabney / render-template-loader

A webpack loader that renders templates using one of any number of templating engines
MIT License
4 stars 2 forks source link

Render Template Loader

Build Status

Render templates with Webpack using one of any number of templating engines.

See The Demo Project for an example webpack project setup that uses render-template-loader with ejs to render index.html, and in the same config renders pug and handlebars templates as well.

See unit tests and webpack.config.js for other usage examples.

Built-in support for and tested with: ejs, handlebars, jade, mustache, pug, twig, and vash.

Partials support tested with ejs, handlebars, jade, twig, and pug.

Custom engine support included; see loader.spec.js.

Installation

Install render-template-loader

> npm install render-template-loader

Install a template engine or two

> npm install ejs pug

Examples

Render index.html from an ejs template

index.ejs (input)

<!doctype html>
<html lang="en">
  <head><title><%=title%></title></head>
  <body>
    <% include body %>
  </body>
</html>

body.ejs (partial)

<h1><%=title%></h1>
<h2><%=desc%></h2>

webpack.config.js (config)

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [{
      test: /\/src\/index.ejs$/,
      use: [{
        loader: 'render-template-loader',
        options: {
          engine: 'ejs',
          locals: {
            title: 'Render Template Loader',
            desc: 'Rendering templates with a Webpack loader since 2017'
          },
          engineOptions: function (info) {
            // Ejs wants the template filename for partials rendering.
            // (Configuring a "views" option can also be done.)
            return { filename: info.filename }
          }
        }
      }]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.ejs'
    })
  ]
}

index.html (output)

<!doctype html>
<html lang="en">
<head><title>Render Template Loader</title></head>
<body>
  <h1>Render Template Loader</h1>
  <h2>Rendering templates with a Webpack loader since 2017</h2>

<script type="text/javascript" src="https://github.com/jabney/render-template-loader/raw/master/main.bundle.js"></script></body>
</html>

Render page.html from a pug template

page.pug (input)

<!doctype html>
html(lang="en")
  head
    title #{title}
  body
    include body

body.pug (partial)

<h1>#{title}</h1>
<h2>#{desc}</h2>

webpack.config.js (config)

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [{
      test: /\.pug$/,
      use: [{
        loader: 'file-loader?name=[name].html'
      },
      {
        /**
         * We use extract-loader in this configuration because
         * file-loader above expects a string. In other configs,
         * we use HtmlWebpackLoader which handles the module
         * output of render-template-loader, so extract-loader
         * is not required.
         */
        loader: 'extract-loader'
      },
      {
        loader: 'render-template-loader',
        options: {
          engine: 'pug',
          locals: {
            title: 'Rendered with Pug!',
            desc: 'Partials Support'
          },
          engineOptions: function (info) {
            return { filename: info.filename }
          }
        }
      }]
    }]
  },
  plugins: []
}

page.html (output)

<!doctype html>
<html lang="en">
  <head><title>Rendered with Pug!</title></head>
<body>
  <h1>Rendered with Pug!</h1>
  <h2>Partials Support</h2>
</body>
</html>

Loader Options

options: {
  // The name of the engine as installed by npm (required).
  engine: 'engine name' | function (template, locals, options) {),
  // Template variables (optional).
  locals: {...} | function () { return {...} },
  // Options specific to the engine (optional).
  engingeOptions: {} | function (info) {}
  // Called before the template is rendered (optional).
  init: function (engine, info, options) {}
}

engine (string|function): (required) the name of the template engine as installed, e.g., ejs, or a custom engine function that returns the rendered template. It can’t be a arrow function because we use .call() to have webpack context as this.

engine: 'ejs',
locals: {
  title: 'Ejs Template',
  desc: 'A template rendered by ejs'
}
engine: function (input, locals, engineOptions) {
  return ejs.render(input, locals, engineOptions)
},
locals: {
  title: 'Custom Template',
  desc: 'A template rendered by a custom function (using ejs)'
}
engine: function (input, locals) {
  return input.replace(/#\{(.+?)\}/g, function (match, p1) {
    return locals[p1]
  })
},
locals: {
  title: 'Custom Template',
  desc: 'A template rendered by a custom function (using regex)'
}

locals (object): (optional) an object containing variables used by the template. A local variable title will be used by a template, e.g., <%= title %>.

locals: {
  title: 'Ejs Template',
  desc: 'A template rendered by ejs'
}

locals can also be a function that returns a locals object:

  locals: function () {
    return {
      title: 'Ejs Template',
      desc: 'A template rendered by ejs'
    }
  }
<h1><%= title %></h1>
<h2><%= desc %></h2>

engineOptions (object|function): (optional) an options object passed to the template engine when it's loaded. The content of this object is determined by each engine's configuration. For simple template rendering (without partials) engineOptions isn't usually required.

engineOptions: {
  views: ['./src/views']
}
engineOptions: function (info) {
  // The info object contains the filename of the
  // template being rendered.
  return { filename: info.filename }
}

init (function): (optional) a function called before the template is rendered. This is useful for engines that might require setup code to run before use. For instance, handlebars partials can be configured by calling handlebars.registerPartial.

engine: 'handlebars',
locals: {
  title: 'Handlebars Template',
  desc: 'A template rendered by Handlebars'
},
init: function (engine, info) {
  engine.registerPartial('description', '<h2>{{ desc }}</h2>')
}

The Loader Context

Options which can be functions have their this context set to the loader context. This allows for advanced usage, such as adding webpack dependencies on the fly. See the webpack documentation on the LoaderContext for its api methods.

Example

json data

{
  "title": "Locals as a Function with addDependency",
  "desc": "The locals function can use the loader context"
}

loader configuration

{
  loader: 'render-template-loader',
  options: {
    engine: 'ejs',
    engineOptions: function (info) {
      // Ejs wants the template filename for partials rendering.
      // (Configuring a "views" option can also be done.)
      return { filename: info.filename }
    },
    locals: function () {
      const file = path.join(__dirname, './data/locals.json')
      // Access the loader context's addDependency method to
      // add a data file dependency.
      this.addDependency(file)
      const buffer = fs.readFileSync(file)
      // Return the loaded json as the locals.
      return JSON.parse(buffer.toString())
    }
  }
}

FAQ

Why do some configurations use extract-loader and others do not?

render-template-loader exports the rendered template as a commonjs javascript module:

module.exports = "<h1>Pug Template</h1><h2>A template rendered by Pug</h2>"

In many cases, such as when using HtmlWebpackPlugin, this is exactly what you want, because module code is expected. In other cases, such as when using file-loader or html-loader or some other loader that expects plain text, extract-loader manages extracting the stirng from the module's output:

<h1>Pug Template</h1><h2>A template rendered by Pug</h2>

with extract-loader

/**
 * Use extract-loader because we're using file loader instead of
 * HtmlWebpackPlugin.
 */
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [{
      test: /\.pug$/,
      use: [{
        loader: 'file-loader?name=[name].html'
      },
      {
        /**
         * We use extract-loader in this configuration because
         * file-loader above expects a string. In other configs,
         * we use HtmlWebpackLoader which handles the module
         * output of render-template-loader, so extract-loader
         * is not required.
         */
        loader: 'extract-loader'
      },
      {
        loader: 'render-template-loader',
        options: {
          engine: 'pug',
          locals: {
            title: 'Rendered with Pug!',
            desc: 'Partials Support'
          },
          engineOptions: function (info) {
            return { filename: info.filename }
          }
        }
      }]
    }]
  },
  plugins: []
}

without extract loader

/**
 * No need for extract-loader here because HtmlWebpackPlugin is
 * handling the index.ejs file.
 *
 */
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [{
      test: /\/src\/index.ejs$/,
      use: [{
        loader: 'render-template-loader',
        options: {
          engine: 'ejs',
          locals: {
            title: 'Render Template Loader',
            desc: 'Rendering templates with a Webpack loader since 2017'
          },
          engineOptions: function (info) {
            // Ejs wants the template filename for partials rendering.
            // (Configuring a "views" option can also be done.)
            return { filename: info.filename }
          }
        }
      }]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      // Tell HtmlWebpackPlugin about our index.ejs template.
      template: 'src/index.ejs'
    })
  ]
}