@honor-minigame/cli
Version:
honor minigame pack cli
246 lines (226 loc) • 9.86 kB
JavaScript
/**
* 打出带 main.js 入口的游戏包,适配联盟引擎
*/
import path from 'path'
import fs from 'fs-extra'
import { globSync } from 'glob'
import chalk from 'chalk'
import { fileURLToPath } from 'url'
import * as paths from '../lib/paths.js'
import { getWorkersPath } from '../lib/manifest.js'
import { error, info } from '../../utils/logger.js'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
/**
* 将webpack引用的js资源保存到webpack_use_moudle.json文件中,动态编译js时在webpack的webpackEmptyContext中可能会用到
* @param {*} modules
*/
function saveWebpackUseModules(chunkGraph, modules, unUsedFile) {
try {
const modulePathMap = {}
unUsedFile.forEach((file) => {
try {
modulePathMap[file.replace(/\\/g, '/')] = -1
} catch (err) {
error(err)
}
})
// 遍历所有模块,生成模块 ID 和路径的映射
modules.forEach((module) => {
try {
const id = chunkGraph.getModuleId(module)
if (id != null) {
const ss = module.resource
if (ss && ss.indexOf(paths.SRC) !== -1) {
const arr = path.relative(paths.SRC, ss).replace(/\\/g, '/')
modulePathMap[arr] = chunkGraph.getModuleId(module)
}
}
} catch (err) {
}
})
const webpackUseModule = path.join(paths.BUILD, 'webpack_use_moudle.json')
if (!fs.existsSync(webpackUseModule)) {
info(`webpackUseModule 保存到 ${webpackUseModule}`)
fs.outputFileSync(webpackUseModule, JSON.stringify(modulePathMap), 'utf8')
}
} catch (err) {
error(err)
}
}
/**
* 将webpack未引用的js文件copy到build目录下参与打包,动态编译js时在webpack的webpackEmptyContext中可能会用到
* @param {*} fileDependencies
* @returns
*/
function copyUnUsedFiles(fileDependencies) {
try {
// 过滤掉workers配置文件
const workersPath = getWorkersPath()
// 获取webpack使用的js模块
const usedModules = Array.from(fileDependencies)
.filter(file => file.indexOf(paths.SRC) !== -1 && (workersPath === '' || file.indexOf(workersPath) === -1))
.reduce((obj, item) => Object.assign(obj, { [item]: true }), {})
const normalizedUsedModules = Object.fromEntries(
Object.entries(usedModules).map(([key, value]) => [
path.normalize(key),
value
])
)
// 获取src下所有的js文件
const files = globSync('**/*.js', { ignore: ['node_modules/**'], cwd: paths.SRC, absolute: true })
// 过滤src下未使用的js文件
const unUsedFiles = files.filter(file => !normalizedUsedModules[path.normalize(file)])
const allFiles = unUsedFiles.reduce(
(array, item) => array.concat(item),
[]
)
if (!allFiles.length) {
return []
}
// 将所有未使用的js文件copy到build目录下
info(chalk.green('*** Unused Plugin ***'))
info(`${allFiles.length} unused source files found.`)
const unUsedFile = []
allFiles.forEach(file => {
if (!file.includes('@babel/runtime') && fs.existsSync(file) && !fs.statSync(file).isDirectory()) {
const relative = path.relative(paths.SRC, file)
const destFile = path.resolve(paths.BUILD, relative)
if (!fs.existsSync(destFile)) {
info(`unused JS File ${relative} copy to Build dir.`)
// 默认情况下,如果 dest 已经存在,则覆盖它。
const destDir = path.dirname(destFile)
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true })
}
fs.copyFileSync(file, destFile)
unUsedFile.push(relative)
}
}
})
// 将未引用的文件缓存下来,webpack完成打包时使用
paths.unUsedFiles = unUsedFile
return unUsedFile
} catch (err) { }
}
function retryCopyFileSync(src, dest, maxRetries = 3) {
let attempts = 0
while (attempts < maxRetries) {
let isCopySuccess = false
try {
// 自动创建目标目录并复制文件
fs.copySync(src, dest, { overwrite: true })
info(`[成功] 文件已复制至: ${dest}`)
isCopySuccess = true
} catch (e) {
error(`[错误] 复制失败: ${e.message}`)
isCopySuccess = false
}
// 如果复制失败,尝试重试
if (isCopySuccess) {
return true
}
attempts++
info(`[重试] 第 ${attempts} 次尝试...`)
}
error(`[终止] 重试 ${maxRetries} 次后仍失败`)
return false
}
class ZipMainPlugin {
constructor(options) {
this.options = options
}
apply(compiler) {
const pluginName = 'ZipMainPlugin'
const { singlePackage, alliance, subpackages } = this.options
compiler.hooks.afterEmit.tapPromise(pluginName, compilation => {
return new Promise(resolve => {
// 将webpack未引入的js文件 copy到build目录下
const unUsedFile = copyUnUsedFiles(compilation.fileDependencies)
// 将webpack的映射信息保存下来
saveWebpackUseModules(compilation.chunkGraph, compilation.modules, unUsedFile)
// 如果是打原整包
if (singlePackage) {
// 联盟适配转换代码
let needTransferAlliance = true
const allianceAdapterJS = 'alliance_adapter'
const gameContent = fs.readFileSync(path.resolve(paths.BUILD, './game.js'), {
encoding: 'utf-8'
})
if (gameContent.includes(allianceAdapterJS)) {
needTransferAlliance = false
if (alliance) {
info('game.js 包含联盟 alliance_adapter.js 转换代码,不进行二次转换...')
}
}
if (needTransferAlliance) {
info('开始写入联盟适配代码...')
// 生成 main.js文件,引入 game.js,作为适配联盟api的入口
if (fs.existsSync(path.resolve(paths.BUILD, './main.js'))) {
info('main.js为入口文件,不允许作为业务文件引入进来,如果游戏有报错,请修改文件名,重新打包')
const content = fs.readFileSync(path.resolve(paths.BUILD, './main.js'), {
encoding: 'utf-8'
})
// 判断build是否包含适配文件
const allianceJs = path.resolve(paths.BUILD, './alliance_adapter.js')
if (!fs.existsSync(allianceJs)) {
const allianceJs = path.join(__dirname, '../alliance_adapter/alliance_adapter.js')
retryCopyFileSync(allianceJs, path.resolve(paths.BUILD, './alliance_adapter.js'))
}
// 判断main.js是否已引入适配js
if (!content.includes(allianceAdapterJS)) {
// 重新写入vivo_alliance_adapter
const allianceAdapterContent = 'require(\'alliance_adapter.js\');\n'
fs.outputFileSync(path.resolve(paths.BUILD, './main.js'), allianceAdapterContent + content, 'utf8')
}
} else {
const allianceJs = path.join(__dirname, '../alliance_adapter/alliance_adapter.js')
retryCopyFileSync(allianceJs, path.resolve(paths.BUILD, './alliance_adapter.js'))
fs.outputFileSync(path.resolve(paths.BUILD, './main.js'), 'require(\'alliance_adapter.js\');\nrequire("game.js");', 'utf8')
}
} else {
// 生成 main.js文件,引入 game.js,作为适配联盟api的入口
if (fs.existsSync(path.resolve(paths.BUILD, './main.js'))) {
info('main.js为入口文件,不允许作为业务文件引入进来,如果游戏有报错,请修改文件名,重新打包')
} else {
fs.outputFileSync(path.resolve(paths.BUILD, './main.js'), 'require("game.js");', 'utf8')
}
}
resolve()
} else {
// 如果是打分包,检查是目录的分包,增加main.js,引入 game.js
subpackages.forEach(({ root }) => {
const ext = path.extname(root)
if (ext === '') {
// 如果是联盟,则为了兼容荣耀分包,将分包内的game.js copy到main.js,防止require引用失败
if (alliance) {
try {
if (fs.existsSync(path.resolve(paths.BUILD, root, './game.js'))) {
fs.renameSync(path.resolve(paths.BUILD, root, './game.js'), path.resolve(paths.BUILD, root, './main.js'))
}
if (fs.existsSync(path.resolve(paths.BUILD, root, './game.js.map'))) {
fs.renameSync(path.resolve(paths.BUILD, root, './game.js.map'), path.resolve(paths.BUILD, root, './main.js.map'))
}
} catch (err) {
console.error(err)
}
} else {
// 如果是目录
// 生成 main.js文件,引入 game.js,作为分包适配联盟api的入口
if (fs.existsSync(path.resolve(paths.BUILD, root, './main.js'))) {
// console.warn('main.js为入口文件,不允许作为业务文件引入进来,如果游戏有报错,请修改文件名,重新打包')
info(root + '/main.js为入口文件,不允许作为业务文件引入进来,如果游戏有报错,请修改文件名,重新打包')
// throw new Error('main.js为子包入口文件,不允许作为业务文件引入,请修改文件名,重新打包')
} else {
fs.outputFileSync(path.resolve(paths.BUILD, root, './main.js'), 'require("./game.js");', 'utf8')
}
}
}
})
resolve()
}
})
})
}
}
export default ZipMainPlugin