UNPKG

@honor-minigame/cli

Version:

honor minigame pack cli

243 lines (223 loc) 9.79 kB
/** * 打出带 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,内容为 require('./分包root/game.js')(相对 src 根目录的路径) // 覆盖 webpack 编译产物,避免 quickgame 平台无法执行 __webpack_require__ 包装的代码 const subRoot = root.endsWith('/') ? root : `${root}/` const mainJsContent = `require('./${subRoot}game.js');\n` fs.outputFileSync(path.resolve(paths.BUILD, root, './main.js'), mainJsContent, 'utf8') } } }) resolve() } }) }) } } export default ZipMainPlugin