UNPKG

21.1 kBJavaScriptView Raw
1(function(){
2
3 var $=require('./$');
4 var fs=require('fs');
5 var syspath=require('path');
6 var urlParser=require('url');
7 var hogan=require('hogan.js');
8 var vm=require('vm');
9
10 var uglify=require('uglify-js');
11 var cleancss=require('clean-css');
12 var btcss=new cleancss();
13
14 var MD5=require('MD5');
15
16 var root=process.cwd();
17
18 var patterns={
19 prd: /^\/([a-zA-Z0-9_-]+)\/prd\/(.+)@(dev|[a-z0-9]{5,60})\.(js|css)$/,
20 src: /^\/([a-zA-Z0-9_-]+)\/src\/(.+)\.(js|css|mustache)$/,
21 mustache: /.+\.mustache$/,
22 relative: /^\/src\/(.+)\.(js|css|mustache)$/,
23 external: /^(http|https)?\:\/\/.+$/,
24 inline: /^([.a-zA-Z0-9_-]*|\/)+\.(js|css|mustache)$/,
25 pack: /^[\s|^.]*require[\s|^.]*\([\s|^.]*\[([\s|^.]*('|")[\s]*([.a-zA-Z0-9-_]|\/)+\.(js|mustache)[\s]*('|")[\s|^.]*(,)?[\s|^.]*)*[\s|^.]*\](.*|[\s|^.]*)*\)[\s|^.]*(;)?[\s|^.]*/,
26 devpack: /^[\s|^.]*require[\s|^.]*\([\s|^.]*\[([\s|^.]*('|")[\s]*([.a-zA-Z0-9-_:=@?%]|\/)+\.js[\s]*('|")[\s|^.]*(,)?[\s|^.]*)*[\s|^.]*\](.*|[\s|^.]*)*\)[\s|^.]*(;)?[\s|^.]*/,
27 require: /([\s|^.]*)require[\s|^.]*\([\s|^.]*\[([\s|^.]*('|")[\s]*([.a-zA-Z0-9-_:=@?%]|\/)+\.js[\s]*('|")[\s|^.]*(,)?[\s|^.]*)*[\s|^.]*\]/g,
28 csspack: /require\(('|")(([.a-zA-Z0-9_-]|\/)+?\.css)('|")\)(;)?/g
29 };
30
31 var parsers=function(compiler){
32 var src=function(options){
33 this.config=$.extend({
34 },options);
35 };
36
37 $.extend(src.prototype,{
38 parse: function(req){
39 var request=this.parseRequest(req);
40
41 if(request.mode=='pack'){
42 var requires={error:[],files:[]};
43
44 if(request.type=='js'){
45 requires=compiler.getJSRequires(request.filepath);
46 }else if(request.type=='css'){
47 requires=compiler.getCssRequires(request.filepath);
48 }else if(request.type=='mustache'){
49 return 'document.write(\'<script type="text/javascript" src="'+request.protocol+'://'+request.headers.host+'/'+request.project+'/src/'+request.file+'?parent='+encodeURIComponent(request.file)+'"></script>\');';
50 }
51
52 if(requires.error){
53 requires.error.forEach(function(v){
54 console.info(v);
55 });
56 }
57
58 requires.files.forEach(function(v, index){
59 console.info(index+1==requires.files.length?'└──':'├──','(include)'.cyan,v.grey);
60 });
61
62
63 var ret=[];
64 if(request.type=='js'){
65 requires.files.forEach(function(v){
66 ret.push('document.write(\'<script type="text/javascript" src="'+request.protocol+'://'+request.headers.host+'/'+request.project+'/src/'+v+'?parent='+encodeURIComponent(request.file)+'"></script>\');');
67 });
68 }else if(request.type=='css'){
69 requires.files.forEach(function(v){
70 ret.push('@import "'+request.protocol+'://'+request.headers.host+'/'+request.project+'/src/'+v+'?parent='+encodeURIComponent(request.file)+'";');
71 });
72 }
73
74 return ret.join('\n');
75
76 }else if(request.mode=='require'||request.mode=='source'){
77 var code='';
78 if(request.type=='js'){
79 code=this.getJSSource(request);
80 }else if(request.type=='css'){
81 code=this.getCssSource(request);
82 }else if(request.type=='mustache'){
83 code=this.getTplSource(request);
84 }
85
86 return code;
87 }else{
88 //throw new Error('sth. wrong');
89 }
90
91
92 return JSON.stringify(request);
93 },
94
95 getJSSource: function(req){
96
97 var file=compiler.readFile(req.filepath), dir=syspath.dirname(req.file), code=file, parent=req.query.parent||req.query.require;
98
99 var requires=compiler.getJSRequires(syspath.join(req.project,'src',parent));
100
101 if(patterns.pack.test(compiler.removeJSComments(file))){
102 try{
103 var script=vm.createScript(code);
104
105 if(req.mode=='require'){
106 script.runInNewContext({require: function(list,func){
107 if(typeof func=='undefined'){
108 code='require('+JSON.stringify(list)+');';
109 }else{
110 code='require('+JSON.stringify(list)+','+func.toString().replace(/^function[\s]*\(/,'function(')+','+JSON.stringify(req.protocol+'://'+req.headers.host+req.url)+');';
111 }
112 }});
113 }else{
114 script.runInNewContext({require: function(list,func){
115 if(typeof func=='undefined'){
116 code='';
117 }else{
118 code='('+func.toString().replace(/^function[\s]*\(/,'function(')+')(this);';
119 }
120 }});
121 }
122 }catch(e){
123 return code;
124 }
125 }
126
127 code=(code||'').replace(patterns.require,function(hole,pre){
128 var ret=pre;
129
130 var script=vm.createScript(hole+')');
131
132 script.runInNewContext({require: function(list){
133 var children=[];
134 list.forEach(function(v){
135 if(patterns.external.test(v)||!patterns.inline.test(v)){
136 return children.push(v);
137 }
138
139 var path=syspath.join(dir,v).replace(/\\/g,'/');
140
141 if(requires.files.indexOf(path)==-1){
142 children.push(req.protocol+'://'+req.headers.host+'/'+req.project+'/src/'+path+'?require='+encodeURIComponent(parent));
143 }
144 });
145
146 ret+='require('+JSON.stringify(children);
147
148 }});
149
150 return ret;
151 });
152
153 return code;
154 },
155
156 getCssSource: function(req){
157 var code=compiler.readFile(req.filepath);
158 return (code||'').replace(patterns.csspack,'');
159 },
160
161 getTplSource: function(req){
162 var text=compiler.readFile(req.filepath);
163
164 var name=req.file.match(/([a-zA-Z0-9_-]+)\.mustache$/)[1];
165
166 return compiler.parseTemplate(text,name);
167 },
168
169 parseRequest: function(req){
170 var request=compiler.formatRequest(req);
171
172 if(patterns.prd.test(request.path)){
173 request.filepath=request.path.replace(patterns.prd,function(hole/* all url */,project/* project name */,path/* file path without extend */,ver/* version */,type/* js|css */){
174 request.mode='pack';
175 request.file=path+'.'+type;
176 request.project=project;
177 return syspath.join(project,'src',path+'.'+type);
178 });
179 }else if(patterns.src.test(request.path)){
180 request.filepath=request.path.replace(patterns.src,function(hole/* all url */,project/* project name */,path/* file path without extend */,type/* js|css */){
181 request.file=path+'.'+type;
182 request.project=project;
183 return syspath.join(project,'src',path+'.'+type);
184 });
185
186 if(request.query.parent||request.query.require){
187 request.mode=request.query.require?'require':'source';
188 }
189 }
190
191 return request;
192 }
193 });
194
195 var dev=function(options){
196 this.config=$.extend({
197 },options);
198 };
199
200 $.extend(dev.prototype,{
201 parse: function(req){
202 var request=this.parseRequest(req), code='';
203
204 if(request.type=='js'){
205 code=this.getJSSource(request);
206 console.info('└──','[PACK FINISHED]'.green);
207 }else if(request.type=='css'){
208 code=this.getCssSource(request);
209 console.info('└──','[PACK FINISHED]'.green);
210 }else{
211 console.info('└──','[UNKNOWN URL]'.red);
212 }
213 return code;
214 },
215
216 parseRequest: function(req){
217 var request=compiler.formatRequest(req);
218
219 if(patterns.prd.test(request.path)){
220 request.filepath=request.path.replace(patterns.prd,function(hole/* all url */,project/* project name */,path/* file path without extend */,ver/* version */,type/* js|css */){
221 request.file=path+'.'+type;
222 request.project=project;
223 return syspath.join(project,'src',path+'.'+type);
224 });
225 }
226
227 return request;
228 },
229
230 getJSSource: function(request){
231
232 var requires=compiler.getJSRequires(request.query.require?syspath.join(request.project,'src',request.query.require):request.filepath);
233
234 if(requires.error){
235 requires.error.forEach(function(v){
236 console.info(v);
237 });
238 }
239
240 var codes=[];
241 if(!request.query.require){
242 requires.files.forEach(function(v){
243 console.info('├──','(include)'.cyan,v.grey);
244
245 codes.push('/* File '+v+' */');
246
247 var path=syspath.join(request.project,'src',v);
248 var dir=syspath.dirname(v);
249
250 var code=compiler.readFile(path);
251
252 if(patterns.mustache.test(v)){
253 var name=v.match(/([a-zA-Z0-9_-]+)\.mustache$/)[1];
254 code=compiler.parseTemplate(code,name);
255 }else{
256 if(patterns.pack.test(compiler.removeJSComments(code))){
257 try{
258 var script=vm.createScript(code);
259
260 script.runInNewContext({require: function(list,func){
261 if(typeof func=='function'){
262 code='('+func.toString().replace(/^function[\s]*\(/,'function(')+')(this);';
263 }else{
264 code='';
265 }
266 }});
267 }catch(e){
268 }
269 }
270
271 code=(code||'').replace(patterns.require,function(hole,pre){
272 var ret=pre;
273
274 var script=vm.createScript(hole+')');
275
276 script.runInNewContext({require: function(list){
277 var children=[];
278 list.forEach(function(v){
279
280 if(patterns.external.test(v)||!patterns.inline.test(v)){
281 return children.push(v);
282 }
283
284 var rqpath=syspath.join(dir,v).replace(/\\/g,'/');
285
286 if(requires.files.indexOf(rqpath)==-1){
287 if(request.project){
288 children.push(request.protocol+'://'+request.headers.host+'/'+request.project+'/prd/'+rqpath.replace(/\.(js|css)$/,function(ext){ return '@dev'+ext; })+'?require='+encodeURIComponent(request.file));
289 }else{
290 children.push(rqpath.replace(/\.(js|css)$/,function(ext){ return '@dev'+ext; }));
291 }
292 }
293 });
294
295 ret+='require('+JSON.stringify(children);
296
297 }});
298
299 return ret;
300 });
301 }
302
303 codes.push(code+'\n');
304 });
305 }else{
306 if(request.project){
307 console.info('├──','(require)'.cyan,request.file.grey);
308 }
309
310 var code=compiler.readFile(request.filepath);
311 var dir=syspath.dirname(request.file);
312
313 if(code===false){
314 console.info('├──','[ERROR]'.red,request.file+' not exits');
315 return false;
316 }
317
318 code=(code||'').replace(patterns.require,function(hole,pre){
319 var ret=pre;
320 var script=vm.createScript(hole+')');
321 var children=[];
322
323 script.runInNewContext({require: function(list){
324 (list||[]).forEach(function(v){
325
326 if(patterns.external.test(v)||!patterns.inline.test(v)){
327 return children.push(v);
328 }
329
330 var rqpath=syspath.join(dir,v).replace(/\\/g,'/');
331
332 if(requires.files.indexOf(rqpath)==-1){
333
334 if(request.project){
335 children.push(request.protocol+'://'+request.headers.host+'/'+request.project+'/prd/'+rqpath.replace(/\.(js|css)$/,function(ext){ return '@dev'+ext; })+'?require='+encodeURIComponent(request.query.require));
336 }else{
337 children.push(rqpath.replace(/\.(js|css)$/,function(ext){ return '@dev'+ext; }));
338 }
339 }
340 });
341 }});
342
343 ret+='require('+JSON.stringify(children);
344
345 return ret;
346 });
347
348 if(patterns.devpack.test(compiler.removeJSComments(code))){
349 var script=vm.createScript(code);
350 script.runInNewContext({require: function(list,func){
351 if(request.project){
352 code='require('+JSON.stringify(list)+','+func.toString().replace(/^function[\s]*\(/,'function(')+','+JSON.stringify(request.protocol+'://'+request.headers.host+request.url)+');';
353 }else{
354 code='require('+JSON.stringify(list)+','+func.toString().replace(/^function[\s]*\(/,'function(')+','+JSON.stringify(request.file.replace(/\.(js|css)$/,function(ext){ return '@dev'+ext; }))+');';
355 }
356 }});
357 }
358
359 codes.push(code);
360 }
361
362 return codes.join('\n');
363 },
364
365 getCssSource: function(request){
366 var requires=compiler.getCssRequires(request.filepath), ret=[];
367
368 if(requires.error){
369 requires.error.forEach(function(v){
370 console.info(v);
371 });
372 }
373
374 requires.files.forEach(function(v){
375 console.info('├──','(include)'.cyan,v.grey);
376
377 ret.push('/* File '+v+' */');
378
379 ret.push((compiler.readFile(syspath.join(request.project,'src',v))||'').replace(patterns.csspack,'').replace(/^[\s]*/,'').replace(/[\s]*$/,'')+'\n');
380 });
381
382 return ret.join('\n');
383 }
384 });
385
386 var prd=function(){
387 };
388
389 $.extend(prd.prototype,{
390 parse: function(req){
391 var request=pack.dev.parseRequest(req), code='';
392
393 if(request.type=='js'){
394 code=uglify.minify(pack.dev.getJSSource(request),{fromString: true}).code;
395 console.info('└──','[MINIFY FINISHED]'.green);
396 }else if(request.type=='css'){
397 var ret=btcss.minify(pack.dev.getCssSource(request));
398 code=ret.styles;
399 console.info('└──','[MINIFY FINISHED]'.green);
400
401 if(ret.errors.length){
402 console.info(' ','[ERROR]'.red,ret.errors);
403 }
404 }else{
405 console.info('└──','[UNKNOWN URL]'.red);
406 }
407 return code;
408 },
409
410 parseRequest: function(req){
411 return pack.dev.parseRequest(req);
412 }
413 });
414
415 var pack={
416 src: new src(),
417 dev: new dev(),
418 prd: new prd()
419 };
420
421 return $.extend({
422 parseRequest: function(req,mode){
423 return pack[mode.toLowerCase()].parseRequest(req);
424 },
425
426 isPrd: function(path){
427 return patterns.prd.test(path);
428 },
429
430 isSrc: function(path){
431 return patterns.src.test(path);
432 },
433
434 getAllRequires: function(path){
435 return {
436 requires: compiler.getJSRequires(path),
437 inlines: compiler.getJSInlineRequires(path)
438 }
439 },
440
441 getInlineRequires: function(path, parent){
442 return compiler.getOneJSinlineRequires(path, parent);
443 }
444 },pack);
445
446 }({
447 formatRequest: function(ret){
448 if(/.+\.js$/.test(ret.path)){
449 ret.type='js';
450 }else if(/.+\.css$/.test(ret.path)){
451 ret.type='css';
452 }else if(/.+\.mustache$/.test(ret.path)){
453 ret.type='mustache';
454 }
455
456 return ret;
457 },
458
459 removeJSComments: function(code){
460 return (code||'').replace(/(?:^|\n|\r)\s*\/\*[\s\S]*?\*\/\s*(?:\r|\n|$)/g,'\n').replace(/(?:^|\n|\r)\s*\/\/.*(?:\r|\n|$)/g,'\n');
461 },
462
463 removeCssComment: function(code){
464 return (code||'').replace(/(?:^|\n|\r)\s*\/\*[\s\S]*?\*\/\s*(?:\r|\n|$)/g,'\n');
465 },
466
467 getRelativePath: function(p){
468
469 path='/'+p.replace(/\\/g,'/');
470 if(patterns.src.test(path)){
471 return path.replace(patterns.src,function(hole/* all url */,project,path/* file path without extend */,type/* js|css */){
472 return path+'.'+type;
473 });
474 }else if(patterns.relative.test(path)){
475 return path.replace(patterns.relative,function(hole/* all url */,path/* file path without extend */,type/* js|css */){
476 return path+'.'+type;
477 });
478 }
479 return p.replace(/\\/g,'/');
480 },
481
482 getProject: function(path){
483 return ('/'+path.replace(/\\/g,'/')).replace(patterns.src,function(hole/* all url */,project/* project name */,path/* file path without extend */,type/* js|css */){
484 return project;
485 });
486 },
487
488 getJSRequires: function(p){
489 var cache={};
490
491 var ret=$.proxy(function(holepath){
492
493 if(cache[this.getRelativePath(holepath)]){
494 return {
495 error: [],
496 files: []
497 };
498 }
499 var file=this.readFile(holepath), dir=syspath.dirname(holepath);
500
501 var hole=this.removeJSComments(file||''), _self=$.proxy(arguments.callee,this), _this=this, error=(file===false?[['├──','[WARN]'.yellow,holepath,'not exist'.yellow].join(' ')]:[]);
502
503 cache[this.getRelativePath(holepath)]=true;
504
505 var files=[];
506 //pack mode
507 if(patterns.pack.test(hole)){
508 var children=[];
509 try{
510 var script=vm.createScript(hole);
511 script.runInNewContext({require: function(list,func){
512 children=list;
513 }});
514 }catch(e){
515 }
516
517 (children||[]).forEach(function(v){
518
519 var path=syspath.join(dir,v);
520
521 if(cache[_this.getRelativePath(path)]){
522 return;
523 }
524
525 var r=_self(path);
526
527 error=error.concat(r.error||[]);
528 files=files.concat(r.files||[]);
529 });
530 }
531
532 files.push(this.getRelativePath(holepath));
533
534 return {
535 error: error,
536 files: files
537 };
538 },this);
539
540 return ret(p);
541 },
542
543 getOneJSinlineRequires: function(p, parent){
544
545 var parents=[];
546
547 if(parent){
548 parents=this.getJSRequires(parent).files||[];
549 }
550
551 var relativePath=this.getRelativePath(p), _this=this;
552 var path=syspath.join('src',relativePath);
553
554 var code=this.readFile(path), dir=syspath.dirname(relativePath), ret=[];
555 (code||'').replace(patterns.require,function(hole,pre){
556 var script=vm.createScript(hole+')');
557 script.runInNewContext({require: function(list){
558 var children=[];
559 list.forEach(function(v){
560 if(patterns.external.test(v)||!patterns.inline.test(v)){
561 return;
562 }
563
564 var path=syspath.join(dir,v).replace(/\\/g,'/');
565
566 if(parents.indexOf(path)==-1&&ret.indexOf(path)==-1){
567 ret.push(path);
568 }
569 });
570
571 }});
572 });
573
574 if(parent==p){
575 for( var i=0; i<parents.length; i++){
576 if(parents[i]!=relativePath){
577 ret=ret.concat(arguments.callee.call(this,syspath.join('src',parents[i]),parent));
578 }
579 }
580 }
581
582 return ret;
583 },
584
585 getJSInlineRequires: function(p){
586
587 var relativePath=this.getRelativePath(p), _this=this;
588
589 var analysised={}, inlineRequires=[], parents=this.getJSRequires(p).files;
590
591 var getRequires=function(relative){
592
593 var path=syspath.join('src',relative);
594
595 if(analysised[path]){
596 return;
597 }
598 analysised[path]=true;
599
600 var requires=_this.getJSRequires(path);
601
602 requires.files.forEach(function(v){
603
604 if(analysised[v]){
605 return;
606 }
607
608 analysised[v]=true;
609
610 var filepath=syspath.join('src',v), dir=syspath.dirname(v);
611 var code=_this.readFile(filepath), analysis=arguments.callee;
612
613 if(!code){
614 return;
615 }
616
617 /*
618 if(patterns.pack.test(_this.removeJSComments(code))){
619 var script=vm.createScript(code);
620
621 script.runInNewContext({require: function(list,func){
622 code=func.toString();
623 }});
624 }*/
625
626 code=(code||'').replace(patterns.require,function(hole,pre){
627 var script=vm.createScript(hole+')');
628 script.runInNewContext({require: function(list){
629 var children=[];
630 list.forEach(function(v){
631 if(patterns.external.test(v)||!patterns.inline.test(v)){
632 return;
633 }
634
635 var path=syspath.join(dir,v).replace(/\\/g,'/');
636
637 if(parents.indexOf(path)==-1){
638 inlineRequires.push(path);
639
640 getRequires(path);
641 }
642 });
643
644 }});
645 });
646 });
647 };
648
649 getRequires(relativePath);
650
651 return inlineRequires;
652 },
653
654 getCssRequires: function(p){
655 var cache={};
656
657 var ret=$.proxy(function(holepath){
658
659 if(cache[this.getRelativePath(holepath)]){
660 return {
661 error: [],
662 files: []
663 };
664 }
665
666 var file=this.readFile(holepath), dir=syspath.dirname(holepath);
667 var hole=this.removeJSComments(file||''), _self=$.proxy(arguments.callee,this), _this=this, error=(file===false?[['#'.grey,'[WARN]'.yellow,holepath,'not exist'.yellow].join(' ')]:[]);
668
669 cache[this.getRelativePath(holepath)]=true;
670
671 var files=[];
672
673 hole.replace(patterns.csspack,function(hole,__i,inc){
674 var _path=syspath.join(dir,inc);
675 if(cache[_this.getRelativePath(_path)]){
676 return;
677 }
678
679 var r=_self(_path);
680
681 files=files.concat(r.files);
682 error=error.concat(r.error);
683 return '';
684 });
685
686 files.push(this.getRelativePath(holepath));
687
688 return {
689 files: files,
690 error: error
691 };
692
693 },this);
694
695 return ret(p);
696 },
697
698 parseTemplate: function(text, name){
699 var code='if(typeof window.TPL === "undefined"){ window.TPL={}; }\n';
700 code+= 'window.TPL.' + name + ' = new window.Hogan.Template(' + hogan.compile(text, { asString: 1}) + ');';
701 return code;
702 },
703
704 readFile: function(path){
705 path=syspath.join(root,path);
706
707 if(!fs.existsSync(path)){
708 return false;
709 }else{
710 return fs.readFileSync(path).toString();
711 }
712 }
713 });
714
715 $.extend(exports,parsers);
716
717}).call(this);
\No newline at end of file