-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathindex.js
243 lines (216 loc) · 6.03 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
var uglify = require('uglify-js');
var fs = require('fs');
var async = require('async');
var gracenode = require('../../');
var log = gracenode.log.create('view');
var parserSource = require('./parser');
/*
* configurations
* view: { // optional
* preloads: ["filepath"...]
* minify: true/false // optional > default is true
*}
*
* Parser class handles these
* (:include filePath:) included in the html
* (:= variable name:) replaced with the value of clientData with the same name
*
*/
var viewList = {};
var config = null;
module.exports.readConfig = function (configIn) {
config = configIn;
};
module.exports.setup = function (cb) {
if (config && config.preloads && config.preloads.length) {
log.verbose('preload view files');
return async.eachSeries(config.preloads, function (path, nextCallback) {
gracenode.lib.walkDir(gracenode.getRootPath() + path, function (error, list) {
if (error) {
return cb(error);
}
async.eachSeries(list, function (item, next) {
var path = item.file;
// get file modtime in unix timestamp
var dateObj = new Date(item.stat.mtime);
var mtime = dateObj.getTime();
// create memory cache key
var key = path + mtime;
fs.readFile(path, { encoding: 'utf8' }, function (error, file) {
if (error) {
return cb(new Error('[' + path + '] ' + error));
}
var fileType = path.substring(path.lastIndexOf('.') + 1);
// process file to optimize the output
var content = processFile(fileType, file);
// store in memory cache
viewList[key] = content;
log.verbose('view output data stored in cache: ', key);
next();
});
}, nextCallback);
});
}, cb);
}
cb();
};
module.exports.create = function () {
return new View();
};
function View() {
this._data = {};
}
View.prototype.assign = function (name, value) {
this._data[name] = value;
};
View.prototype.get = function (name) {
if (this._data[name]) {
return gracenode.lib.cloneObj(this._data[name]);
}
return null;
};
View.prototype.load = function (viewFilePath, cb) {
var seen = [];
load(viewFilePath, seen, this._data, cb);
};
function load(viewFilePath, seen, clientData, cb) {
// validate callback
if (typeof cb !== 'function') {
log.error('function load is missing callback');
throw new Error('missing callback');
}
// create the source path
var path = gracenode.getRootPath() + viewFilePath;
log.verbose('loading a view file: ', path);
// view file parser
var parser = parserSource.create(clientData);
// start loading
var outputData = '';
gracenode.lib.walkDir(path, function (error, list) {
if (error) {
return cb(error);
}
async.eachSeries(list, function (item, nextCallback) {
readFile(item.file, item.stat, parser, seen, clientData, function (error, data) {
if (error) {
return cb(error);
}
outputData += data;
nextCallback();
});
},
function (error) {
if (error) {
return cb(error);
}
cb(null, outputData);
});
});
}
function readFile(path, stat, parser, seen, clientData, cb) {
// content data
var content = null;
// get file modtime in unix timestamp
var dateObj = new Date(stat.mtime);
var mtime = dateObj.getTime();
// create memory cache key
var key = path + mtime;
// check if we have included this file for this view
if (seen.indexOf(key) !== -1) {
log.verbose('file already included [' + key + ']: ignored');
return cb(null, '');
}
seen.push(key);
// check for cache in memory
content = viewList[key] || null;
if (content) {
// cache found > use it
log.verbose('view output data found in cache: ', key);
// handle included files
return parseContent(content, parser, seen, clientData, function (error, contentData) {
if (error) {
return cb(error);
}
cb(null, contentData);
});
}
// no cached data found > read the file
fs.readFile(path, { encoding: 'utf8' }, function (error, file) {
if (error) {
return cb(new Error('failed to load view file: [' + path + ']\n' + JSON.stringify(error, null, 4)));
}
var fileType = path.substring(path.lastIndexOf('.') + 1);
// process file to optimize the output
content = processFile(fileType, file);
// store in memory cache
viewList[key] = content;
log.verbose('view output data stored in cache: ', key);
// handle included files
parseContent(content, parser, seen, clientData, function (error, contentData) {
if (error) {
return cb(error);
}
cb(null, contentData);
});
});
}
function embedData(outputData, clientData) {
// prepare for embedding all the variables in the view template
var clientVars = '<script type="text/javascript">window.gracenode = ' + JSON.stringify(clientData) + ';</script>';
// remove HTML comments
outputData = outputData.replace(/<!--[\s\S]*?-->/g, '');
// embed
return outputData.replace('</head>', clientVars + '\n</head>', 'i');
}
function parseContent(outputData, parser, seen, clientData, cb) {
outputData = embedData(outputData, clientData);
var result = parser.parseData(outputData);
var list = result.includeList;
outputData = result.data;
// include files asynchronously
async.eachSeries(list, function (item, next) {
var tag = item.tag;
var path = item.path;
load(path, seen, clientData, function (error, data) {
if (error) {
return cb(error);
}
outputData = outputData.replace(tag, data);
next();
});
},
function (error) {
if (error) {
return cb(error);
}
cb(null, outputData);
});
}
function processFile(type, data) {
switch (type) {
case 'js':
try {
if (config.minify !== false) {
// FIX ME: too slow
data = uglify.minify(data, { fromString: true }).code;
}
} catch (exp) {
log.error('failed to minify a js file:', exp);
}
break;
case 'css':
if (config.minify !== false) {
// remove line breaks and tabs
data = data.replace(/(\r\n|\n|\r|\t)/gm, '');
}
break;
case 'png':
case 'gif':
case 'jpg':
case 'jpeg':
var bin = new Buffer(data, 'binary').toString('base64');
data = 'data:image/.' + type + ';base64,' + bin;
break;
}
return data;
}