You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

203 lines
5.5 KiB

  1. # Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
  2. #
  3. # MIT License (MIT)
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a
  6. # copy of this software and associated documentation files (the "Software"),
  7. # to deal in the Software without restriction, including without limitation
  8. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9. # and/or sell copies of the Software, and to permit persons to whom the
  10. # Software is furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  16. # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17. # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  18. # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  19. # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  20. # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. #
  22. from __future__ import unicode_literals
  23. from webnotes.utils.minify import JavascriptMinify
  24. import os, sys
  25. class Bundle:
  26. """
  27. Concatenate, compress and mix (if required) js+css files from build.json
  28. """
  29. no_compress = False
  30. timestamps = {}
  31. path = '.'
  32. def concat(self, filelist, outfile=None):
  33. """
  34. Concat css and js files into a bundle
  35. """
  36. from cStringIO import StringIO
  37. out_type = outfile and outfile.split('.')[-1] or 'js'
  38. outtxt = ''
  39. for f in filelist:
  40. suffix = None
  41. if ':' in f:
  42. f, suffix = f.split(':')
  43. if not os.path.exists(f) or os.path.isdir(f):
  44. continue
  45. self.timestamps[f] = os.path.getmtime(f)
  46. # get datas
  47. with open(f, 'r') as infile:
  48. # get file type
  49. ftype = f.split('.')[-1]
  50. data = unicode(infile.read(), 'utf-8')
  51. outtxt += ('\n/*\n *\t%s\n */' % f)
  52. # append
  53. if suffix=='concat' or out_type != 'js' or self.no_compress or ('.min.' in f):
  54. outtxt += '\n' + data + '\n'
  55. else:
  56. jsm = JavascriptMinify()
  57. tmpin = StringIO(data.encode('utf-8'))
  58. tmpout = StringIO()
  59. jsm.minify(tmpin, tmpout)
  60. tmpmin = unicode(tmpout.getvalue() or '', 'utf-8')
  61. tmpmin.strip('\n')
  62. outtxt += tmpmin
  63. with open(outfile, 'w') as f:
  64. f.write(outtxt.encode("utf-8"))
  65. print "Wrote %s - %sk" % (outfile, str(int(os.path.getsize(outfile)/1024)))
  66. def dirty(self):
  67. """check if build files are dirty"""
  68. self.make_build_data()
  69. for builddict in self.bdata:
  70. for f in self.get_infiles(builddict):
  71. if ':' in f:
  72. f, suffix = f.split(':')
  73. if not os.path.exists(f) or os.path.isdir(f):
  74. continue
  75. if os.path.getmtime(f) != self.timestamps.get(f):
  76. print f + ' dirty'
  77. return True
  78. else:
  79. return False
  80. def make(self):
  81. """Build (stitch + compress) the file defined in build.json"""
  82. print "Building js and css files..."
  83. self.make_build_data()
  84. for builddict in self.bdata:
  85. outfile = builddict.keys()[0]
  86. infiles = self.get_infiles(builddict)
  87. self.concat(infiles, os.path.relpath(os.path.join(self.path, outfile), os.curdir))
  88. self.reset_app_html()
  89. def reset_app_html(self):
  90. import webnotes
  91. if os.path.exists("public/app.html"):
  92. os.remove("public/app.html")
  93. with open('lib/public/html/app.html', 'r') as app_html:
  94. data = app_html.read()
  95. data = data % {"_version_number": webnotes.generate_hash() }
  96. with open('public/app.html', 'w') as new_app_html:
  97. new_app_html.write(data)
  98. def get_infiles(self, builddict):
  99. """make list of files to merge"""
  100. outfile = builddict.keys()[0]
  101. infiles = builddict[outfile]
  102. # add app js and css to the list
  103. if outfile in self.appfiles:
  104. for f in self.appfiles[outfile]:
  105. if f not in infiles:
  106. infiles.append(f)
  107. fl = []
  108. for f in infiles:
  109. ## load files from directory
  110. if f.endswith('/'):
  111. # add init js first
  112. fl += [os.path.relpath(os.path.join(f, 'init.js'), os.curdir)]
  113. # files other than init.js and beginning with "_"
  114. fl += [os.path.relpath(os.path.join(f, tmp), os.curdir) \
  115. for tmp in os.listdir(f) if (tmp != 'init.js' and not tmp.startswith('_'))]
  116. else:
  117. fl.append(os.path.relpath(os.path.join(self.path, f), os.curdir))
  118. return fl
  119. def make_build_data(self):
  120. """merge build.json and lib/build.json"""
  121. # framework js and css files
  122. with open('lib/public/build.json', 'r') as bfile:
  123. bdata = eval(bfile.read())
  124. # app js and css files
  125. if os.path.exists('app/public/build.json'):
  126. with open('app/public/build.json', 'r') as bfile:
  127. appfiles = eval(bfile.read())
  128. else:
  129. appfiles = {}
  130. # add additional app files in bdata
  131. buildfile_list = [builddict.keys()[0] for builddict in bdata]
  132. for f in appfiles:
  133. if f not in buildfile_list:
  134. bdata.append({f: appfiles[f]})
  135. self.appfiles = appfiles
  136. self.bdata = bdata
  137. def check_public():
  138. from webnotes.install_lib.setup_public_folder import make
  139. make()
  140. def bundle(no_compress, cms_make=True):
  141. """concat / minify js files"""
  142. # build js files
  143. check_public()
  144. bundle = Bundle()
  145. bundle.no_compress = no_compress
  146. bundle.make()
  147. if cms_make:
  148. # build index.html and app.html
  149. from website.helpers.make_web_include_files import make
  150. make()
  151. def watch(no_compress):
  152. """watch and rebuild if necessary"""
  153. import time
  154. bundle = Bundle()
  155. bundle.no_compress = no_compress
  156. while True:
  157. if bundle.dirty():
  158. bundle.make()
  159. time.sleep(3)