您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

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