Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

361 linhas
9.1 KiB

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