UNPKG

shunter

Version:

A Node.js application built to read JSON and translate it into HTML

259 lines (226 loc) 8.39 kB
'use strict'; module.exports = function(config) { var dust = require('dustjs-helpers'); var mincer = require('mincer'); var fs = require('fs'); var path = require('path'); var glob = require('glob'); var inputFilters = require('./input-filter')(config); var eachModule = require('each-module'); var hostAppDir = config.path.root; var modulesPaths = config.modules.map(function(module) { return path.join(hostAppDir, 'node_modules', module); }); mincer.logger.use(config.log); var initExtensions = function() { var paths = [].slice.call(arguments, 0, -1); var callback = arguments[arguments.length - 1]; var locations = [config.path.shunterRoot].concat(modulesPaths, hostAppDir); if (typeof callback === 'function') { locations.forEach(function(dir) { var extensionPath = path.join.apply(path, [dir].concat(paths)); eachModule(extensionPath, callback); }); } }; // load input filters from host app and modules initExtensions(config.structure.filters, config.structure.filtersInput, function(name, inputFilter) { if (typeof inputFilter === 'function') { inputFilters.add(inputFilter); } }); // load mincer extensions from the host app and modules initExtensions(config.structure.mincer, function(name, initMincerExtension) { if (typeof initMincerExtension === 'function') { initMincerExtension(mincer, config); } }); var environment = new mincer.Environment(); var manifest = new mincer.Manifest(environment, config.path.publicResources); // host app can be shunter-based app or manifest, so rely on root var assetPath = function(name) { var isProduction = config.env.isProduction(); var asset = (isProduction) ? manifest.assets[name] : environment.findAsset(name); if (!asset) { return ''; } return ( isProduction ? path.join(config.web.publicResources, asset) : path.join(config.web.resources, asset.digestPath) ); }; environment.registerHelper('asset_path', assetPath); // Assets must be loaded in order (e.g. styles relies on images already being available) var assetTypes = [config.structure.fonts, config.structure.images, config.structure.styles, config.structure.scripts]; var themeResourcesPath = config.path.resources; // NB: risk of mincer clashes until stuff is moved out of proxy // for each asset type, add host then module. this order is important assetTypes.forEach(function(assetType) { var newPath = path.join(themeResourcesPath, assetType); if (fs.existsSync(newPath)) { environment.appendPath(newPath); } modulesPaths.reverse().forEach(function(modulePath) { var newPath = path.join(modulePath, 'resources', assetType); if (fs.existsSync(newPath)) { environment.appendPath(newPath); } }); }); // load ejs helpers from the host app and modules initExtensions(config.structure.ejs, function(name, initEjsHelper) { if (typeof initEjsHelper === 'function') { initEjsHelper(environment, manifest, config); } }); return { TEMPLATE_CACHE_KEY_PREFIX: 'root', dust: dust, environment: environment, manifest: manifest, assetPath: assetPath, assetServer: function() { return mincer.createServer(environment); }, initDustExtensions: function() { require('./dust')(dust, this, config); initExtensions(config.structure.dust, function(name, initDustExtension) { if (typeof initDustExtension === 'function') { initDustExtension(dust, this, config); } }.bind(this)); }, compileFile: function(fp) { var ext = config.structure.templateExt; var id; var compiled; var sandboxNS; var splitPath; var timer; if (path.extname(fp) === ext) { sandboxNS = path.relative(config.path.themes, fp); // Trim out the relative paths of inherited templates sandboxNS = sandboxNS.substring(sandboxNS.indexOf('view')); splitPath = sandboxNS.split(path.sep); if (splitPath.indexOf(config.structure.templates) > -1) { // remove internal structure path splitPath.splice(splitPath.indexOf(config.structure.templates), 1); } // reset to basename splitPath[splitPath.length - 1] = path.basename(fp, ext); splitPath.unshift(this.TEMPLATE_CACHE_KEY_PREFIX); // build id from path parts id = splitPath.join('__'); timer = config.timer(); try { compiled = dust.compile(fs.readFileSync(fp, 'utf8'), id); dust.loadSource(compiled); } catch (e) { config.log.error('Compilation error: ' + e.message + ' in ' + fp); } timer('Compiling ' + fp + ' as ' + id); } }, // Just used for testing? compilePaths: function(paths) { var self = this; if (typeof paths === 'string') { paths = [].slice.call(arguments, 0); } paths.forEach(function(name) { // DEPRECATED: checking both themes and templates folders for the right template file // when updated, should just look for 'self.compileFile(name));' // name will need to be full path, or contain the relevant subfolders e.g. laserwolf/views/subject/foo.dust if (fs.existsSync(path.join(config.path.themes, name))) { // themes self.compileFile(path.join(config.path.themes, name)); } else if (fs.existsSync(path.join(config.path.templates, name))) { // old shunter-proxy self.compileFile(path.join(config.path.templates, name)); } else if (fs.existsSync(name)) { // full path self.compileFile(name); } else { config.log.info('Could not find template ' + name); } // end DEPRECATED }); }, compileTemplates: function(forTests) { var fullFiles = []; // Get all defined modules templates first (in order defined by the host app) config.modules.forEach(function(module) { var moduleResourcesPath = (forTests) ? forTests : path.join(hostAppDir, 'node_modules', module); // must use / for glob even with windows var templates = [moduleResourcesPath, config.structure.templates, '**', ('*' + config.structure.templateExt)].join('/'); fullFiles = fullFiles.concat(glob.sync(templates, {})); }); // Then get the app's templates // (must use / for glob even with windows) var templates = [config.path.themes, config.structure.templates, '**', ('*' + config.structure.templateExt)].join('/'); fullFiles = fullFiles.concat(glob.sync(templates, {})); this.compileFileList(fullFiles); }, // accepts an array of files with full paths, sends each to compile compileFileList: function(fileArr) { var self = this; fileArr.forEach(function(file) { self.compileFile(file); }); }, watchTemplates: function() { var watchTree; var watcher; var folders = [config.path.templates]; var self = this; var compile = function(fp) { self.compileFile(fp); }; modulesPaths.forEach(function(mp) { folders.push(path.join(mp, config.structure.templates)); }); watchTree = require('./watcher')(config.structure.templateExt).watchTree; watcher = watchTree(folders, config.log); watcher.on('fileModified', compile); watcher.on('fileCreated', compile); config.log.info('Watching ' + folders.join(', ') + ' for changes'); }, watchDustExtensions: function() { var watchTree; var watcher; var folders = [config.path.dust]; var self = this; var compile = function(fp) { config.log.info('Loading Dust extension ' + fp); delete require.cache[require.resolve(fp)]; require(fp)(dust, self, config); }; modulesPaths.forEach(function(mp) { folders.push(path.join(mp, config.structure.dust)); }); watchTree = require('./watcher')('.js').watchTree; watcher = watchTree(folders, config.log); watcher.on('fileModified', compile); watcher.on('fileCreated', compile); config.log.info('Watching ' + folders.join(', ') + ' for changes'); }, render: function(req, res, data, callback) { var name = (data && data.layout && data.layout.template) ? data.layout.template : 'layout'; this.renderPartial(name, req, res, data, callback); }, renderPartial: function(partial, req, res, data, callback) { inputFilters.run(req, res, data, function(data) { var ns = (data && data.layout && data.layout.namespace) ? data.layout.namespace : null; var base = dust.makeBase({ namespace: ns }, { namespace: ns }); dust.render(partial, base.push(data), function(err, out) { callback(err, out); }); }); } }; };