UNPKG

8.92 kBJavaScriptView Raw
1'use strict';
2
3module.exports = function (config) {
4 var dust = require('dustjs-helpers');
5 var mincer = require('mincer');
6 var fs = require('fs');
7 var path = require('path');
8 var glob = require('glob');
9 var inputFilters = require('./input-filter')(config);
10 var eachModule = require('each-module');
11
12 var hostAppDir = config.path.root;
13 var modulesPaths = config.modules.map(function (module) {
14 return path.join(hostAppDir, 'node_modules', module);
15 });
16
17 mincer.logger.use(config.log);
18
19 var initExtensions = function () {
20 var paths = [].slice.call(arguments, 0, -1);
21 var callback = arguments[arguments.length - 1];
22 var locations = [config.path.shunterRoot].concat(modulesPaths, hostAppDir);
23
24 if (typeof callback === 'function') {
25 locations.forEach(function (dir) {
26 var extensionPath = path.join.apply(path, [dir].concat(paths));
27 eachModule(extensionPath, callback);
28 });
29 }
30 };
31
32 // Load input filters from host app and modules
33 initExtensions(config.structure.filters, config.structure.filtersInput, function (name, inputFilter) {
34 if (typeof inputFilter === 'function') {
35 inputFilters.add(inputFilter);
36 }
37 });
38
39 // Load mincer extensions from the host app and modules
40 initExtensions(config.structure.mincer, function (name, initMincerExtension) {
41 if (typeof initMincerExtension === 'function') {
42 initMincerExtension(mincer, config);
43 }
44 });
45
46 var environment = new mincer.Environment();
47 var manifest = new mincer.Manifest(environment, config.path.publicResources);
48 // Host app can be shunter-based app or manifest, so rely on root
49
50 var assetPath = function (name) {
51 var isProduction = config.env.isProduction();
52 var asset = (isProduction) ? manifest.assets[name] : environment.findAsset(name);
53 if (!asset) {
54 return '';
55 }
56
57 return (
58 isProduction ?
59 path.join(config.web.publicResources, asset) :
60 path.join(config.web.resources, asset.digestPath)
61 );
62 };
63
64 environment.registerHelper('asset_path', assetPath);
65 // Assets must be loaded in order (e.g. styles relies on images already being available)
66 var assetTypes = [config.structure.fonts, config.structure.images, config.structure.styles, config.structure.scripts];
67 var themeResourcesPath = config.path.resources;
68 // NB: risk of mincer clashes until stuff is moved out of proxy
69 // for each asset type, add host then module. this order is important
70 assetTypes.forEach(function (assetType) {
71 var newPath = path.join(themeResourcesPath, assetType);
72 if (fs.existsSync(newPath)) {
73 environment.appendPath(newPath);
74 }
75 modulesPaths.reverse().forEach(function (modulePath) {
76 var newPath = path.join(modulePath, 'resources', assetType);
77 if (fs.existsSync(newPath)) {
78 environment.appendPath(newPath);
79 }
80 });
81 });
82
83 // Load ejs helpers from the host app and modules
84 initExtensions(config.structure.ejs, function (name, initEjsHelper) {
85 if (typeof initEjsHelper === 'function') {
86 initEjsHelper(environment, manifest, config);
87 }
88 });
89
90 return {
91 TEMPLATE_CACHE_KEY_PREFIX: 'root',
92
93 dust: dust,
94 environment: environment,
95 manifest: manifest,
96 assetPath: assetPath,
97
98 assetServer: function () {
99 return mincer.createServer(environment);
100 },
101
102 initDustExtensions: function () {
103 require('./dust')(dust, this, config);
104 initExtensions(config.structure.dust, function (name, initDustExtension) {
105 if (typeof initDustExtension === 'function') {
106 initDustExtension(dust, this, config);
107 }
108 }.bind(this));
109 },
110
111 compileFile: function (fp) {
112 var ext = config.structure.templateExt;
113 var id;
114 var compiled;
115 var sandboxNS;
116 var splitPath;
117 var timer;
118
119 if (path.extname(fp) === ext) {
120 sandboxNS = path.relative(config.path.themes, fp);
121 // Trim out the relative paths of inherited templates
122 sandboxNS = sandboxNS.substring(sandboxNS.indexOf('view'));
123 splitPath = sandboxNS.split(path.sep);
124 if (splitPath.indexOf(config.structure.templates) > -1) {
125 // Remove internal structure path
126 splitPath.splice(splitPath.indexOf(config.structure.templates), 1);
127 }
128 // Reset to basename
129 splitPath[splitPath.length - 1] = path.basename(fp, ext);
130 splitPath.unshift(this.TEMPLATE_CACHE_KEY_PREFIX);
131 // Build id from path parts
132 id = splitPath.join('__');
133
134 timer = config.timer();
135 try {
136 compiled = dust.compile(fs.readFileSync(fp, 'utf8'), id);
137 dust.loadSource(compiled);
138 } catch (err) {
139 config.log.error('Compilation error: ' + err.message + ' in ' + fp);
140 }
141 timer('Compiling ' + fp + ' as ' + id);
142 }
143 },
144
145 // Just used for testing?
146 compilePaths: function (paths) {
147 var self = this;
148 if (typeof paths === 'string') {
149 paths = [].slice.call(arguments, 0);
150 }
151 paths.forEach(function (name) {
152 // DEPRECATED: checking both themes and templates folders for the right template file
153 // when updated, should just look for 'self.compileFile(name));'
154 // name will need to be full path, or contain the relevant subfolders e.g. laserwolf/views/subject/foo.dust
155
156 if (fs.existsSync(path.join(config.path.themes, name))) {
157 // Themes
158 self.compileFile(path.join(config.path.themes, name));
159 } else if (fs.existsSync(path.join(config.path.templates, name))) {
160 // Old shunter-proxy
161 self.compileFile(path.join(config.path.templates, name));
162 } else if (fs.existsSync(name)) {
163 // Full path
164 self.compileFile(name);
165 } else {
166 config.log.info('Could not find template ' + name);
167 }
168 // End DEPRECATED
169 });
170 },
171
172 compileTemplates: function (forTests) {
173 var fullFiles = [];
174 // Get all defined modules templates first (in order defined by the host app)
175 config.modules.forEach(function (module) {
176 var moduleResourcesPath = (forTests) ? forTests : path.join(hostAppDir, 'node_modules', module);
177 // Must use / for glob even with windows
178 var templates = [moduleResourcesPath, config.structure.templates, '**', ('*' + config.structure.templateExt)].join('/');
179 fullFiles = fullFiles.concat(glob.sync(templates, {}));
180 });
181 // Then get the app's templates
182 // (must use / for glob even with windows)
183 var templates = [config.path.themes, config.structure.templates, '**', ('*' + config.structure.templateExt)].join('/');
184 fullFiles = fullFiles.concat(glob.sync(templates, {}));
185 this.compileFileList(fullFiles);
186 },
187
188 compileOnDemand: function (name) {
189 var self = this;
190 var localPath = path.join(config.structure.templates, name.split('__').join(path.sep) + config.structure.templateExt);
191 config.modules.map(function (module) {
192 return path.join(hostAppDir, 'node_modules', module, localPath);
193 }).concat([
194 path.join(config.path.themes, localPath)
195 ]).filter(function (file) {
196 return fs.existsSync(file);
197 }).forEach(function (file) {
198 self.compileFile(file);
199 });
200 },
201
202 // Accepts an array of files with full paths, sends each to compile
203 compileFileList: function (fileArr) {
204 var self = this;
205 fileArr.forEach(function (file) {
206 self.compileFile(file);
207 });
208 },
209
210 watchTemplates: function () {
211 var watchTree;
212 var watcher;
213 var folders = [config.path.templates];
214 var self = this;
215
216 var compile = function (fp) {
217 self.compileFile(fp);
218 };
219
220 modulesPaths.forEach(function (mp) {
221 folders.push(path.join(mp, config.structure.templates));
222 });
223 watchTree = require('./watcher')(config.structure.templateExt).watchTree;
224 watcher = watchTree(folders, config.log);
225 watcher.on('fileModified', compile);
226 watcher.on('fileCreated', compile);
227 config.log.debug('Watching ' + folders.join(', ') + ' for changes');
228 },
229
230 watchDustExtensions: function () {
231 var watchTree;
232 var watcher;
233 var folders = [config.path.dust];
234 var self = this;
235
236 var compile = function (fp) {
237 config.log.info('Loading Dust extension ' + fp);
238 delete require.cache[require.resolve(fp)];
239 require(fp)(dust, self, config);
240 };
241
242 modulesPaths.forEach(function (mp) {
243 folders.push(path.join(mp, config.structure.dust));
244 });
245 watchTree = require('./watcher')('.js').watchTree;
246 watcher = watchTree(folders, config.log);
247 watcher.on('fileModified', compile);
248 watcher.on('fileCreated', compile);
249 config.log.debug('Watching ' + folders.join(', ') + ' for changes');
250 },
251
252 render: function (req, res, data, callback) {
253 var name = (data && data.layout && data.layout.template) ? data.layout.template : 'layout';
254 this.renderPartial(name, req, res, data, callback);
255 },
256
257 renderPartial: function (partial, req, res, data, callback) {
258 inputFilters.run(req, res, data, function (data) {
259 var ns = (data && data.layout && data.layout.namespace) ? data.layout.namespace : null;
260 var base = dust.makeBase({
261 namespace: ns
262 }, {
263 namespace: ns
264 });
265 dust.render(partial, base.push(data), function (err, out) {
266 callback(err, out);
267 });
268 });
269 }
270 };
271};