UNPKG

@honor-minigame/cli

Version:

honor minigame pack cli

363 lines (320 loc) 15 kB
import chalk from 'chalk' import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' import { fsExt, replaceFiles } from '../utils/fsUtils.js' import { error, info } from '../utils/logger.js' import * as readline from 'readline' import * as prettyjson from 'prettyjson' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) export async function _transfer(targetPath) { const cwd = process.cwd() info(chalk.blueBright(JSON.stringify(targetPath))) const wxGamePath = targetPath || process.env.PATH_PROJECT || cwd // 校验项目是否为微信工程 await check(wxGamePath) const qgGameName = path.basename(wxGamePath) + '-qg-transfer' const qgGamePath = path.join(path.dirname(wxGamePath), qgGameName) info(chalk.blueBright(`###工程根目录是 ${wxGamePath}, 新工程将导出在 ${qgGamePath}`)) // 如果有qg-transfer文件夹,则清理一下qg-transfer,否则创建 info(`正在清理新工程目录:${qgGamePath}`) fsExt.clearDir(qgGamePath, { recursive: true, exclude: /node_modules|src[\\/]+manifest\.json|sign/, build: true }) // 将qg-transfer文件夹内的manifest以及sign拷贝到转换工程中 const wxQgTransfer = path.join(wxGamePath, 'qg-transfer') const manifestTransfer = path.join(wxQgTransfer, 'manifest.json') if (fs.existsSync(manifestTransfer)) { info(`正在拷贝微小工程qg-transfer内的 manifest.json 配置到转换目录下`) fsExt.overwrite(manifestTransfer, path.join(qgGamePath, 'src/manifest.json')) } const signTransfer = path.join(wxQgTransfer, 'sign') if (fs.existsSync(signTransfer)) { info(`正在拷贝微小工程qg-transfer内的 sign 文件夹配置到转换目录下`) fsExt.overwrite(signTransfer, path.join(qgGamePath, 'sign')) } // 将旧的manifest文件读取出来 let qgGameManifestJson if (fs.existsSync(path.join(qgGamePath, 'src/manifest.json'))) { const qgGameManifestJsonPath = path.join(qgGamePath, 'src/manifest.json') qgGameManifestJson = fsExt.parse(qgGameManifestJsonPath) qgGameManifestJson.subpackages = [] } // 将overwrite文件夹里面的文件覆盖${qgGamePath}里面同名的文件夹和文件 info('初始化工程...') const overWritePath = path.join(__dirname, 'wx-templates') fsExt.overwrite(overWritePath, qgGamePath) // 获取微信小游戏的game.json的内容,并转化为JSON对象 @@MAYBE 需要判断当前目录下是否存在game.json const wxGameJsonPath = path.join(wxGamePath, 'game.json') info(`正在获取微信小游戏的${wxGameJsonPath}中的信息`) const wxGameJson = fsExt.parse(wxGameJsonPath) info('正在获取package.json的信息') // 读取项目配置package.json const wxPackageJsonPath = path.join(wxGamePath, 'package.json') let wxPackageJson = '' if (fs.existsSync(wxPackageJsonPath)) { wxPackageJson = fsExt.parse(wxPackageJsonPath) } let wxProjectJson = '' // 读取项目配置project.config.json if (fs.existsSync(path.join(wxGamePath, 'project.config.json'))) { wxProjectJson = fsExt.parse(path.join(wxGamePath, 'project.config.json')) } // 获取当前的manifest.json中的内容,并转化为JSON对象 info('正在获取manifest.json的信息') const qgGameManifestJsonPath = path.join(qgGamePath, 'src/manifest.json') if (!qgGameManifestJson) { qgGameManifestJson = fsExt.parse(qgGameManifestJsonPath) } // 同步屏幕方向与分包信息、引擎插件名称 let mainSubName = '' let unityPluginVersion = '' const pluginProxy = {} if (wxGameJson) { if (wxGameJson.deviceOrientation) { qgGameManifestJson.deviceOrientation = wxGameJson.deviceOrientation info(`Manifest配置的deviceOrientation重写为${wxGameJson.deviceOrientation}`) } // 旧版本微信支持subPackages驼峰写法 if (!wxGameJson.subpackages) { for (var prop in wxGameJson) { if (prop && prop.toLowerCase() === 'subpackages') { wxGameJson.subpackages = wxGameJson[prop] break } } } /** * 分包小游戏适配,将分包copy到manifest,修改main分包为main_sub,分包后缀与前缀修改 */ if (wxGameJson.subpackages) { qgGameManifestJson.subpackages = wxGameJson.subpackages // 判断分包内有没有包含main的分包,有的话重写为main_sub for (let i = 0; i < qgGameManifestJson.subpackages.length; i++) { if (qgGameManifestJson.subpackages[i].name === 'main') { let suffix = '' while (qgGameManifestJson.subpackages.find((item) => item.name === `main_sub${suffix}`) !== undefined) { suffix = suffix === '' ? '1' : suffix.replace(/\d+/, (n) => Number(n) + 1) } qgGameManifestJson.subpackages[i].name = `main_sub${suffix}` mainSubName = `main_sub${suffix}` info(`微信分包中包含main分包,将其转换为 ${mainSubName},运行时会注册到global._main_sub_name_中使用`) } let root = qgGameManifestJson.subpackages[i].root if (!root.endsWith('.js') && !root.endsWith('/')) { root = root + '/' } if (root.startsWith('/')) { root = root.substring(1) } qgGameManifestJson.subpackages[i].root = root } info(`Manifest配置的subpackages分包重写为:${prettyjson.render(qgGameManifestJson.subpackages)}\n`) } /** * 引擎插件修改 */ if (wxGameJson.plugins) { for (const key in wxGameJson.plugins) { if (Object.hasOwnProperty.call(wxGameJson.plugins, key)) { const pluginName = key const value = wxGameJson.plugins[key] if (value.path) { pluginProxy[pluginName] = value.path } if (pluginName === 'UnityPlugin' && value.version) { unityPluginVersion = value.version console.log(unityPluginVersion) qgGameManifestJson.isUnity = true } } } } } // 同步名称和版本号 if (qgGameManifestJson.name === 'wxdemo' || qgGameManifestJson.name === wxProjectJson.projectname || qgGameManifestJson.name === wxPackageJson.projectname) { if (wxPackageJson) { qgGameManifestJson.name = wxPackageJson.name ? wxPackageJson.name : 'wxdemo' info(`Manifest配置的name为${qgGameManifestJson.name}`) } else if (wxProjectJson) { qgGameManifestJson.name = wxProjectJson.projectname ? wxProjectJson.projectname : 'wxdemo' info(`Manifest配置的name为${qgGameManifestJson.name}`) } } if (qgGameManifestJson.versionName === '1' || qgGameManifestJson.versionName === wxProjectJson.libVersion || qgGameManifestJson.versionName === wxPackageJson.version) { if (wxPackageJson) { qgGameManifestJson.versionName = wxPackageJson.version ? wxPackageJson.version : '1' info(`Manifest配置的version为${qgGameManifestJson.versionName}`) } else if (wxProjectJson) { qgGameManifestJson.versionName = wxProjectJson.libVersion ? wxProjectJson.libVersion : '1' info(`Manifest配置的version为${qgGameManifestJson.versionName}`) } } qgGameManifestJson.engine = 'wx' if (unityPluginVersion) { qgGameManifestJson.engine = `wx_unityPlugin_${unityPluginVersion}` } qgGameManifestJson.allianceVersion = 1300 fs.writeFileSync(qgGameManifestJsonPath, JSON.stringify(qgGameManifestJson)) // 读取模版gameJs内容 const qgGameEntry = path.join(qgGamePath, 'src/game.js') const originGameContent = fs.readFileSync(qgGameEntry, { encoding: 'utf8' }) // 拷贝微信小游戏的工程到src目录下 const qgGameSrcPath = path.join(qgGamePath, 'src') info(`正在拷贝微信小游戏源码到${qgGameSrcPath}`) fsExt.overwrite(wxGamePath, qgGameSrcPath, { exclude: new RegExp('(node_modules|manifest.json|qg-transfer|vivo-transfer)') }) const wxManifest = path.join(wxGamePath, 'manifest.json') if (fs.existsSync(wxManifest)) { await overwriteManifest(wxManifest, path.join(qgGameSrcPath, 'manifest.json')) } // copy后检查下copy的文件 await check(qgGameSrcPath) // 将转换后拷贝的gameJS重命名 const gameJs = path.resolve(qgGameSrcPath, 'game.js') let wxGameJs = path.join(qgGameSrcPath, 'wx_Game.js') if (fs.existsSync(wxGameJs)) { wxGameJs = path.join(qgGameSrcPath, 'wx_Game_temp.js') } fs.renameSync(gameJs, wxGameJs) info(`拷贝后的game.js重命名为${wxGameJs},并重新生成gameJS`) // 引入改写的分包main名称 let subContent = '' if (mainSubName !== '') { subContent = `global._main_sub_name_ = '${mainSubName}'\n` } // 将转换后的引擎插件目录重命名 let hasPlugin = false for (const key in pluginProxy) { if (key !== pluginProxy[key]) { const pluginPath = path.resolve(qgGameSrcPath, key) const wxPluginPath = path.join(qgGameSrcPath, pluginProxy[key]) fs.renameSync(wxPluginPath, pluginPath) } subContent += `global._plugin_name_ = '${key}'\n` hasPlugin = true } // 改写game.js,清除weapp-adapter,用weixin-qg-adapter替换 info('正在改写game.js') const wxGameName = path.relative(qgGameSrcPath, wxGameJs) const gameWx2Content = `require('./${wxGameName}')\n` fs.writeFileSync(qgGameEntry, subContent + originGameContent + gameWx2Content, 'utf8') if (hasPlugin || unityPluginVersion) { // 改写js中的requirePlugin,替换为wx.requirePlugin info('微信引擎插件替换 requirePlugin,替换为wx.requirePlugin') replaceFiles(qgGamePath, { include: /\.[jJ][sS]$/, exclude: /node_modules|\.json|babel\.config\.js$|minigame\.config\.js|wxadapter$/, map: /requirePlugin\(/g, to: 'wx.requirePlugin(' }) } // 解决白鹭适配层问题导致的屏幕尺寸变形,查找到白鹭的updateScreenSize函数,其中setCanvasScale微信适配前取整了,qg适配层不需要取整,则将Math.ceil替换为Math.abs,解决尺寸适配问题 info('解决白鹭引擎屏幕尺寸问题,setCanvasScale可以传入小数值') replaceFiles(qgGamePath, { include: /egret\.wxgame\.js$/, exclude: /node_modules|\.json|babel\.config\.js$|minigame\.config\.js|wxadapter$/, map: /Math\.ceil\([^;]*\)[;,][^;,]*Math\.ceil\([^;]*\)[;,][^;,]*DisplayList\.\$setCanvasScale/g, replaceFrom: /Math\.ceil/g, replaceTo: 'Math.abs' }) // 解决cocos动态引用问题 replaceFiles(qgGamePath, { include: /\.[jJ][sS]$/, exclude: /node_modules|\.json|babel\.config\.js$|minigame\.config\.js|wxadapter$/, map: /["']function["']\s*==\s*typeof\s*require\s*&&\s*require[;, ]/g, replaceFrom: /["']function["']\s*==\s*typeof\s*require\s*&&\s*require/g, replaceTo: 'global.__qgRequire' }) // Unity微信小游戏适配 if (unityPluginVersion) { unityPluginVersion = '1.2.32' // if (unityPluginVersion > '1.2.32') { // unityPluginVersion = '1.2.32' // } // 从后台配置中获取wx的unityPlugin代码 info(`开始获取UnityPlugin ${unityPluginVersion}的代码`) const pluginName = `unity_plugin_${unityPluginVersion}.js` // await httpsGetSync(path.join(qgGameSrcPath, pluginName), unityPluginVersion.replace(/\./g, '-')) // 写入UnityPlugin.js到src目录下 info('生成WxUnityPlugin.js文件') const unityPluginPath = path.join(qgGameSrcPath, 'WxUnityPlugin.js') const unityPluginContent = `let plugin;try{require("wx_unity_audio.js");plugin=require("${pluginName}")}catch(error){console.error('Failed to load WxUnityPlugin:',error);throw error}module.exports=plugin;\n` fs.writeFileSync(unityPluginPath, unityPluginContent) info('copy UnityPlugin依赖的babel-runtime...') const overWritePath = path.join(__dirname, 'wx-unity-babel') fsExt.overwrite(overWritePath, qgGameSrcPath) // 支持qg Unity SDK , 将unityInstance 替换为微信的 Module,用于回调 const unitywebJs = path.join(qgGameSrcPath, 'webgl.wasm.framework.unityweb.js') replaceFiles(unitywebJs, { map: /unityInstance/g, to: 'Module' }) // 支持qg Unity日志打印 replaceFiles(unitywebJs, { map: /function HandleError\(err, code\) \{/g, to: 'function HandleError(err, code) { \n console.error(err); ' }) // 解决WebSocket onclose没有reason的bug replaceFiles(unitywebJs, { map: /ev.reason;/g, to: 'ev.reason || "";' }) // 解决WXBundle路径问题 replaceFiles(qgGamePath, { map: /(?<![a-zA-Z])wxfile:/g, to: 'internal:\\/\\/files' }) // 解决wasm分包,导致卡顿问题 replaceFiles(qgGamePath, { map: /(GameGlobal\.canUseH5Renderer)\s*\?\s*([a-zA-Z_$][0-9a-zA-Z_$]*\.wasm_split\.logCall\s*=)/g, to: '$1 || true ? $2' }) } info(chalk.blueBright(`\n###转化完成,微信的部分API和联盟小游戏存在差异,需要手动修改,日志中将会提示不支持的API,请根据提示进行手动修改`)) info(chalk.blueBright(`\n###转化完成后,建议先配置manifest文件中包名版本号等,再进行编译打包!\n\n`)) return qgGamePath } async function overwriteManifest (wxManifest, qgGameSrcManifest) { const wxManifestJson = fsExt.parse(wxManifest) console.log('\n 微信工程下的manifest文件:') console.log(wxManifestJson) const isOverwrite = await readInput('检测到微信工程存在manifest.json,是否覆盖新工程下manifest.json: yes or no (default yes)? ') if (!isOverwrite || isOverwrite === 'yes') { warn(chalk.blueBright(`\n###manifest已被微信工程下文件替换,请检查!\n`)) fsExt.overwrite(wxManifest, qgGameSrcManifest) } } async function readInput (question) { return new Promise(resolve => { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) rl.question(question, answer => { rl.close() resolve(answer) }) rl.on('SIGINT', () => { rl.close() // 关闭Interface对象 }) }) } async function check(wxGamePath) { if (!fs.existsSync(path.resolve(wxGamePath, 'game.js'))) { error(`请确认项目${wxGamePath}下存在文件:game.js`) throw new Error(`请确认项目${wxGamePath}下存在文件:game.js`) } if (!fs.existsSync(path.resolve(wxGamePath, 'game.json'))) { error(`请确认项目${wxGamePath}下存在文件:game.json`) throw new Error(`请确认项目${wxGamePath}下存在文件:game.json`) } }