@honor-minigame/cli
Version:
honor minigame pack cli
333 lines (292 loc) • 13.9 kB
JavaScript
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'
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}, qg联盟工程将导出在 ${qgGamePath}`))
// 如果有qg-transfer文件夹,则清理一下qg-transfer,否则创建
info(`正在清理qg联盟工程目录:${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) {
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 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`)
}
}