UNPKG

8.03 kBJavaScriptView Raw
1"use strict";
2
3var series = require('async').series;
4var clone = require('clone');
5var compile = require('es6-templates').compile;
6var extend = require('extend');
7var fs = require('fs');
8var isarray = require('isarray');
9var join = require('path').join;
10var dirname = require('path').dirname;
11
12
13// -----------------------------------------------------------------------------
14// Configuration.
15var defaults = {
16 base: '/',
17 target: 'es6',
18 indent: 2,
19 useRelativePaths: false,
20 removeLineBreaks: false,
21 templateExtension: '.html',
22 templateFunction: false,
23 templateProcessor: defaultProcessor,
24 styleProcessor: defaultProcessor,
25 customFilePath: defaultCustomFilePath,
26 supportNonExistentFiles: false
27};
28
29function defaultProcessor(path, file, cb) {
30 return cb(null, file);
31}
32
33function defaultCustomFilePath(ext, path) {
34 return path;
35}
36
37var htmlOptions = function (opts) {
38 return {
39 type: 'html',
40 prop_url: 'templateUrl',
41 prop: 'template',
42 start_pattern: /templateUrl\s*:.*/,
43 end_pattern: new RegExp('.*\\' + opts.templateExtension + '\s*(\'\\)|\')|.*\\' + opts.templateExtension + '\s*("\\)|")'),
44 oneliner_pattern: new RegExp('templateUrl.*(\\' + opts.templateExtension + '\s*(\'\\)|\')|\\' + opts.templateExtension + 's*("\\)|"))')
45 };
46};
47
48var cssOptions = function () {
49 return {
50 type: 'css',
51 prop_url: 'styleUrls',
52 prop: 'styles',
53 start_pattern: /styleUrls\s*:.*/,
54 end_pattern: /.*]/,
55 oneliner_pattern: /styleUrls(.*?)]/
56 };
57};
58
59
60module.exports = function parser(file, options) {
61 var opts = extend({}, defaults, (options || {}));
62 var lines = file.contents.toString().replace(/\r/g, '').split('\n');
63 var start_line_idx, end_line_idx, frag;
64 var HTML = false;
65 var CSS = false;
66
67 return function parse(done) {
68 series([
69 processTemplate,
70 processStyles
71 ], function () {
72 done(null, lines.join('\n'));
73 });
74 };
75
76
77 function processTemplate(done) {
78 if (opts.templateProcessor) {
79 HTML = true;
80 extend(opts, htmlOptions(opts));
81 execute(function () {
82 reset();
83 done(null);
84 });
85 }
86 }
87
88 function processStyles(done) {
89 if (opts.styleProcessor) {
90 CSS = true;
91 extend(opts, cssOptions());
92 execute(function () {
93 reset();
94 done(null);
95 });
96 }
97 }
98
99 function execute(done) {
100 var seriesArray = [];
101
102 for(var i = 0; i < lines.length; i++) {
103 (function (i) {
104 seriesArray.push(function (cb) {
105 var idx = i;
106 var line = lines[idx];
107 getIndexes(line, idx);
108 if (i === end_line_idx && start_line_idx) {
109 series([
110 getFragment,
111 replaceFrag
112 ], cb);
113 } else {
114 cb(null);
115 }
116 });
117 }(i));
118 }
119
120 series(seriesArray, done);
121 }
122
123 function getIndexes(line, i) {
124 if (opts.start_pattern.test(line)) {
125 start_line_idx = i;
126 }
127 if (opts.end_pattern.test(line)) {
128 // Match end pattern without start.
129 // end_line_idx is till equal to previous loop turn value.
130 if (start_line_idx <= end_line_idx) return;
131 end_line_idx = i;
132 }
133 }
134
135 function getFragment(cb) {
136 var fragStart, fragEnd;
137 if (start_line_idx < 0 || end_line_idx < 0) return cb();
138 // One liner.
139 if (start_line_idx === end_line_idx) {
140 frag = opts.oneliner_pattern.exec(lines[start_line_idx])[0];
141 }
142 // One or more lines.
143 if (start_line_idx < end_line_idx) {
144 fragStart = opts.start_pattern.exec(lines[start_line_idx])[0];
145 fragEnd = opts.end_pattern.exec(lines[end_line_idx])[0];
146 frag = concatLines();
147 }
148
149 cb();
150
151 function concatLines() {
152 var _lines = clone(lines);
153 _lines[start_line_idx] = fragStart;
154 _lines[end_line_idx] = fragEnd;
155 return _lines.splice(start_line_idx, end_line_idx - start_line_idx + 1).join('');
156 }
157 }
158
159 function replaceFrag(cb) {
160 var _urls;
161 var fnIndex = frag.indexOf('(');
162 if (fnIndex > -1 && opts.templateFunction) {
163 // Using template function.
164 _urls = opts.templateFunction(/'(.*)'/.exec(frag)[1]);
165 } else {
166 _urls = eval('({' + frag + '})')[opts.prop_url];
167 }
168
169 var urls = isarray(_urls) ? _urls : [_urls];
170 var line = lines[start_line_idx];
171 var indentation = /^\s*/.exec(line)[0];
172 var assetFiles = '';
173 var startOfInsertionBlock = '\n';
174 var endOfInsertionBlock = '\n';
175
176 series([
177 getFilesAndApplyCustomProcessor,
178 _replaceFrag
179 ], cb);
180
181 function _replaceFrag(cb) {
182 // Trim trailing line breaks and unescape any backtick characters present.
183 assetFiles = assetFiles.replace(/(\n*)$/, '').replace(/`/g, '\\`');
184
185 // We don't need indentation if we are going to insert it as one line
186 if(!opts.removeLineBreaks) {
187 // Indent content.
188 assetFiles = indent(assetFiles);
189 }
190
191 if(opts.removeLineBreaks) {
192 assetFiles = removeLineBreaks(assetFiles);
193 // don't need the indentation
194 indentation = '';
195 startOfInsertionBlock = '';
196 endOfInsertionBlock = '';
197 }
198
199 // Build the final string.
200 if ('html' === opts.type)
201 assetFiles = opts.prop + ': `' + startOfInsertionBlock + assetFiles + endOfInsertionBlock + indentation + '`';
202 if ('css' === opts.type)
203 assetFiles = opts.prop + ': [`' + startOfInsertionBlock + assetFiles + endOfInsertionBlock + indentation + '`]';
204 if ('es5' === opts.target) assetFiles = compile(assetFiles).code;
205
206 // One liner.
207 if (start_line_idx === end_line_idx) {
208 lines[start_line_idx] = line.replace(opts.oneliner_pattern, assetFiles);
209 }
210 // One or more lines.
211 if (start_line_idx < end_line_idx) {
212 if (/(,)$/.test(lines[end_line_idx])) assetFiles += ',';
213 lines[start_line_idx] = line.replace(opts.start_pattern, assetFiles);
214 lines.splice(start_line_idx + 1, end_line_idx - start_line_idx);
215 }
216
217 cb(null);
218 }
219
220 function getFilesAndApplyCustomProcessor(cb) {
221 series(urls.map(function (url) {
222 return function (cb) {
223 var file = getFile(url);
224 var ext = /\.[0-9a-z]+$/i.exec(url);
225 if (HTML && opts.templateProcessor) {
226 process.nextTick(function () {
227 opts.templateProcessor(ext, file, customProcessorCallback(cb));
228 });
229 }
230 if (CSS && opts.styleProcessor) {
231 process.nextTick(function () {
232 opts.styleProcessor(ext, file, customProcessorCallback(cb));
233 });
234 }
235 };
236 }), cb);
237
238 function customProcessorCallback(cb) {
239 return function (err, file) {
240 if (err) return cb(err);
241 assetFiles += file;
242 process.nextTick(cb);
243 };
244 }
245 }
246
247 function getFile(filepath) {
248 var absPath = opts.useRelativePaths ? join(dirname(file.path), filepath)
249 : join(process.cwd(), opts.base, filepath);
250
251 if(opts.supportNonExistentFiles && !fs.existsSync(absPath)) {
252 return '';
253 }
254
255 var ext = /\.[0-9a-z]+$/i.exec(absPath);
256 absPath = opts.customFilePath(ext, absPath);
257
258 return fs.readFileSync(absPath)
259 .toString()
260 .replace(/\r/g, '')
261 .replace(/[\u200B-\u200D\uFEFF]/g, '');
262 }
263
264 function indent(str) {
265 var lines = [];
266 var spaces = '';
267 for (var i = 0; i < indentation.length + opts.indent; i++) { spaces += ' '; }
268 str.split('\n').forEach(function (line) {
269 // Add indentation spaces only to non-empty lines.
270 lines.push((/^(\s*)$/.test(line) ? '' : spaces) + line);
271 });
272 return lines.join('\n');
273 }
274
275 function removeLineBreaks(str) {
276 return str.replace(/(\r\n|\n|\r)/gm," ");
277 }
278 }
279
280 function reset() {
281 start_line_idx = undefined;
282 end_line_idx = undefined;
283 frag = undefined;
284 HTML = false;
285 CSS = false;
286 }
287};