scniro/angular-tpl2js

View on GitHub
index.js

Summary

Maintainability
C
1 day
Test Coverage
var async = require('async');
var fs = require('fs');
var minify = require('html-minifier').minify;
var path = require('path');
var cheerio = require('cheerio');
var engine;

function TemplateEngine() {

  var self = this;
  var _config = {};

  function readAsync(file, callback) {
    if (file)
      fs.readFile(file, 'utf8', callback);
  }

  function merge(obj1, obj2) {
    var obj3 = {};
    for (var a1 in obj1) {
      obj3[a1] = obj1[a1];
    }
    for (var a2 in obj2) {
      obj3[a2] = obj2[a2];
    }
    return obj3;
  }

  function embedIncludes(template, source) {

    var $ = cheerio.load(template, {decodeEntities: false});

    function recurse() {
      $('[ng-include]').each(function (i, ele) {
        var src = path.dirname(source) + '/' + ($(ele).attr('ng-include') || $(ele).attr('src')).replace(/"/g, '').replace(/'/g, '').trim();
        var include = fs.readFileSync(src, 'utf8');

        $(ele).append(include);
        $(this).removeAttr('ng-include');

        if ($(ele).find('[ng-include]').length > 0) {
          recurse();
        }
      });
    }

    recurse();

    return $.html();
  }

  self.config = {
    get: function () {
      return _config;
    },
    set: function (config) {

      var HTMLMinifier = {
        collapseWhitespace: true,
        removeComments: true
      };

      _config = config || {};
      _config.HTMLMinifier = merge(HTMLMinifier, (config.HTMLMinifier || {}));
      _config.include = config.include || false;
    }
  }

  self.source = {
    hash: function (raw, base) {
      var source = {contents: raw, templates: []};
      var parts = source.contents.split(/(?=templateUrl)/g);

      parts.forEach(function (element, index) {

        var match = (element.match(/(?!,)templateUrl(.*)$/gm));

        if (match) {
          var templateUrl = match[0].split(':')[1].split(',')[0].replace(/"/g, '').replace(/'/g, '').trim()
          var relative = path.resolve(__dirname + base, '../' + templateUrl);
          source.templates.push(relative);
        }
      });

      return source;
    },
    read: function (target) {
      return new Promise(function (resolve, reject) {
        fs.readFile(__dirname + target, 'utf8', function (err, data) {
          resolve(data);
        });
      });
    }
  };

  self.templates = {
    get: function (source) {

      return new Promise(function (resolve, reject) {
        async.map(source.templates, readAsync, function (err, results) {

          if (err)
            reject('template not found: ' + err.path);
          else {
            results.forEach(function (element, index, arr) {

              var t, $ = cheerio.load(element, {decodeEntities: false});

              if ($($.html()).find('[ng-include]').length > 0 && _config.include) {
                t = embedIncludes($.html(), source.templates[index])
              }

              var template = minify((t || $.html()), _config.HTMLMinifier) // minify the markup
              source.templates[index] = template;
              resolve(source);
            });

            resolve(source);
          }
        });
      });
    },
    set: function (transformed) {
      return new Promise(function (resolve, reject) {

        try {
          var parts = transformed.contents.split(/(?=templateUrl)(?!,)/g);

          if (parts.length === 1)
            throw 'unable to set template: no templateUrl clause';

          parts.forEach(function (element, index, arr) {

            var match = (element.match(/(?!,)templateUrl(.*)$/gm));

            if (match)
              arr[index] = arr[index].replace(/(?!,)templateUrl(.*)(?!,)$/gm, 'template: \'' + transformed.templates.shift().replace(/'/g, "\\'") + '\',')
          });

          resolve(parts.join(''));
        } catch (err) {
          reject(err)
        }
      });
    }
  }
}

function TemplateManager() {

  var self = this;

  self.inline = function (input, config, done) { // -- in

    engine = new TemplateEngine();

    // shift optional config argument
    if (arguments.length === 2 && Object.prototype.toString.call(arguments[1]) === '[object Function]') {
      done = config;
    } else {
      engine.config.set(config);
    }

    var base, js;

    function run(js) {
      engine.templates.get(js).then(function (transformed) {
        engine.templates.set(transformed).then(function (output) {
          done(null, output); // -- out
        }, function (err) {
          done(err) // -- templates.set promise error
        });
      }, function (err) {
        done(err); // -- templates.get promise error
      });
    }

    if (input instanceof Buffer) {
      base = '/' + path.dirname(path.relative(__dirname, config.target));
      js = engine.source.hash(input.toString(), base);
      run(js);
    } else {
      engine.source.read(input).then(function (data) {
        base = path.dirname(input);
        js = engine.source.hash(data, base);
        run(js);
      });
    }
  }
}

module.exports = new TemplateManager();
module.exports.engine = new TemplateEngine();