assets-hot-update.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. const fs = require('fs');
  2. const path = require('path');
  3. const crypto = require('crypto');
  4. const setting = require('./build-setting');
  5. const utils = require('./libs/utils');
  6. const assetsObfuscator = require('./assets-obfuscator');
  7. module.exports = (function (){
  8. var _instance;
  9. function AssetsHotUpdate (params) {
  10. return {
  11. _ready: false,
  12. _src: null,
  13. _dest: null,
  14. _manifest: {
  15. packageUrl: 'http://localhost/hot-update/',
  16. remoteManifestUrl: 'http://localhost/hot-update/project.manifest',
  17. remoteVersionUrl: 'http://localhost/hot-update/version.manifest',
  18. version: '300001',
  19. assets: {},
  20. searchPaths: []
  21. },
  22. init (actualPlatform) {
  23. this._ready = false;
  24. let assetsHotUpdate = `assetsHotUpdate`;
  25. let update = setting.getInstance().getItem(assetsHotUpdate);
  26. if (!update) { Editor.warn('缺少配置,跳过资源热更!'); return false; }
  27. if (!update.enable) { Editor.warn('未开启资源热更!'); return false; }
  28. let platform = update.platform || ''; // 平台配置
  29. let supportList = platform.split(',');
  30. if (!supportList.includes(actualPlatform)) {
  31. Editor.warn(actualPlatform, '平台不支持资源热更');
  32. return false;
  33. }
  34. let src = update.src;
  35. let dest = update.dest;
  36. let appVersion = update.appVersion; // 指定的代码版本号
  37. let hxCode = update.hxCode; // 指定的混淆码
  38. let resVersion = update.resVersion; // 指定的代码资源号
  39. let remoteUrl = update.remoteUrl;
  40. if (!src || !dest || !remoteUrl) {
  41. Editor.error('资源热更配置不满足');
  42. return false;
  43. }
  44. let cfg = setting.getInstance().getVersionConfig() || {};
  45. // 如果没有指定代码版本号就使用 VersionConfig.js 的代码版本号
  46. if (!resVersion || resVersion.length === 0) {
  47. appVersion = String(cfg.APP_VERSION) || '';
  48. }
  49. // 如果没有指定混淆码就使用 VersionConfig.js 的混淆码
  50. if (!hxCode || hxCode.length === 0) {
  51. hxCode = String(cfg.HX_CODE) || '';
  52. }
  53. // 如果没有指定代码资源号就使用 VersionConfig.js 的代码资源号
  54. if (!resVersion || resVersion.length === 0) {
  55. resVersion = String(cfg.RES_VERSION) || '';
  56. }
  57. this.appVersion = appVersion;
  58. this.hxCode = hxCode;
  59. this.resVersion = resVersion;
  60. Editor.log('代码版本号: ' + appVersion + '; 混淆Code: ' + hxCode + '; 代码资源号: ' + resVersion);
  61. this._src = path.join(Editor.Project.path, src);
  62. this._dest = path.join(Editor.Project.path, dest);
  63. if (fs.existsSync(this._dest)) {
  64. utils.rmdirSync(this._dest);
  65. }
  66. fs.mkdirSync(this._dest);
  67. this._manifest.version = resVersion;
  68. this._manifest.packageUrl = remoteUrl;
  69. this._manifest.remoteManifestUrl = remoteUrl + '/project.manifest';
  70. this._manifest.remoteVersionUrl = remoteUrl + '/version.manifest';
  71. this._manifest.assets = {};
  72. this._manifest.searchPaths = [];
  73. // 获取UUID
  74. let assetsdb = this.getAssetsdbPath(this._src);
  75. let projectManifestPath = this._src + '/project.manifest';
  76. utils.accessPath(projectManifestPath, (err)=> {
  77. if (err && err.code === 'ENOENT') {
  78. let assetsdbProjectManifestPath = assetsdb + '/project.manifest';
  79. utils.createProjectManifestAssets(assetsdbProjectManifestPath, remoteUrl, (err, results) => {
  80. if (err) {
  81. Editor.error(err);
  82. return false;
  83. }
  84. let result = results[0];
  85. this._projectManifestUUID = result.uuid;
  86. });
  87. } else {
  88. this._projectManifestUUID = Editor.assetdb._path2uuid[projectManifestPath];
  89. }
  90. });
  91. let versionManifestPath = this._src + '/version.manifest';
  92. utils.accessPath(versionManifestPath, (err)=> {
  93. let assetsdbVersionManifestPath = assetsdb + '/version.manifest';
  94. if (err && err.code === 'ENOENT') {
  95. utils.createVersionManifestAssets(assetsdbVersionManifestPath, remoteUrl, (err, results) => {
  96. if (err) {
  97. Editor.error(err);
  98. return false;
  99. }
  100. let result = results[0];
  101. this._versionManifestUUID = result.uuid;
  102. });
  103. } else {
  104. this._versionManifestUUID = Editor.assetdb._path2uuid[versionManifestPath];
  105. }
  106. });
  107. this._ignoreFileUuids = [];
  108. let ignores = update.ignores;
  109. if (ignores) {
  110. for (const aPath of ignores) {
  111. let fullpath = path.join(Editor.Project.path, aPath);
  112. let stat = fs.statSync(fullpath);
  113. if (stat.isDirectory()) {
  114. utils.findFileSync(fullpath, (filePath)=> {
  115. this.addIgnoreFile(filePath);
  116. });
  117. } else if (stat.isFile()) {
  118. this.addIgnoreFile(fullpath);
  119. }
  120. }
  121. }
  122. this._ready = true;
  123. return true;
  124. },
  125. isReady () {
  126. return this._ready;
  127. },
  128. addIgnoreFile (filePath) {
  129. let index = filePath.lastIndexOf('.');
  130. let ext = filePath.substr(index + 1);
  131. if (ext !== 'meta') {
  132. let uuid = Editor.assetdb._path2uuid[filePath];
  133. this._ignoreFileUuids.push(uuid);
  134. }
  135. },
  136. getAssetsdbPath (root) {
  137. let assetsdb = 'db://';
  138. let assetsIdx = root.indexOf('assets');
  139. if (assetsIdx !== -1) {
  140. assetsdb += root.substr(assetsIdx, root.length);
  141. } else {
  142. assetsdb += '/assets';
  143. }
  144. return assetsdb;
  145. },
  146. addHotUpdateSearchPaths (buildDest) {
  147. var root = path.normalize(buildDest);
  148. var url = path.join(root, 'main.js');
  149. let data = fs.readFileSync(url, 'utf8');
  150. if (data) {
  151. try {
  152. var newStr =
  153. ' // 编译助手追加代码 开始\n' +
  154. ' var hotUpdateSearchPaths = jsb.fileUtils.getWritablePath() + \'remote-assets\';\n' +
  155. ' var curVersionName = \'' + this.appVersion + '\';\n' +
  156. ' var curHxCode = \'' + this.hxCode + '\';\n' +
  157. ' var oldVersionName = cc.sys.localStorage.getItem(\'HOT_UPDATE_VERSION_NAME\');\n' +
  158. ' var oldHxCode = cc.sys.localStorage.getItem(\'HOT_UPDATE_HX_CODE\');\n' +
  159. ' if (curVersionName != oldVersionName || curHxCode != oldHxCode) {\n' +
  160. ' jsb.fileUtils.removeDirectory(hotUpdateSearchPaths);\n' +
  161. ' }\n' +
  162. ' var searchPaths = jsb.fileUtils.getSearchPaths();\n' +
  163. ' searchPaths.unshift(hotUpdateSearchPaths);\n' +
  164. ' jsb.fileUtils.setSearchPaths(searchPaths);\n' +
  165. ' // 编译助手追加代码 结束\n\n' +
  166. ' window.boot();';
  167. newStr = data.replace(' window.boot();', newStr);
  168. fs.writeFileSync(url, newStr);
  169. Editor.log('热更搜索路径写入' + url + '成功');
  170. } catch (error) {
  171. Editor.error('热更搜索路径成功写入' + url + '失败');
  172. }
  173. } else {
  174. Editor.error('读取' + url + '失败');
  175. }
  176. },
  177. getAssetsBuiledUrl (root, uuid) {
  178. if (!root || !uuid) {
  179. return;
  180. }
  181. let url;
  182. let dirname = uuid.substr(0, 2);
  183. let dirPath = path.join(root, 'res/raw-assets', dirname);
  184. let files = fs.readdirSync(dirPath);
  185. for (const fileName of files) {
  186. if (fileName.includes(uuid)) {
  187. url = path.join(dirPath, fileName);
  188. break;
  189. }
  190. }
  191. return url;
  192. },
  193. readBuildDest (root, dir, obj) {
  194. let stat = fs.statSync(dir);
  195. if (!stat.isDirectory()) {
  196. return;
  197. }
  198. let subpaths = fs.readdirSync(dir), subpath, size, md5, compressed, relative;
  199. for (let i = 0; i < subpaths.length; ++i) {
  200. if (subpaths[i][0] === '.') {
  201. continue;
  202. }
  203. subpath = path.join(dir, subpaths[i]);
  204. stat = fs.statSync(subpath);
  205. if (stat.isDirectory()) {
  206. this.readBuildDest(root, subpath, obj);
  207. } else if (stat.isFile()) {
  208. let hasIgnore = false;
  209. for (const uuid of this._ignoreFileUuids) {
  210. let include = subpath.includes(uuid);
  211. if (include) {
  212. hasIgnore = true;
  213. break;
  214. }
  215. }
  216. if (hasIgnore) {
  217. continue;
  218. }
  219. // Size in Bytes
  220. size = stat['size'];
  221. md5 = crypto.createHash('md5').update(fs.readFileSync(subpath)).digest('hex');
  222. compressed = path.extname(subpath).toLowerCase() === '.zip';
  223. relative = path.relative(root, subpath);
  224. relative = relative.replace(/\\/g, '/');
  225. relative = encodeURI(relative);
  226. obj[relative] = {
  227. 'size': size,
  228. 'md5': md5
  229. };
  230. if (compressed) {
  231. obj[relative].compressed = true;
  232. }
  233. }
  234. }
  235. },
  236. generateManifest (buildDest, callback) {
  237. let projectManifestUUID = this._projectManifestUUID;
  238. let versionManifestUUID = this._versionManifestUUID;
  239. if (assetsObfuscator.getInstance().isReady()) {
  240. let mima = assetsObfuscator.getInstance().getMima();
  241. if (this._projectManifestUUID) {
  242. projectManifestUUID = assetsObfuscator.getInstance().getNewName(mima, this._projectManifestUUID);
  243. }
  244. if (this._versionManifestUUID) {
  245. Editor.log('this._versionManifestUUID', this._versionManifestUUID);
  246. versionManifestUUID = assetsObfuscator.getInstance().getNewName(mima, this._versionManifestUUID);
  247. Editor.log('versionManifestUUID', versionManifestUUID);
  248. }
  249. }
  250. let projectManifestURL = this.getAssetsBuiledUrl(buildDest, projectManifestUUID);
  251. if (!projectManifestURL) {
  252. Editor.error('生成project.manifest失败');
  253. return;
  254. }
  255. let versionManifestURL = this.getAssetsBuiledUrl(buildDest, versionManifestUUID);
  256. if (!versionManifestURL) {
  257. Editor.error('生成version.manifest失败');
  258. return;
  259. }
  260. let destProjectManifestURL = path.join(this._dest, 'project.manifest');
  261. let destVersionManifestURL = path.join(this._dest, 'version.manifest');
  262. this.readBuildDest(buildDest, path.join(buildDest, 'src'), this._manifest.assets);
  263. this.readBuildDest(buildDest, path.join(buildDest, 'res'), this._manifest.assets);
  264. try {
  265. fs.writeFileSync(projectManifestURL, JSON.stringify(this._manifest));
  266. fs.writeFileSync(destProjectManifestURL, JSON.stringify(this._manifest));
  267. Editor.log('写入project.manifest成功', projectManifestURL);
  268. let md5 = crypto.createHash('md5').update(fs.readFileSync(destProjectManifestURL)).digest('hex');
  269. Editor.warn('project.manifest md5 = ', md5);
  270. delete this._manifest.assets;
  271. delete this._manifest.searchPaths;
  272. fs.writeFileSync(versionManifestURL, JSON.stringify(this._manifest));
  273. fs.writeFileSync(destVersionManifestURL, JSON.stringify(this._manifest));
  274. Editor.log('写入version.manifest成功', versionManifestURL);
  275. callback();
  276. } catch (error) {
  277. callback(error);
  278. }
  279. },
  280. copyAssets (buildDest) {
  281. // fs.copyFileSync(path.join(buildDest, 'main.js'), path.join(this._dest, 'main.js'));
  282. // fs.copyFileSync(path.join(buildDest, 'project.json'), path.join(this._dest, 'project.json'));
  283. // utils.copyFolder(path.join(buildDest, 'jsb-adapter'), path.join(this._dest, 'jsb-adapter'));
  284. utils.copyFolder(path.join(buildDest, 'src'), path.join(this._dest, 'src'));
  285. utils.copyFolder(path.join(buildDest, 'res'), path.join(this._dest, 'res'));
  286. // let debuggingPath = path.join(buildDest, 'js backups (useful for debugging)');
  287. // if (fs.existsSync(debuggingPath)) {
  288. // utils.copyFolder(debuggingPath, path.join(this._dest, 'js backups (useful for debugging)'));
  289. // }
  290. }
  291. };
  292. }
  293. return {
  294. getInstance (params) {
  295. if (!_instance){
  296. _instance = AssetsHotUpdate(params);
  297. }
  298. return _instance;
  299. }
  300. };
  301. })();