import fs from 'fs'; import path from 'path'; import { SubpackageConfig } from './Config'; import { Utils } from './Utils'; const START_VERSION = 1000; export type ItemInfo = { md5: Utils.FileMD5[], version: number, size: { version: number, size: number }[] }; export type ItemHandlerResult = { info: ItemInfo, pickFiles: string[] }; export class ItemHandler { private resourcesDir: string; private assetsOutputDir: string; private zipCacheOutputDir: string; private zipOutputDir: string; private versionCacheDir: string; private config: SubpackageConfig; constructor(projectDir: string, mode: string, config: SubpackageConfig) { this.config = config; const buildDir = path.join(projectDir, 'build'); const outputBaseDir = path.join(buildDir, 'subpackage'); Utils.log(`-------------------------------------------Make package [${this.config.key}] handler environment---------------------------------------------------`, Utils.LogLevel.Particular); this.assetsOutputDir = path.join(outputBaseDir, 'assets', config.key); Utils.log(`Make package [${this.config.key}] assets dir: ${this.assetsOutputDir}`, Utils.LogLevel.Particular); Utils.rmdir(this.assetsOutputDir); Utils.mkdir(this.assetsOutputDir); this.zipCacheOutputDir = path.join(outputBaseDir, 'cache', config.key); Utils.log(`Make package [${this.config.key}] zip cache dir: ${this.zipCacheOutputDir}`, Utils.LogLevel.Particular); Utils.rmdir(this.zipCacheOutputDir); Utils.mkdir(this.zipCacheOutputDir); this.zipOutputDir = path.join(outputBaseDir, 'build'); Utils.log(`Make package [${this.config.key}] zip dir: ${this.zipOutputDir}`, Utils.LogLevel.Particular); Utils.mkdir(this.zipOutputDir); this.versionCacheDir = path.join(projectDir, 'tools', 'subpackage', mode, config.key); Utils.log(`Make package [${this.config.key}] info dir: ${this.versionCacheDir}`, Utils.LogLevel.Particular); Utils.mkdir(this.versionCacheDir); this.resourcesDir = path.join(buildDir, 'jsb-default', 'res'); Utils.log(`-------------------------------------------Make package [${this.config.key}] handler environment finished------------------------------------------\n\n`, Utils.LogLevel.Particular); } private readExistVersion(): number[] { const versions: number[] = []; const versionDirs = fs.readdirSync(this.versionCacheDir); for (const versionDir of versionDirs) { // try { const version = parseInt(versionDir); versions.push(version); // } catch (error: unknown) { // continue; // } } return versions.sort((a: number, b: number): number => { return a - b; }); } private pickFile(dir: string, cache: string[]): string[] { cache = cache || []; const files = fs.readdirSync(dir); for (let i = 0; i < files.length; i++) { const subFileName = files[i]; const subFilePath = path.join(dir, subFileName) const stat = fs.statSync(subFilePath); if (stat.isDirectory()) { this.pickFile(subFilePath, cache); } else { const fileName = subFileName.substring(0, subFileName.indexOf('.')); const nameIndex = this.config.uuids.indexOf(fileName); if (nameIndex < 0) continue; // this.config.uuids.splice(nameIndex, 1); Utils.log(`Pick package [${this.config.key}] file: ${subFilePath}`, Utils.LogLevel.Particular); const outAssetBundleDir = path.join(this.assetsOutputDir, dir.replace(this.resourcesDir, 'res')) Utils.mkdir(outAssetBundleDir); fs.copyFileSync(subFilePath, path.join(outAssetBundleDir, subFileName)); cache.push(subFilePath) } } return cache; } private readInfo(version: number): ItemInfo | null { const infoFilePath: string = path.join(this.versionCacheDir, version.toString(), `${this.config.key}.json`); if (!fs.existsSync(infoFilePath)) return null; const subpackageInfoContent = fs.readFileSync(infoFilePath); const info: ItemInfo = JSON.parse(subpackageInfoContent.toString()); return info; } private writeInfo(md5: Utils.FileMD5[], version: number, size: { version: number, size: number }[]): void { const info: ItemInfo = { md5, version, size } const infoContent = JSON.stringify(info); let infoFilePath = path.join(this.versionCacheDir, version.toString(), `${this.config.key}.json`); Utils.mkdir(path.dirname(infoFilePath)); fs.writeFileSync(infoFilePath, infoContent); } public async handle(): Promise { Utils.log(`-----------------------------------------------------Handle package [${this.config.key}] start-----------------------------------------------------`); //筛选文件 const pickFiles: string[] = []; this.pickFile(this.resourcesDir, pickFiles); const md5 = Utils.md5dir(this.assetsOutputDir); //读取版本缓存 const existVersions = this.readExistVersion(); //初始化前一版本数据 let lastItemInfo: ItemInfo = { md5: [], version: START_VERSION, size: [] }; let lastVersion = START_VERSION; let result: ItemHandlerResult; if (existVersions.length) { lastVersion = existVersions[existVersions.length - 1]; Utils.log(`Package [${this.config.key}] last version: ${lastVersion}`, Utils.LogLevel.Particular); lastItemInfo = this.readInfo(lastVersion) || lastItemInfo; lastVersion = lastItemInfo.version; } else { Utils.log(`Package [${this.config.key}] not have version cache`, Utils.LogLevel.Particular); } //对比前一版本 const lastMD5CompareResult = Utils.fileMD5Compare(lastItemInfo.md5, md5); Utils.log(`Package [${this.config.key}] compare with last: added=${lastMD5CompareResult.added.length}, deleted:${lastMD5CompareResult.deleted.length}, same:${lastMD5CompareResult.same.length}, changed:${lastMD5CompareResult.changed.length}`, Utils.LogLevel.Particular); const isChanged = lastMD5CompareResult.changed.length || lastMD5CompareResult.added.length; if (!isChanged) { Utils.log(`Package [${this.config.key}] no changes`); result = { info: lastItemInfo, pickFiles }; } else { Utils.log(`Package [${this.config.key}] changed`); const newVersion = lastVersion + 1; const size: { version: number, size: number }[] = []; for (const version of existVersions) { Utils.log(`Pick package [${this.config.key}] assets for version ${version} to ${newVersion}`); const versionInfo = this.readInfo(version); if (!versionInfo) continue; //对比文件 const versionCompareResult = Utils.fileMD5Compare(versionInfo.md5, md5); Utils.log(`Package [${this.config.key}] compare with version ${version}: added=${versionCompareResult.added.length}, deleted:${versionCompareResult.deleted.length} , same:${versionCompareResult.same.length}, changed:${versionCompareResult.changed.length}`, Utils.LogLevel.Particular); //只压缩changed和added const files = versionCompareResult.changed.concat(versionCompareResult.added); const zipCacheDir = path.join(this.zipCacheOutputDir, `${version}_${newVersion}`); Utils.mkdir(zipCacheDir); for (const fileMD5 of files) { const outPath = path.join(zipCacheDir, fileMD5.path.replace(this.assetsOutputDir, '')) Utils.mkdir(path.dirname(outPath)); fs.copyFileSync(fileMD5.path, outPath); } const zipOutFile = path.join(this.zipOutputDir, `${this.config.key}_${version}_${newVersion}.zip`); await Utils.zipdir(zipCacheDir, zipOutFile); Utils.rmdir(zipCacheDir); const zipStat = fs.statSync(zipOutFile); size.push({ version, size: zipStat.size }); } const zipOutFile = path.join(this.zipOutputDir, `${this.config.key}_${newVersion}.zip`); await Utils.zipdir(this.assetsOutputDir, zipOutFile); Utils.rmdir(this.assetsOutputDir); const zipStat = fs.statSync(zipOutFile); size.push({ version: 0, size: zipStat.size }); this.writeInfo(md5, newVersion, size); result = { info: { version: newVersion, size, md5 }, pickFiles }; } Utils.log(`-----------------------------------------------------Handle package [${this.config.key}] end-------------------------------------------------------\n\n`); return result; } }