dwyl/hapi-riot

View on GitHub
lib/index.js

Summary

Maintainability
A
1 hr
Test Coverage
'use strict';

var Path = require('path');
var Riot = require('riot');
var fs = require('fs');

var DEFAULTS = { removeCache: process.env.NODE_ENV !== 'production' };

var layoutCache = {};
var tagsLoaded = null; // when null all tags in view directory will be loaded

/*
 * Riot does not Automatically resolve and require nested tags.
 * so loadTags requires all files in the template's directory
 * QUESTION: do we need to require recursively? discuss: https://git.io/viqjY
 */
function loadTags (filename) {
  var viewdir;

  if (tagsLoaded) {
    return; // early return since have already made the requires
  }

  viewdir = filename.slice(0, filename.lastIndexOf('/'));
  tagsLoaded = fs.readdirSync(viewdir).map(function (file) {
    var filepath = Path.join(viewdir, file);

    if (filepath !== filename && !require.cache[filepath]) {
      require(filepath); //eslint-disable-line
    }

    return filepath;
  });
}

function loadLayout (filePath) {
  layoutCache[filePath] = layoutCache[filePath]
    || fs.readFileSync(filePath, 'utf8')
  ;

  return layoutCache[filePath];
}

/*
 * during development we want to "un-require" tags to ensure
 * that changes made in components are re-loaded
 */
function unCache () {
  tagsLoaded.forEach(function (file) {
    delete require.cache[require.resolve(file)];
  });
  tagsLoaded = null;
  layoutCache = {};
}

/*
 * Vision expects this format a compile object that returns a runtime method.
 * template is the template name e.g: 'home' or 'dashboard'
 * context is the object (2nd argument) passed in to the reply.view method!
 */
function compile (template, compileOpts) {
  var baseOpts = Object.assign({}, DEFAULTS, compileOpts);

  return function render (context, renderOpts) {
    var layoutFilePath, layoutFileName, content, View, compiledFileRoute;
    var mergedOpts = Object.assign({}, baseOpts, renderOpts);
    var riotTag = template.split('>')[0].slice(1);

    loadTags(mergedOpts.filename);
    compiledFileRoute = mergedOpts.compiledFileRoute || '/bundle.js';
    View = require(mergedOpts.filename); //eslint-disable-line
    content = Riot.render(View, context)
    + '\n<script src="https://cdn.jsdelivr.net/riot/2.6/riot.min.js" ></script>'
    + '\n<script src="' + compiledFileRoute + '"></script>'
    + '\n<script>\nriot.mount("'
    + riotTag + '",' + JSON.stringify(context) + ')\n</script>'
    ;
    if (mergedOpts.layout) {
      layoutFileName = mergedOpts.layout === true
        ? 'layout.html'
        : mergedOpts.layout
      ;
      layoutFilePath = Path.join(mergedOpts.layoutPath, layoutFileName);

      content = loadLayout(layoutFilePath).replace('<<<RIOT>>>', content);
    }

    /*
     * Delete the view and layout html from the require cache
     * so we don't need to restart the app to see view changes.
     * Skipped By default when NODE_ENV=production`.
     */
    if (mergedOpts.removeCache) {
      unCache();
    }

    return content;
  };
}


module.exports = { compile: compile };