Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

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