slim-wechat-build.mjs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import fs from 'node:fs';
  2. import path from 'node:path';
  3. import { fileURLToPath } from 'node:url';
  4. const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
  5. const buildRoot = path.join(projectRoot, 'build', 'wechatgame');
  6. const ccDepsWithoutPhysics = [
  7. 'plugin:cocos/2d.js',
  8. 'plugin:cocos/sorting.js',
  9. 'plugin:cocos/3d.js',
  10. './affine-transform.js',
  11. './animation.js',
  12. 'plugin:cocos/audio.js',
  13. 'plugin:cocos/base.js',
  14. './custom-pipeline.js',
  15. 'plugin:cocos/dragon-bones.js',
  16. 'plugin:cocos/gfx-webgl.js',
  17. 'plugin:cocos/graphics.js',
  18. 'plugin:cocos/intersection-2d.js',
  19. 'plugin:cocos/light-probe.js',
  20. 'plugin:cocos/mask.js',
  21. 'plugin:cocos/particle.js',
  22. 'plugin:cocos/particle-2d.js',
  23. 'plugin:cocos/primitive.js',
  24. 'plugin:cocos/profiler.js',
  25. 'plugin:cocos/rich-text.js',
  26. './skeletal-animation.js',
  27. 'plugin:cocos/spine.js',
  28. 'plugin:cocos/terrain.js',
  29. 'plugin:cocos/tiled-map.js',
  30. 'plugin:cocos/tween.js',
  31. 'plugin:cocos/ui.js',
  32. 'plugin:cocos/ui-skew.js',
  33. 'plugin:cocos/video.js',
  34. 'plugin:cocos/webview.js',
  35. ];
  36. function readJson(file) {
  37. return JSON.parse(fs.readFileSync(file, 'utf8'));
  38. }
  39. function writeJson(file, data) {
  40. fs.writeFileSync(file, JSON.stringify(data));
  41. }
  42. function safeBuildPath(relPath) {
  43. const target = path.resolve(buildRoot, relPath);
  44. if (target !== buildRoot && !target.startsWith(buildRoot + path.sep)) {
  45. throw new Error(`Refusing to touch path outside build root: ${target}`);
  46. }
  47. return target;
  48. }
  49. function removeBuildPath(relPath) {
  50. fs.rmSync(safeBuildPath(relPath), { recursive: true, force: true });
  51. }
  52. function rewriteCcWithoutPhysics() {
  53. const ccPath = safeBuildPath(path.join('cocos-js', 'cc.js'));
  54. if (!fs.existsSync(ccPath)) return;
  55. const body = `System.register(${JSON.stringify(ccDepsWithoutPhysics)}, function (_export, _context) {
  56. "use strict";
  57. function exportAll(module) {
  58. var exportObj = {};
  59. for (var key in module) {
  60. if (key !== "default" && key !== "__esModule") exportObj[key] = module[key];
  61. }
  62. _export(exportObj);
  63. }
  64. return {
  65. setters: [${ccDepsWithoutPhysics.map(() => 'exportAll').join(', ')}],
  66. execute: function () {}
  67. };
  68. });
  69. `;
  70. fs.writeFileSync(ccPath, body);
  71. }
  72. function removeFirstScreen() {
  73. const gamePath = safeBuildPath('game.js');
  74. if (!fs.existsSync(gamePath)) return;
  75. let source = fs.readFileSync(gamePath, 'utf8');
  76. source = source.replace("const firstScreen = require('./first-screen');\r\n\r\n", '');
  77. source = source.replace("const firstScreen = require('./first-screen');\n\n", '');
  78. source = source.replace(/firstScreen\.start\('default', 'default', 'false'\)\.then\(\(\) => \{\s*return System\.import\('\.\/application\.js'\);\s*\}\)\.then\(\(module\) => \{\s*return firstScreen\.setProgress\(0\.2\)\.then\(\(\) => Promise\.resolve\(module\)\);\s*\}\)/s, "System.import('./application.js')");
  79. source = source.replace(/\.then\(\(application\) => \{\s*return firstScreen\.setProgress\(0\.4\)\.then\(\(\) => Promise\.resolve\(application\)\);\s*\}\)/s, '');
  80. source = source.replace(/return firstScreen\.setProgress\(0\.6\)\.then\(\(\) => Promise\.resolve\(module\)\);/g, 'return module;');
  81. source = source.replace(/return firstScreen\.end\(\)\.then\(\(\) => application\.start\(\)\);/g, 'return application.start();');
  82. fs.writeFileSync(gamePath, source);
  83. removeBuildPath('first-screen.js');
  84. removeBuildPath('logo.png');
  85. removeBuildPath('slogan.png');
  86. }
  87. function patchFullscreenCanvas() {
  88. const gamePath = safeBuildPath('game.js');
  89. if (!fs.existsSync(gamePath)) return;
  90. let source = fs.readFileSync(gamePath, 'utf8');
  91. source = source.replace(/\/\/ Adapt for IOS, swap if opposite[\s\S]*?const importMap = require\("src\/import-map\.js"\)\.default;/, 'const importMap = require("src/import-map.js").default;');
  92. source = source.replace(/\nfunction __steelGetLandscapeFrame \(\) \{[\s\S]*?\nwx\.onWindowResize && wx\.onWindowResize\(__steelApplyFullscreenCanvas\);\n\n?/, '\n');
  93. const shim = `
  94. function __steelGetLandscapeFrame () {
  95. const info = (wx.getWindowInfo && wx.getWindowInfo()) || wx.getSystemInfoSync();
  96. const add = function (list, width, height) {
  97. if (!width || !height || width <= 0 || height <= 0) return;
  98. list.push({ width: Math.max(width, height), height: Math.min(width, height) });
  99. };
  100. const candidates = [];
  101. add(candidates, info.windowWidth, info.windowHeight);
  102. add(candidates, info.screenWidth, info.screenHeight);
  103. add(candidates, canvas.width, canvas.height);
  104. add(candidates, canvas.clientWidth, canvas.clientHeight);
  105. const frame = candidates.reduce(function (best, item) {
  106. return item.width / Math.max(1, item.height) > best.width / Math.max(1, best.height) ? item : best;
  107. }, { width: 1624, height: 750 });
  108. const dpr = info.pixelRatio || info.devicePixelRatio || window.devicePixelRatio || 1;
  109. return { width: frame.width, height: frame.height, dpr };
  110. }
  111. function __steelSetWindowValue (key, value) {
  112. try {
  113. const descriptor = Object.getOwnPropertyDescriptor(window, key);
  114. if (!descriptor || descriptor.configurable) {
  115. Object.defineProperty(window, key, { value, configurable: true, writable: true });
  116. } else if (descriptor.writable) {
  117. window[key] = value;
  118. }
  119. } catch (error) {}
  120. }
  121. function __steelApplyFullscreenCanvas () {
  122. if (!canvas) return;
  123. const frame = __steelGetLandscapeFrame();
  124. const pixelWidth = Math.max(1, Math.round(frame.width * frame.dpr));
  125. const pixelHeight = Math.max(1, Math.round(frame.height * frame.dpr));
  126. __steelSetWindowValue('innerWidth', frame.width);
  127. __steelSetWindowValue('innerHeight', frame.height);
  128. __steelSetWindowValue('screen', {
  129. width: frame.width,
  130. height: frame.height,
  131. availWidth: frame.width,
  132. availHeight: frame.height,
  133. availLeft: 0,
  134. availTop: 0
  135. });
  136. canvas.width = pixelWidth;
  137. canvas.height = pixelHeight;
  138. canvas.style = canvas.style || {};
  139. canvas.style.top = '0px';
  140. canvas.style.left = '0px';
  141. canvas.style.width = frame.width + 'px';
  142. canvas.style.height = frame.height + 'px';
  143. canvas.getBoundingClientRect = function () {
  144. return { top: 0, left: 0, width: frame.width, height: frame.height };
  145. };
  146. console.log('[SteelAssaultGame] wx fullscreen canvas ' + frame.width + 'x' + frame.height + ' dpr ' + frame.dpr + ' backing ' + pixelWidth + 'x' + pixelHeight);
  147. }
  148. __steelApplyFullscreenCanvas();
  149. wx.onWindowResize && wx.onWindowResize(__steelApplyFullscreenCanvas);
  150. `;
  151. source = source.replace(/require\('\.\/web-adapter'\);\r?\n/, `require('./web-adapter');\n${shim}\n`);
  152. fs.writeFileSync(gamePath, source);
  153. }
  154. function disablePhysicsSettingOnly() {
  155. const settingsPath = safeBuildPath(path.join('src', 'settings.json'));
  156. if (!fs.existsSync(settingsPath)) return;
  157. const settings = readJson(settingsPath);
  158. settings.physics = { physicsEngine: '' };
  159. if (settings.splashScreen) {
  160. settings.splashScreen.totalTime = 0;
  161. if (settings.splashScreen.logo) settings.splashScreen.logo.image = '';
  162. }
  163. writeJson(settingsPath, settings);
  164. }
  165. function removeDefaultPhysicsMaterialAsset() {
  166. const settingsPath = safeBuildPath(path.join('src', 'settings.json'));
  167. const internalConfigPath = safeBuildPath(path.join('assets', 'internal', 'config.json'));
  168. const internalPackPath = safeBuildPath(path.join('assets', 'internal', 'import', '07', '07d3aae9f.json'));
  169. if (!fs.existsSync(settingsPath) || !fs.existsSync(internalConfigPath) || !fs.existsSync(internalPackPath)) return;
  170. const settings = readJson(settingsPath);
  171. if (settings.engine?.builtinAssets) {
  172. settings.engine.builtinAssets = settings.engine.builtinAssets.filter((uuid) => uuid !== 'd1346436-ac96-4271-b863-1f4fdead95b0');
  173. writeJson(settingsPath, settings);
  174. }
  175. const internalConfig = readJson(internalConfigPath);
  176. const internalPack = readJson(internalPackPath);
  177. const packIds = internalConfig.packs?.['07d3aae9f'] ?? [];
  178. const physicsIndex = packIds.indexOf(22);
  179. if (physicsIndex >= 0 && Array.isArray(internalPack[5])) {
  180. internalPack[5].splice(physicsIndex, 1);
  181. }
  182. const physicsTypeIndex = Array.isArray(internalPack[3])
  183. ? internalPack[3].findIndex((entry) => entry?.[0] === 'cc.PhysicsMaterial')
  184. : -1;
  185. if (physicsTypeIndex >= 0) {
  186. internalPack[3].splice(physicsTypeIndex, 1);
  187. if (Array.isArray(internalPack[4])) {
  188. internalPack[4] = internalPack[4].filter((entry) => entry?.[0] !== physicsTypeIndex);
  189. }
  190. }
  191. if (internalConfig.packs?.['07d3aae9f']) {
  192. internalConfig.packs['07d3aae9f'] = packIds.filter((id) => id !== 22);
  193. }
  194. delete internalConfig.paths?.['22'];
  195. if (internalConfig.types) {
  196. internalConfig.types = internalConfig.types.filter((type) => type !== 'cc.PhysicsMaterial');
  197. }
  198. writeJson(internalConfigPath, internalConfig);
  199. writeJson(internalPackPath, internalPack);
  200. }
  201. function removePhysicsEngineFiles() {
  202. [
  203. 'cocos-js/physics-framework.js',
  204. 'cocos-js/physics-ammo.js',
  205. 'cocos-js/physics-2d-framework.js',
  206. 'cocos-js/physics-2d-framework-CVNkMl3f.js',
  207. 'cocos-js/physics-2d-box2d.js',
  208. 'cocos-js/index-D5MM0hSc.js',
  209. 'cocos-js/collision-matrix-C-tmFF1b.js',
  210. 'cocos-js/array-collision-matrix-DU5_JQW3.js',
  211. 'cocos-js/tuple-dictionary-By7Ih6PO.js',
  212. 'cocos-js/bullet.release.asm-CtSjh1p_.js',
  213. 'cocos-js/bullet.release.wasm-CV1y-N0i.js',
  214. 'cocos-js/bullet.release.wasm-mT4aJDLq.js',
  215. 'cocos-js/assets/bullet.release.wasm-BIzkn7bF.wasm',
  216. ].forEach(removeBuildPath);
  217. }
  218. function removeUnusedDefaultScene() {
  219. const mainConfigPath = safeBuildPath(path.join('assets', 'main', 'config.json'));
  220. if (fs.existsSync(mainConfigPath)) {
  221. const mainConfig = readJson(mainConfigPath);
  222. for (const id of ['3', '4', '19']) delete mainConfig.paths?.[id];
  223. if (mainConfig.scenes) delete mainConfig.scenes['db://assets/scenes/scene.scene'];
  224. delete mainConfig.packs?.['0699aaa4c'];
  225. delete mainConfig.packs?.['06add430f'];
  226. mainConfig.redirect = [];
  227. mainConfig.types = ['cc.SceneAsset'];
  228. writeJson(mainConfigPath, mainConfig);
  229. }
  230. [
  231. 'assets/main/import/e6/e612b3cf-fca7-4207-8dcc-5a0b5d305242.json',
  232. 'assets/main/import/06/0699aaa4c.json',
  233. 'assets/main/import/06/06add430f.json',
  234. 'assets/main/import/fd/fd8ec536-a354-4a17-9c74-4f3883c378c8.json',
  235. 'assets/main/native/6f',
  236. 'assets/main/native/d0',
  237. ].forEach(removeBuildPath);
  238. }
  239. function totalBuildBytes() {
  240. let total = 0;
  241. const walk = (dir) => {
  242. for (const name of fs.readdirSync(dir)) {
  243. const file = path.join(dir, name);
  244. const stat = fs.statSync(file);
  245. if (stat.isDirectory()) walk(file);
  246. else total += stat.size;
  247. }
  248. };
  249. walk(buildRoot);
  250. return total;
  251. }
  252. if (!fs.existsSync(buildRoot)) {
  253. throw new Error(`Wechat build folder not found: ${buildRoot}`);
  254. }
  255. removeFirstScreen();
  256. patchFullscreenCanvas();
  257. rewriteCcWithoutPhysics();
  258. disablePhysicsSettingOnly();
  259. removeDefaultPhysicsMaterialAsset();
  260. removePhysicsEngineFiles();
  261. removeUnusedDefaultScene();
  262. const total = totalBuildBytes();
  263. console.log(`wechat build size: ${total} bytes (${(total / 1024 / 1024).toFixed(2)} MB)`);