UNPKG

@quick-game/cli

Version:

Command line interface for rapid qg development

501 lines (449 loc) 22.2 kB
"use strict";var _WeakMap = require("@babel/runtime-corejs2/core-js/weak-map");var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/define-property");var _Object$getOwnPropertyDescriptor = require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptor");var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");_Object$defineProperty(exports, "__esModule", { value: true });exports.release = exports.build = void 0;var _create = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/create"));var _parseFloat2 = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/parse-float"));var _map = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/map"));var _values = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/values"));var _path = _interopRequireDefault(require("path")); var _index = require("../cli-shared-utils/index.js"); var _http = _interopRequireDefault(require("http")); var _process = require("./config/process.js"); var paths = _interopRequireWildcard(require("./lib/paths.js")); var _manifest = require("./lib/manifest.js"); var _constanst = require("./lib/constanst.js"); var _subpackage = require("./lib/subpackage.js"); var _zipPack = _interopRequireDefault(require("./lib/zipPack.js")); var _fsUtils = require("../cli-shared-utils/lib/fsUtils"); var _requestIde = require("../utils/requestIde.js"); var _index2 = _interopRequireDefault(require("./webpack/lib/index.js"));function _getRequireWildcardCache(e) {if ("function" != typeof _WeakMap) return null;var r = new _WeakMap(),t = new _WeakMap();return (_getRequireWildcardCache = function (e) {return e ? t : r;})(e);}function _interopRequireWildcard(e, r) {if (!r && e && e.__esModule) return e;if (null === e || "object" != typeof e && "function" != typeof e) return { default: e };var t = _getRequireWildcardCache(r);if (t && t.has(e)) return t.get(e);var n = { __proto__: null },a = _Object$defineProperty && _Object$getOwnPropertyDescriptor;for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) {var i = a ? _Object$getOwnPropertyDescriptor(e, u) : null;i && (i.get || i.set) ? _Object$defineProperty(n, u, i) : n[u] = e[u];}return n.default = e, t && t.set(e, n), n;} /** * 将webpack源码引入进来解决动态编译问题,修改lib\ContextModule.js * 1、__webpack_require__.webpackContext = webpackContext,将webpackContext赋值给__webpack_require__ * 2、修改webpackEmptyContext,if (__webpack_require__.webpackContext.resolve(req)),若存在已编译的路径,则直接运行 **/let isIde = false;let packageType = 'build'; let targetPath = paths.DIST; const notifyIde = (status) => { if (isIde) { const type = packageType === 'release' ? 'releaseFinish' : 'buildFinish'; (0, _requestIde.fetchInfo)({ status }, type); } }; /** * 发布构建 * * @param {Object} args - 构建参数 * @returns {Promise<Object>} 构建结果 */ const release = async (args) => { return build({ ...args, isRelease: true, buildType: 'release' }); }; /** * 构建项目包 * * @param {Object} options - 构建选项 * @param {boolean} [options.isRelease=false] - 是否为发布版本 * @param {boolean} [options.inject=true] - 是否注入依赖 * @param {string} [options.buildType] - 构建类型 * @param {function} [options.complete=() => {}] - 构建完成后的回调函数 * @param {boolean} [options.amd=true] - 是否使用AMD模块 * @param {boolean} [options.rpk=false] - 是否使用RPK打包 * @returns {Promise<void>} 无返回值 */exports.release = release; const build = async ({ isRelease = false, inject = true, buildType, complete = () => {}, amd = true, rpk = false, ide = false, dist = '' }) => { packageType = isRelease ? 'release' : 'build'; // 设置全局是否ide调用 isIde = ide; targetPath = dist || paths.DIST; // 校验manifest (0, _manifest.check)(); try { // 校验通过后,可以清空编译目录 _index.fs.removeSync(paths.BUILD); // IDE支持修改dist路径,如果路径为项目父级,会出现报错,所以在IDE中使用时,不删除dist目录 if (!ide) { _index.fs.removeSync(targetPath); } // TODO 开发需要看temp里面的包,方便测试,上线后去掉 _index.fs.removeSync(paths.TEMP); } catch (err) { console.error('removeSync failed', err); } // 读取manifest配置 const manifest = (0, _manifest.get)(); // 添加manifest配置 buildType if (buildType) { (0, _manifest.add)('buildType', buildType); } else { (0, _manifest.add)('buildType', 'dev'); } // 过滤出合法的分包配置 const subpackages = (0, _subpackage.filter)(manifest.subpackages); // 将分包配置转化为对应的webpack的打包入口 // 将分包配置转化为对应的externals用于打出兼容的原整包 const { entry, externals, isMain } = (0, _subpackage.toConfigs)(subpackages); // 对分包名称判断 不能为main if (isMain) { (0, _index.error)('请检查分包的配置,分包名称不能为main,请修改分包名称'); notifyIde('error'); return; } // 取出包名 const packageName = manifest.package || 'Bundle'; const rpkStartTime = new Date().valueOf(); // 如果配置了rpk,则直接将src目录copy到build目录并压缩 if (rpk) { rpkZip(isRelease, packageName, subpackages, manifest, buildType, rpkStartTime, complete); return; } // 先打原整包 const singleCompiler = (0, _index2.default)((0, _process.singleConfig)(entry, isRelease, externals, inject, amd)); /** * 编译原整包的完成处理函数 * @param {Object} err * @param {Object} stats */ const singleDone = (err, stats) => { if ((0, _process.checkError)(err, stats)) { notifyIde('error'); return; } // 如果存在未使用的模块,则将所有未使用的模块作为入口重新webpack打包 if (paths.unUsedFiles.length !== 0) { (0, _index.info)(`paths.unUsedFiles 存在未引入的文件重新打包,module: ${paths.unUsedFiles}`); const entry = (0, _create.default)(null); paths.unUsedFiles.forEach((unUsedFile) => { const filePath = _path.default.resolve(paths.BUILD, unUsedFile); if (_index.fs.existsSync(filePath)) { const parsedPath = _path.default.parse(unUsedFile); const entryKey = _path.default.join(parsedPath.dir, parsedPath.name); entry[entryKey] = filePath; } }); paths.unUsedFiles = []; const compiler = (0, _index2.default)((0, _process.base)(isRelease, entry, amd)); compiler.run(singleDone); return; } const compileStartTime = new Date().valueOf(); const promise = (0, _zipPack.default)(isRelease, packageName, true, subpackages, manifest, buildType, compileStartTime); promise.then((resolve) => { doneNext(); }); }; // 等待原整包打包完成再继续编译 const doneNext = () => { (0, _index.info)(`${_constanst.LOG_TITLE}原整包打包完成! 耗时: ${(new Date().valueOf() - rpkStartTime) / 1000}s`); // 原整包打包完成后,判断是否存在分包配置,如果有就编译分包,如果没有,拷贝原整包作为最终的输出包 if (subpackages.length) { // 原整包打包完成后,开始打分包 (0, _index.info)(`${_constanst.LOG_TITLE}开始编译分包...`); (0, _index2.default)((0, _process.multiConfig)(entry, isRelease, packageName, externals, subpackages, manifest, buildType, amd), (err, stats) => { if ((0, _process.checkError)(err, stats)) { notifyIde('error'); return; } (0, _index.info)(`${_constanst.LOG_TITLE}总包打包完成~~~! 总耗时: ${(new Date().valueOf() - rpkStartTime) / 1000}s`); const qgGameName = _path.default.basename(_index.projectPath) + '-qg-build'; const dist = targetPath ? _path.default.join(targetPath, qgGameName) : _path.default.join(_path.default.dirname(_index.projectPath), qgGameName); const isDistParent = _path.default.dirname(dist).startsWith(paths.DIST) || _path.default.resolve(_path.default.dirname(dist)).toLowerCase() === _path.default.resolve(paths.DIST).toLowerCase(); const isTempParent = _path.default.dirname(dist).startsWith(paths.TEMP) || _path.default.resolve(_path.default.dirname(dist)).toLowerCase() === _path.default.resolve(paths.TEMP).toLowerCase(); if (!isDistParent && !isTempParent) { try { // 创建A和B文件夹 const folderDist = _path.default.join(dist, 'dist'); const folderDistTemp = _path.default.join(dist, 'dist_temp'); // 检查目标路径是否存在且有写入权限 if (!_index.fs.existsSync(folderDist)) { _index.fs.mkdirSync(folderDist, { recursive: true }); } if (!_index.fs.existsSync(folderDistTemp)) { _index.fs.mkdirSync(folderDistTemp, { recursive: true }); } // 确保文件夹存在 try { _index.fs.ensureDirSync(folderDistTemp); _index.fs.ensureDirSync(folderDist); } catch (mkdirErr) { (0, _index.error)(`创建文件夹失败: ${mkdirErr.message}`); throw mkdirErr; } // 复制DIST内容到folderDist文件夹 if (_index.fs.existsSync(paths.DIST)) { try { _index.fs.copySync(paths.DIST, folderDist); (0, _index.info)(`已将 ${paths.DIST} 内容复制到 ${folderDist}`); } catch (copyErr) { (0, _index.error)(`复制DIST内容失败: ${copyErr.message}`); throw copyErr; } } // 复制TEMP内容到folderDist_temp文件夹 if (_index.fs.existsSync(paths.TEMP)) { try { _index.fs.copySync(paths.TEMP, folderDistTemp); (0, _index.info)(`已将 ${paths.TEMP} 内容复制到 ${folderDistTemp}`); } catch (copyErr) { (0, _index.error)(`复制TEMP内容失败: ${copyErr.message}`); throw copyErr; } } } catch (err) { (0, _index.error)(`处理文件夹操作失败: ${err.message}`); notifyIde('error'); return; } } final(packageName, complete, rpkStartTime); }); } else { // 不分包不带插件包的 才走这个逻辑 // 不分包的带插件包的打包已经在webpack插件里面做了 const temp = _path.default.join(paths.TEMP, `${packageName}${_constanst.RPK}`); const dist = _path.default.join(targetPath, `${packageName}${isRelease ? _constanst.RPK_SIGNED : _constanst.RPK}`); _index.fs.copy(temp, dist).then(() => { final(packageName, complete, rpkStartTime); }).catch((err) => { (0, _index.error)(err); notifyIde('error'); }); } }; (0, _index.info)(`${_constanst.LOG_TITLE}开始编译原整包...`); singleCompiler.run(singleDone); }; // 全部结束后的处理函数 exports.build = build;const final = (packageName, complete, rpkStartTime) => { complete(); notifyIde('success'); (0, _index.done)(`${_constanst.LOG_TITLE}【统一rpk】压缩包已生成:${targetPath} ${(new Date().valueOf() - rpkStartTime) / 1000}s`); // 打印包体信息 logRpk(packageName); // 通知引擎自动更新包 notify(); }; function logRpk(packageName) { // 打印包体信息 try { (0, _index.done)(`【统一rpk】打包完成,包体信息如下:\n`); const files = _index.fs.readdirSync(paths.TEMP); let subAllSize = 0; let subSize = 0; let mainSize = 0; let fileInfoArray = files.map((file) => { if (file.endsWith('.rpk')) { const filePath = _path.default.resolve(paths.TEMP, file); const stats = _index.fs.statSync(filePath); let size = Number(stats.size / 1024 / 1024).toFixed(3); size = size > 1 ? Number(size).toFixed(1) : size; const fileName = file.split('.rpk')[0]; // 包体名称 let name = fileName === 'main' ? '主包' : `分包/${fileName}`; name = fileName === packageName ? '原整包' : name; name = fileName === 'cocos-library' || fileName === 'laya-library' || fileName === 'egret-library' ? `插件包` : name; // 分包size加到一起 if (name.startsWith('分包')) { subAllSize += (0, _parseFloat2.default)(size); subSize++; } if (name === '主包') { mainSize = size; } return { name: name, file, size: `${size} MB` }; } }).filter((fileInfo) => fileInfo !== undefined); if (fileInfoArray.length === 1) { const files = _index.fs.readdirSync(targetPath); fileInfoArray = files.map((file) => { if (file.endsWith('.rpk')) { const fileName = file.split('.rpk')[0]; if (fileName.includes(packageName)) { const filePath = _path.default.resolve(targetPath, file); const stats = _index.fs.statSync(filePath); let size = Number(stats.size / 1024 / 1024).toFixed(3); size = size > 1 ? Number(size).toFixed(1) : size; return { name: 'dist/整包', file, size: `${size} MB` }; } } }).filter((fileInfo) => fileInfo !== undefined); } else { const files = _index.fs.readdirSync(targetPath); const fileDistArray = files.map((file) => { if (file.endsWith('.rpk')) { const fileName = file.split('.rpk')[0]; if (fileName.includes(packageName)) { const filePath = _path.default.resolve(targetPath, file); const stats = _index.fs.statSync(filePath); let size = Number(stats.size / 1024 / 1024).toFixed(3); size = size > 1 ? Number(size).toFixed(1) : size; return { name: 'dist/整包', file, size: `${size} MB` }; } } }).filter((fileInfo) => fileInfo !== undefined); if (subAllSize !== 0) { fileInfoArray.push({ name: '所有分包', file: `${subSize}个`, size: `${Number(subAllSize).toFixed(3)} MB` }); } const sortOrder = new _map.default([ ['原整包', 1], ['主包', 2], ['插件包', 3], ['所有分包', 4]] ); fileInfoArray.sort((a, b) => { if (sortOrder.get(a.name) && sortOrder.get(b.name)) { return sortOrder.get(a.name) - sortOrder.get(b.name); } if (sortOrder.get(a.name)) { return -1; } if (sortOrder.get(b.name)) { return 1; } const sizeA = (0, _parseFloat2.default)(a.size.split('MB')[0]); const sizeB = (0, _parseFloat2.default)(b.size.split('MB')[0]); // 先按文件大小排序 if (sizeA !== sizeB) { return sizeB - sizeA; } else { return b.file.localeCompare(a.file); } }); fileInfoArray = fileDistArray.concat(fileInfoArray); } console.log(fileInfoArray.map((obj) => (0, _values.default)(obj))); if (fileInfoArray.length === 1) { console.info(`\n### 开平会校验单个整包大小:${fileInfoArray[0].size}`); } else if (mainSize !== 0 && subAllSize !== 0) { console.info(`\n### 开平只会校验 主包main.rpk大小:${mainSize} MB, 所有分包大小:${Number(subAllSize).toFixed(3)} MB`); } console.log('\n'); } catch (err) { } } function notify() { try { (0, _index.info)(`### App Server ### 发送更新通知请求`); const json = _index.fs.readJsonSync(_path.default.resolve(_index.projectPath, 'client.json')); console.log(json); // 给游戏前台发送更新通知 const gameRequrl = `http://${json.client}:49517/update`; const reqGame = _http.default.request({ host: json.client, port: 49517, path: '/update', timeout: 2000 }, (res) => { (0, _index.info)(`### App Server ### 给引擎发送更新通知请求: ${gameRequrl} 成功`); }).on('error', () => { (0, _index.info)(`### App Server ### 给引擎发送更新通知请求: ${gameRequrl} 失败`); // 给调试器发送 const requrl = `http://${json.client}:39517/update`; const req = _http.default.request({ host: json.client, port: 39517, path: '/update', timeout: 2000 }, (res) => { (0, _index.info)(`### App Server ### 发送自动更新调试器器请求: ${requrl} 成功`); }).on('error', () => { // error(`### App Server ### 发送自动更新调试器请求: ${requrl} 失败: ${err.message}`) }).on('timeout', function () {// error(`### App Server ### 发送自动更新调试器请求: ${requrl} 超时: 3000ms`) req.abort(); }); req.end(); }).on('timeout', function () { (0, _index.info)(`### App Server ### 给引擎发送更新通知请求: ${gameRequrl} 超时: 3000ms`); reqGame.abort(); }); reqGame.end(); } catch (error) { (0, _index.info)(`### App Server ### 没找到client信息`); } } /** * 不使用webpack直接压缩打包联盟RPK文件 * * @param {boolean} isRelease - 是否为发布版本 * @param {string} packageName - 包的名称 * @param {Array} subpackages - 分包配置 * @param {Object} manifest - 包的清单文件 * @param {string} buildType - 构建类型 * @param {number} rpkStartTime - RPK打包开始时间 * @param {function} complete - 打包完成后的回调函数 * @returns {Promise} 返回一个Promise,表示打包过程的状态 */ const rpkZip = (isRelease, packageName, subpackages, manifest, buildType, rpkStartTime, complete) => { // copy src目录下文件到build目录 _fsUtils.fsExt.overwrite(paths.SRC, paths.BUILD); // 联盟包体处理 // 联盟适配转换代码 let needTransferAlliance = true; const vivoAllianceAdapterJS = 'vivo_alliance_adapter'; const gameContent = _index.fs.readFileSync(_path.default.resolve(paths.BUILD, './game.js'), { encoding: 'utf-8' }); if (gameContent.includes(vivoAllianceAdapterJS)) { needTransferAlliance = false; (0, _index.info)('game.js 包含联盟 vivo_alliance_adapter.js 转换代码,不进行二次转换...'); } if (needTransferAlliance) { (0, _index.info)('开始写入联盟转换代码...'); // 生成 main.js文件,引入 game.js,作为适配联盟api的入口 if (_index.fs.existsSync(_path.default.resolve(paths.BUILD, './main.js'))) { (0, _index.info)('main.js为入口文件,不允许作为业务文件引入进来,如果游戏有报错,请修改文件名,重新打包'); const content = _index.fs.readFileSync(_path.default.resolve(paths.BUILD, './main.js'), { encoding: 'utf-8' }); // 判断build是否包含适配文件 const allianceJs = _path.default.resolve(paths.BUILD, './vivo_alliance_adapter.js'); if (!_index.fs.existsSync(allianceJs)) { const allianceJs = _path.default.join(__dirname, './plugins/alliance_adapter/vivo_alliance_adapter.js'); _index.fs.copyFileSync(allianceJs, _path.default.resolve(paths.BUILD, './vivo_alliance_adapter.js')); } // 判断main.js是否已引入适配js if (!content.includes(vivoAllianceAdapterJS)) { // 重新写入vivo_alliance_adapter const allianceAdapterContent = 'require(\'vivo_alliance_adapter.js\');\n'; _index.fs.outputFileSync(_path.default.resolve(paths.BUILD, './main.js'), allianceAdapterContent + content, 'utf8'); } } else { const allianceJs = _path.default.join(__dirname, './plugins/alliance_adapter/vivo_alliance_adapter.js'); _index.fs.copyFileSync(allianceJs, _path.default.resolve(paths.BUILD, './vivo_alliance_adapter.js')); _index.fs.outputFileSync(_path.default.resolve(paths.BUILD, './main.js'), 'require(\'vivo_alliance_adapter.js\');\nrequire("game.js");', 'utf8'); } } else { // 生成 main.js文件,引入 game.js,作为适配联盟api的入口 if (_index.fs.existsSync(_path.default.resolve(paths.BUILD, './main.js'))) { (0, _index.info)('main.js为入口文件,不允许作为业务文件引入进来,如果游戏有报错,请修改文件名,重新打包'); // throw new Error() } else { _index.fs.outputFileSync(_path.default.resolve(paths.BUILD, './main.js'), 'require("game.js");', 'utf8'); } } // 打包原整包 const compileStartTime = new Date().valueOf(); const promise = (0, _zipPack.default)(isRelease, packageName, true, subpackages, manifest, buildType, compileStartTime); promise.then((resolve) => { doneNext(); }); // 等待原整包打包完成再继续编译 const doneNext = () => { (0, _index.info)(`${_constanst.LOG_TITLE}原整包打包完成! 耗时: ${(new Date().valueOf() - rpkStartTime) / 1000}s`); // 原整包打包完成后,判断是否存在分包配置,如果有就编译分包,如果没有,拷贝原整包作为最终的输出包 if (subpackages.length) { // 原整包打包完成后,开始打分包 (0, _index.info)(`${_constanst.LOG_TITLE}开始编译分包...`); const compileStartTime = new Date().valueOf(); const promise = (0, _zipPack.default)(isRelease, packageName, false, subpackages, manifest, buildType, compileStartTime); promise.then((resolve) => { final(packageName, complete, rpkStartTime); }); } else { const temp = _path.default.join(paths.TEMP, `${packageName}${_constanst.RPK}`); const dist = _path.default.join(targetPath, `${packageName}${isRelease ? _constanst.RPK_SIGNED : _constanst.RPK}`); _index.fs.copy(temp, dist).then(() => { final(packageName, complete, rpkStartTime); }).catch((err) => { (0, _index.error)(err); }); } }; };