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.
 
 
 
 
 
 

322 regels
7.9 KiB

  1. const path = require('path');
  2. const fs = require('fs');
  3. const babel = require('babel-core');
  4. const less = require('less');
  5. const chokidar = require('chokidar');
  6. const path_join = path.resolve;
  7. // for file watcher
  8. const app = require('express')();
  9. const http = require('http').Server(app);
  10. const io = require('socket.io')(http);
  11. // basic setup
  12. const sites_path = path_join(__dirname, '..', '..', '..', 'sites');
  13. const apps_path = path_join(__dirname, '..', '..', '..', 'apps'); // the apps folder
  14. const apps_contents = fs.readFileSync(path_join(sites_path, 'apps.txt'), 'utf8');
  15. const apps = apps_contents.split('\n');
  16. const app_paths = apps.map(app => path_join(apps_path, app, app)) // base_path of each app
  17. const assets_path = path_join(sites_path, 'assets');
  18. const build_map = make_build_map();
  19. const file_watcher_port = get_conf().file_watcher_port;
  20. // command line args
  21. const action = process.argv[2] || '--build';
  22. if (['--build', '--watch'].indexOf(action) === -1) {
  23. console.log('Invalid argument: ', action);
  24. process.exit();
  25. }
  26. if (action === '--build') {
  27. const minify = process.argv[3] === '--minify' ? true : false;
  28. build(minify);
  29. }
  30. if (action === '--watch') {
  31. watch();
  32. }
  33. function build(minify) {
  34. for (const output_path in build_map) {
  35. pack(output_path, build_map[output_path], minify);
  36. }
  37. }
  38. let socket_connection = false;
  39. function watch() {
  40. http.listen(file_watcher_port, function () {
  41. console.log('file watching on *:', file_watcher_port);
  42. });
  43. compile_less().then(() => {
  44. build();
  45. watch_less(function (filename) {
  46. if(socket_connection) {
  47. io.emit('reload_css', filename);
  48. }
  49. });
  50. watch_js(function (filename) {
  51. if(socket_connection) {
  52. io.emit('reload_js', filename);
  53. }
  54. });
  55. });
  56. io.on('connection', function (socket) {
  57. socket_connection = true;
  58. socket.on('disconnect', function() {
  59. socket_connection = false;
  60. })
  61. });
  62. }
  63. function pack(output_path, inputs, minify) {
  64. const output_type = output_path.split('.').pop();
  65. let output_txt = '';
  66. for (const file of inputs) {
  67. if (!fs.existsSync(file)) {
  68. console.log('File not found: ', file);
  69. continue;
  70. }
  71. let file_content = fs.readFileSync(file, 'utf-8');
  72. if (file.endsWith('.html') && output_type === 'js') {
  73. file_content = html_to_js_template(file, file_content);
  74. }
  75. if(file.endsWith('class.js')) {
  76. file_content = minify_js(file_content, file);
  77. }
  78. if (file.endsWith('.js') && !file.includes('/lib/') && output_type === 'js' && !file.endsWith('class.js')) {
  79. file_content = babelify(file_content, file, minify);
  80. }
  81. if(!minify) {
  82. output_txt += `\n/*\n *\t${file}\n */\n`
  83. }
  84. output_txt += file_content;
  85. output_txt = output_txt.replace(/['"]use strict['"];/, '');
  86. }
  87. const target = path_join(assets_path, output_path);
  88. try {
  89. fs.writeFileSync(target, output_txt);
  90. console.log(`Wrote ${output_path} - ${get_file_size(target)}`);
  91. return target;
  92. } catch (e) {
  93. console.log('Error writing to file', output_path);
  94. console.log(e);
  95. }
  96. }
  97. function babelify(content, path, minify) {
  98. let presets = ['es2015', 'es2016'];
  99. // if(minify) {
  100. // presets.push('babili'); // new babel minifier
  101. // }
  102. try {
  103. return babel.transform(content, {
  104. presets: presets,
  105. comments: false
  106. }).code;
  107. } catch (e) {
  108. console.log('Cannot babelify', path);
  109. console.log(e);
  110. return content;
  111. }
  112. }
  113. function minify_js(content, path) {
  114. try {
  115. return babel.transform(content, {
  116. comments: false
  117. }).code;
  118. } catch (e) {
  119. console.log('Cannot minify', path);
  120. console.log(e);
  121. return content;
  122. }
  123. }
  124. function make_build_map() {
  125. const build_map = {};
  126. for (const app_path of app_paths) {
  127. const build_json_path = path_join(app_path, 'public', 'build.json');
  128. if (!fs.existsSync(build_json_path)) continue;
  129. let build_json = fs.readFileSync(build_json_path);
  130. try {
  131. build_json = JSON.parse(build_json);
  132. } catch (e) {
  133. console.log(e);
  134. continue;
  135. }
  136. for (const target in build_json) {
  137. const sources = build_json[target];
  138. const new_sources = [];
  139. for (const source of sources) {
  140. const s = path_join(app_path, source);
  141. new_sources.push(s);
  142. }
  143. if (new_sources.length)
  144. build_json[target] = new_sources;
  145. else
  146. delete build_json[target];
  147. }
  148. Object.assign(build_map, build_json);
  149. }
  150. return build_map;
  151. }
  152. function compile_less() {
  153. return new Promise(function (resolve) {
  154. const promises = [];
  155. for (const app_path of app_paths) {
  156. const public_path = path_join(app_path, 'public');
  157. const less_path = path_join(public_path, 'less');
  158. if (!fs.existsSync(less_path)) continue;
  159. const files = fs.readdirSync(less_path);
  160. for (const file of files) {
  161. if(file.includes('variables.less')) continue;
  162. promises.push(compile_less_file(file, less_path, public_path))
  163. }
  164. }
  165. Promise.all(promises).then(() => {
  166. console.log('Less files compiled');
  167. resolve();
  168. });
  169. });
  170. }
  171. function compile_less_file(file, less_path, public_path) {
  172. const file_content = fs.readFileSync(path_join(less_path, file), 'utf8');
  173. const output_file = file.split('.')[0] + '.css';
  174. console.log('compiling', file);
  175. return less.render(file_content, {
  176. paths: [less_path],
  177. filename: file,
  178. sourceMap: false
  179. }).then(output => {
  180. const out_css = path_join(public_path, 'css', output_file);
  181. fs.writeFileSync(out_css, output.css);
  182. return out_css;
  183. }).catch(e => {
  184. console.log('Error compiling ', file);
  185. console.log(e);
  186. });
  187. }
  188. function watch_less(ondirty) {
  189. const less_paths = app_paths.map(path => path_join(path, 'public', 'less'));
  190. const to_watch = [];
  191. for (const less_path of less_paths) {
  192. if (!fs.existsSync(less_path)) continue;
  193. to_watch.push(less_path);
  194. }
  195. chokidar.watch(to_watch).on('change', (filename, stats) => {
  196. console.log(filename, 'dirty');
  197. var last_index = filename.lastIndexOf('/');
  198. const less_path = filename.slice(0, last_index);
  199. const public_path = path_join(less_path, '..');
  200. filename = filename.split('/').pop();
  201. compile_less_file(filename, less_path, public_path)
  202. .then(css_file_path => {
  203. // build the target css file for which this css file is input
  204. for (const target in build_map) {
  205. const sources = build_map[target];
  206. if (sources.includes(css_file_path)) {
  207. pack(target, sources);
  208. ondirty && ondirty(target);
  209. break;
  210. }
  211. }
  212. })
  213. });
  214. }
  215. function watch_js(ondirty) {
  216. const js_paths = app_paths.map(path => path_join(path, 'public', 'js'));
  217. const to_watch = [];
  218. for (const js_path of js_paths) {
  219. if (!fs.existsSync(js_path)) continue;
  220. to_watch.push(js_path);
  221. }
  222. chokidar.watch(to_watch).on('change', (filename, stats) => {
  223. console.log(filename, 'dirty');
  224. var last_index = filename.lastIndexOf('/');
  225. const js_path = filename.slice(0, last_index);
  226. const public_path = path_join(js_path, '..');
  227. // build the target js file for which this js/html file is input
  228. for (const target in build_map) {
  229. const sources = build_map[target];
  230. if (sources.includes(filename)) {
  231. pack(target, sources);
  232. ondirty && ondirty(target);
  233. }
  234. }
  235. });
  236. }
  237. function html_to_js_template(path, content) {
  238. let key = path.split('/');
  239. key = key[key.length - 1];
  240. key = key.split('.')[0];
  241. content = scrub_html_template(content);
  242. return `frappe.templates['${key}'] = '${content}';\n`;
  243. }
  244. function scrub_html_template(content) {
  245. content = content.replace(/\s/g, ' ');
  246. content = content.replace(/(<!--.*?-->)/g, '');
  247. return content.replace("'", "\'");
  248. }
  249. function get_file_size(filepath) {
  250. const stats = fs.statSync(filepath);
  251. const size = stats.size;
  252. // convert it to humanly readable format.
  253. const i = Math.floor(Math.log(size) / Math.log(1024));
  254. return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];
  255. }
  256. function get_conf() {
  257. // defaults
  258. var conf = {
  259. file_watcher_port: 6787
  260. };
  261. var read_config = function(path) {
  262. if (!fs.existsSync(path)) return;
  263. var bench_config = JSON.parse(fs.readFileSync(path));
  264. for (var key in bench_config) {
  265. if (bench_config[key]) {
  266. conf[key] = bench_config[key];
  267. }
  268. }
  269. }
  270. read_config(path_join(sites_path, 'common_site_config.json'));
  271. return conf;
  272. }