// Run from the project root: node assets/test/tests/preview.cjs [editorPort=7457] [port=7460] // Uses the installed Cocos 2.4.15 compiler and the running editor's engine/assets. const fs = require('fs'); const http = require('http'); const path = require('path'); const Module = require('module'); const root = path.resolve(__dirname, '../../..'); function loadTypeScript() { const editor = process.env.COCOS_EDITOR_HOME || path.join(process.env.ProgramData || 'C:/ProgramData', 'cocos/editors/Creator/2.4.15'); const archive = path.join(editor, 'resources/app.asar'); const fd = fs.openSync(archive, 'r'); try { const header = Buffer.alloc(16); fs.readSync(fd, header, 0, 16, 0); const json = Buffer.alloc(header.readUInt32LE(12)); fs.readSync(fd, json, 0, json.length, 16); const files = JSON.parse(json).files.node_modules.files.typescript.files.lib.files; const entry = files['typescript.js']; const code = Buffer.alloc(entry.size); fs.readSync(fd, code, 0, code.length, 8 + header.readUInt32LE(4) + Number(entry.offset)); const compiler = new Module(path.join(__dirname, 'cocos-typescript.js'), module); compiler.filename = path.join(__dirname, 'cocos-typescript.js'); compiler.paths = module.paths; compiler._compile(code.toString(), compiler.filename); return compiler.exports; } finally { fs.closeSync(fd); } } function compress(uuid) { if (uuid.length !== 36) return uuid; const hex = uuid.replace(/-/g, ''); const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; let result = hex.slice(0, 5); for (let i = 5; i < 32; i += 3) { const value = parseInt(hex.slice(i, i + 3), 16); result += chars[value >> 6] + chars[value & 63]; } return result; } function projectScripts(ts) { let result = 'window.__quick_compile_project__={load:function(done){const modules={};\n'; for (const relative of ['assets/Script/module/Tool/LevelGameplay.ts', 'assets/GameplayIntro/script/GuideCarousel.ts', 'assets/GameplayIntro/script/GameplayIntro.ts', 'assets/test/script/TestLayoutPanel.ts']) { const file = path.join(root, relative); const name = path.basename(file, '.ts'); const uuid = compress(JSON.parse(fs.readFileSync(file + '.meta')).uuid); const code = ts.transpileModule(fs.readFileSync(file, 'utf8'), { compilerOptions: { experimentalDecorators: true, module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2017 } }).outputText; result += `(function(){const module={exports:{}};const exports=module.exports;const require=p=>modules[p.split('/').pop()];cc._RF.push(module,${JSON.stringify(uuid)},${JSON.stringify(name)});\n${code}\ncc._RF.pop();modules[${JSON.stringify(name)}]=module.exports;})();\n`; } return result + 'done();}};'; } function start() { const ts = loadTypeScript(); const projectSettings = JSON.parse(fs.readFileSync(path.join(root, 'settings/project.json'), 'utf8')); const editorPort = Number(process.argv[2] || projectSettings['preview-port'] || 7456); const port = Number(process.argv[3] || 7460); http.createServer(async (request, response) => { try { const pathname = request.url.split('?')[0]; response.setHeader('Cache-Control', 'no-store'); if (pathname === '/preview-scene.json') { const scene = JSON.parse(fs.readFileSync(path.join(root, 'assets/test/scene/TestLayout.fire'))); scene[6].__type__ = compress(scene[6].__type__); response.setHeader('Content-Type', 'application/json'); return response.end(JSON.stringify(scene)); } if (pathname === '/preview-scripts/__quick_compile__.js') { response.setHeader('Content-Type', 'application/javascript'); return response.end(projectScripts(ts)); } // This server only reads the editor's preview files; editor mutations stay disabled. if (request.method !== 'GET' || pathname === '/update-db') { response.statusCode = 405; return response.end(); } const upstream = await fetch('http://127.0.0.1:' + editorPort + request.url); let body = Buffer.from(await upstream.arrayBuffer()); response.statusCode = upstream.status; response.setHeader('Content-Type', upstream.headers.get('content-type') || 'application/octet-stream'); if (pathname === '/settings.js') body = Buffer.from(body.toString().replace(/jsList:\s*\[[\s\S]*?\]/, 'jsList: []')); if (pathname === '/') body = Buffer.from(body.toString() .replace('window.__socket_io__ = window.io;', 'window.__socket_io__ = function(){return {on:function(){}}};') .replace('', '')); response.end(body); } catch (error) { response.statusCode = 500; response.end('Test preview failed. Keep this project open in Cocos Creator and check the editor port.\n' + error.message); } }).listen(port, '127.0.0.1', () => console.log('TestLayout preview: http://127.0.0.1:' + port)); } module.exports = { loadTypeScript }; if (require.main === module) start();