PageRenderTime 52ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/src/build/compile.js

https://github.com/xinlut1990/devkit
JavaScript | 359 lines | 227 code | 74 blank | 58 comment | 33 complexity | 03133ac8ea53c4f32b80b3ad51e7f911 MD5 | raw file
Possible License(s): GPL-3.0
  1. /** @license
  2. * This file is part of the Game Closure SDK.
  3. *
  4. * The Game Closure SDK is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. * The Game Closure SDK is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. * You should have received a copy of the GNU General Public License
  13. * along with the Game Closure SDK. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. var fs = require('fs');
  16. var path = require('path');
  17. var crypto = require('crypto');
  18. var clc = require('cli-color');
  19. var _uglify = require('uglify-js');
  20. var parser = _uglify.parser;
  21. var uglify = _uglify.uglify;
  22. var common = require("../common");
  23. var addonManager = require("../AddonManager");
  24. var jvmtools = require("./jvmtools");
  25. // Import the external js.io compiler.
  26. var jsio = require(common.paths.sdk('jsio/jsio'));
  27. jsio.path.add(common.paths.lib("js.io/compilers/"));
  28. jsio('import jsio_compile.compiler');
  29. var compile = this;
  30. /**
  31. * Arguments.
  32. */
  33. var argv = require('optimist')
  34. .alias('verbose', 'v').describe('verbose', 'Verbose mode.').boolean('verbose')
  35. .argv;
  36. /**
  37. * Default path for game js.io compilation.
  38. */
  39. compile.JSIO_SDK_PATHS = [
  40. 'sdk/jsio',
  41. 'sdk/gc/api/',
  42. 'sdk/',
  43. 'sdk/timestep/',
  44. ];
  45. var logger = new common.Formatter('jsio');
  46. var compressLog = new common.Formatter('compressor');
  47. /**
  48. * Compile an entry with a set of js.io options.
  49. */
  50. var BuildQueue = compile.BuildQueue = common.BuildQueue;
  51. /**
  52. * Interface to compile js.io
  53. */
  54. var BasilJsioInterface = Class(function () {
  55. this.logger = logging.get('jsioCompile');
  56. this.setNext = function (next) {
  57. this._next = next;
  58. }
  59. // interface methods for jsioCompile hooks
  60. this.setCompiler = function (compiler) {
  61. this._compiler = compiler;
  62. }
  63. this.run = function (args, opts) {
  64. this._compiler.run(args, opts);
  65. }
  66. this.onError = function (opts, msg) {
  67. this.logger.error(msg);
  68. if (this._next) {
  69. this._next(msg, null);
  70. }
  71. }
  72. this.onFinish = function (opts, src) {
  73. if (this._next) {
  74. this._next(null, src);
  75. }
  76. }
  77. /**
  78. * Create a custom compression option.
  79. */
  80. this.compress = function (filename, src, opts, cb) {
  81. compressLog.log("compressing", filename);
  82. var cachePath;
  83. if (opts.compressorCachePath && filename) {
  84. try {
  85. var cacheFilename = (/^\.\//.test(filename) ? 'R-' + filename.substring(2) : 'A-' + filename)
  86. .replace(/\.\.\//g, '--U--')
  87. .replace(/\//g, '---');
  88. cachePath = path.join(opts.compressorCachePath, cacheFilename);
  89. if (crypto) {
  90. var hash = crypto.createHash('md5');
  91. hash.update(src);
  92. var checksum = hash.digest('hex');
  93. } else {
  94. var stat = fs.statSync(filename);
  95. var checksum = '' + stat.mtime;
  96. }
  97. if (fs.existsSync(cachePath)) {
  98. var cachedContents = fs.readFileSync(cachePath, 'utf8');
  99. var i = cachedContents.indexOf('\n');
  100. var cachedChecksum = cachedContents.substring(0, i);
  101. if (checksum == cachedChecksum) {
  102. onCompress(null, cachedContents.substring(i + 1));
  103. return;
  104. } else {
  105. compressLog.log('current:', checksum, 'cached:', cachedChecksum);
  106. }
  107. }
  108. } catch (e) {
  109. onCompress(e, src);
  110. }
  111. }
  112. exports.compress(filename, src, opts, onCompress);
  113. function onCompress(err, src) {
  114. if (err) {
  115. compressLog.error(err);
  116. } else {
  117. try {
  118. if (cachePath) {
  119. fs.writeFile(cachePath, checksum + '\n' + src);
  120. }
  121. } catch(e) {
  122. compressLog.error(e);
  123. }
  124. }
  125. exports.strip(src, opts, function (err, src) {
  126. cb(src);
  127. });
  128. }
  129. }
  130. });
  131. exports.compress = function (filename, src, opts, cb) {
  132. var closureOpts = [
  133. '--compilation_level', 'SIMPLE_OPTIMIZATIONS',
  134. ];
  135. if (opts.noIE) {
  136. closureOpts.push('--jscomp_off', 'internetExplorerChecks');
  137. }
  138. jvmtools.exec("closure", closureOpts, src, function (compiler) {
  139. var out = [];
  140. var err = [];
  141. compiler.on("out", function (data) { out.push(data); });
  142. compiler.on("err", function (data) {
  143. err.push(data);
  144. });
  145. // err.push(data); });
  146. compiler.on("end", function (code) {
  147. err = err.join('').replace(/^stdin:(\d+):/mg, 'Line $1:');
  148. if (err.length) {
  149. compressLog.log(clc.green.bright(filename + ':\n') + err);
  150. }
  151. if (code == 0) {
  152. var compressedSrc = out.join('');
  153. cb(null, compressedSrc);
  154. } else {
  155. compressLog.error("exited with code", code);
  156. cb({'code': code}, src);
  157. }
  158. });
  159. });
  160. }
  161. exports.strip = function (src, opts, cb) {
  162. var ast = parser.parse(src);
  163. var defines = {};
  164. for (var key in opts.defines) {
  165. var type = 'string';
  166. if (typeof opts.defines[key] == 'boolean') {
  167. type = 'name';
  168. } else if (typeof opts.defines[key] == 'number') {
  169. type = 'number';
  170. }
  171. defines[key] = [type, JSON.stringify(opts.defines[key])];
  172. }
  173. ast = uglify.ast_mangle(ast, {
  174. defines: defines
  175. });
  176. ast = uglify.ast_squeeze(ast);
  177. ast = uglify.ast_lift_variables(ast);
  178. var src = uglify.gen_code(ast);
  179. cb(null, src);
  180. }
  181. /**
  182. * js.io compiler
  183. */
  184. compile.JsioCompiler = Class(function () {
  185. var _queue = new BuildQueue();
  186. this.init = function () {
  187. this.opts = {};
  188. }
  189. this.compile = function (entry, next) {
  190. // jsio_compile doesn't yet support multiple simultaneous builds, so use a build queue
  191. var task = bind(this, function (next) {
  192. logger.log('Compiling Source code.')
  193. // for debugging purposes, build the equivalent command that can be executed
  194. // from the command-line (not used for anything other than logging to the screen)
  195. var cmd = ["jsio_compile", JSON.stringify(entry)];
  196. for (var key in this.opts) {
  197. cmd.push('--' + key);
  198. var value = JSON.stringify(this.opts[key]);
  199. if (typeof this.opts[key] != 'string') {
  200. value = JSON.stringify(value);
  201. }
  202. cmd.push(value);
  203. }
  204. logger.log(cmd.join(' '));
  205. // The BasilJsioInterface implements platform-specific functions that the js.io
  206. // compiler needs like basic control flow and compression. It's really more
  207. // like a controller that conforms to the js.io-compiler's (controller) interface.
  208. this.opts['interface'] = new BasilJsioInterface();
  209. this.opts['interface'].setNext(next);
  210. this.opts['preCompress'] = bind(this, 'preCompress');
  211. // start the compile by passing something equivalent to argv (first argument is
  212. // ignored, but traditionally should be the name of the executable?)
  213. jsio_compile.compiler.start(['jsio_compile', this.opts.cwd || '.', entry], this.opts);
  214. });
  215. _queue.add(task, next);
  216. }
  217. this.setCwd = function (cwd) {
  218. this.opts.cwd = cwd;
  219. }
  220. /**
  221. * Infer js.io options from a (runtime.mode) tuple.
  222. */
  223. this.inferOptsFromEntry = function (entry) {
  224. var match = entry.match(/^gc.(.*).launchClient$/);
  225. if (!match) {
  226. throw new Error("unknown platform: " + entry);
  227. }
  228. var platform = match[1];
  229. logger.log("Inferring opts from platform: " + platform);
  230. var env;
  231. switch (platform) {
  232. case "browser":
  233. case "overlay":
  234. env = "browser";
  235. break;
  236. case "native":
  237. env = "native";
  238. break;
  239. }
  240. return this.inferOptsFromEnv(env);
  241. }
  242. this.preCompress = function (srcTable) {}
  243. /**
  244. * Return default options.
  245. */
  246. this.inferOptsFromEnv = function (env) {
  247. var paths = ['.'].concat(exports.JSIO_SDK_PATHS);
  248. // if the addon manager is found, pull in the paths
  249. if (addonManager) {
  250. paths = paths.concat(addonManager.getPaths());
  251. }
  252. merge(this.opts, {
  253. jsioPath: 'sdk/jsio',
  254. noIE: true,
  255. environment: env,
  256. path: paths,
  257. includeJsio: true,
  258. appendImport: false,
  259. debug: argv.verbose ? 2 : 4
  260. });
  261. }
  262. /**
  263. * Return optins for enabling compression.
  264. */
  265. this.setCompressEnabled = function (isEnabled) {
  266. if (isEnabled) {
  267. this.opts.compressSources = true;
  268. this.opts.compressResult = true;
  269. } else {
  270. delete this.opts.compressSources;
  271. delete this.opts.compressResult;
  272. }
  273. }
  274. /**
  275. * use the class opts to compress source code directly
  276. */
  277. this.compress = function (tag, src, cb) {
  278. exports.compress(tag, src, this.opts, cb);
  279. }
  280. this.strip = function (src, cb) {
  281. exports.strip(src, this.opts, cb);
  282. }
  283. });