1/* 2 * Copyright (c) 2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use rollupObject file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import path from 'path'; 17import fs from 'fs'; 18import { 19 EXTNAME_ETS, 20 EXTNAME_JS, 21 EXTNAME_TS, 22 EXTNAME_MJS, 23 EXTNAME_CJS, 24 SOURCEMAPS, 25 SOURCEMAPS_JSON, 26 yellow, 27 reset 28} from "./common/ark_define"; 29import { 30 changeFileExtension, 31 isCommonJsPluginVirtualFile, 32 isCurrentProjectFiles, 33 isDebug, 34 shouldETSOrTSFileTransformToJS 35} from "./utils"; 36import { 37 toUnixPath, 38 isPackageModulesFile, 39 getProjectRootPath 40} from "../../utils"; 41import { 42 handleObfuscatedFilePath, 43 mangleFilePath, 44 enableObfuscateFileName 45} from './common/ob_config_resolver'; 46import { MemoryMonitor } from '../meomry_monitor/rollup-plugin-memory-monitor'; 47import { MemoryDefine } from '../meomry_monitor/memory_define'; 48import { 49 ArkTSInternalErrorDescription, 50 ErrorCode 51} from './error_code'; 52import { 53 CommonLogger, 54 LogData, 55 LogDataFactory 56} from './logger'; 57import { 58 createAndStartEvent, 59 CompileEvent, 60 stopEvent 61} from '../../performance'; 62import { BytecodeObfuscator } from './bytecode_obfuscator'; 63 64let isShouldSourceMap: boolean = true; 65 66export class SourceMapGenerator { 67 private static instance: SourceMapGenerator | undefined = undefined; 68 private static rollupObject: Object | undefined; 69 70 private projectConfig: Object; 71 private sourceMapPath: string; 72 private sourceMapPathTmp: string; 73 private cacheSourceMapPath: string; 74 private sourceMapForMergePath: string; 75 private triggerAsync: Object; 76 private triggerEndSignal: Object; 77 private sourceMaps: Object = {}; 78 private sourceMapKeys: Set<string> = new Set([]); 79 private isNewSourceMap: boolean = true; 80 private keyCache: Map<string, string> = new Map(); 81 private logger: CommonLogger; 82 private isFirstAppend: boolean = true; 83 private isCompileSingle: boolean = false; 84 private originFileEdited: boolean = false; 85 private tempFileEdited: boolean = false; 86 87 public sourceMapKeyMappingForObf: Map<string, string> = new Map(); 88 89 constructor(rollupObject: Object) { 90 this.projectConfig = Object.assign(rollupObject.share.arkProjectConfig, rollupObject.share.projectConfig); 91 this.sourceMapPath = this.getSourceMapSavePath(); 92 this.sourceMapPathTmp = path.join(this.projectConfig.cachePath, SOURCEMAPS_JSON + '.tmp'); 93 this.cacheSourceMapPath = path.join(this.projectConfig.cachePath, SOURCEMAPS_JSON); 94 this.sourceMapForMergePath = this.cacheSourceMapPath.replace('.json', 'Merge.json'); 95 this.triggerAsync = rollupObject.async; 96 this.triggerEndSignal = rollupObject.signal; 97 this.logger = CommonLogger.getInstance(rollupObject); 98 } 99 100 static init(rollupObject: Object): void { 101 SourceMapGenerator.rollupObject = rollupObject; 102 SourceMapGenerator.instance = new SourceMapGenerator(SourceMapGenerator.rollupObject); 103 104 // adapt compatibility with hvigor 105 if (!SourceMapGenerator.instance.projectConfig.entryPackageName || 106 !SourceMapGenerator.instance.projectConfig.entryModuleVersion) { 107 SourceMapGenerator.instance.isNewSourceMap = false; 108 } 109 110 if ((SourceMapGenerator.instance.projectConfig.hotReload && 111 SourceMapGenerator.instance.projectConfig.watchMode !== 'true') || 112 (SourceMapGenerator.instance.projectConfig.coldReload)) { 113 isShouldSourceMap = SourceMapGenerator.instance.projectConfig.isFirstBuild; 114 } 115 116 SourceMapGenerator.instance.isCompileSingle = SourceMapGenerator.instance.isNewSourceMap && 117 SourceMapGenerator.instance.projectConfig.singleFileEmit && isShouldSourceMap; 118 119 if (SourceMapGenerator.instance.projectConfig.hotReload) { 120 isShouldSourceMap = false; 121 } 122 } 123 124 static getInstance(): SourceMapGenerator { 125 if (!SourceMapGenerator.instance) { 126 SourceMapGenerator.instance = new SourceMapGenerator(SourceMapGenerator.rollupObject); 127 } 128 return SourceMapGenerator.instance; 129 } 130 131 //In window plateform, if receive path join by '/', should transform '/' to '\' 132 private getAdaptedModuleId(moduleId: string): string { 133 return moduleId.replace(/\//g, path.sep); 134 } 135 136 private getPkgInfoByModuleId(moduleId: string, shouldObfuscateFileName: boolean = false): Object { 137 moduleId = this.getAdaptedModuleId(moduleId); 138 139 const moduleInfo: Object = SourceMapGenerator.rollupObject.getModuleInfo(moduleId); 140 if (!moduleInfo) { 141 const errInfo: LogData = LogDataFactory.newInstance( 142 ErrorCode.ETS2BUNDLE_INTERNAL_GET_MODULE_INFO_FAILED, 143 ArkTSInternalErrorDescription, 144 `Failed to get ModuleInfo, moduleId: ${moduleId}` 145 ); 146 this.logger.printErrorAndExit(errInfo); 147 } 148 const metaInfo: Object = moduleInfo['meta']; 149 if (!metaInfo) { 150 const errInfo: LogData = LogDataFactory.newInstance( 151 ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_MODULE_INFO_META, 152 ArkTSInternalErrorDescription, 153 `Failed to get ModuleInfo properties 'meta', moduleId: ${moduleId}` 154 ); 155 this.logger.printErrorAndExit(errInfo); 156 } 157 const pkgPath = metaInfo['pkgPath']; 158 if (!pkgPath) { 159 const errInfo: LogData = LogDataFactory.newInstance( 160 ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_MODULE_INFO_META_PKG_PATH, 161 ArkTSInternalErrorDescription, 162 `Failed to get ModuleInfo properties 'meta.pkgPath', moduleId: ${moduleId}` 163 ); 164 this.logger.printErrorAndExit(errInfo); 165 } 166 167 const dependencyPkgInfo = metaInfo['dependencyPkgInfo']; 168 let middlePath = this.getIntermediateModuleId(moduleId, metaInfo).replace(pkgPath + path.sep, ''); 169 if (shouldObfuscateFileName) { 170 middlePath = mangleFilePath(middlePath); 171 } 172 return { 173 entry: { 174 name: this.projectConfig.entryPackageName, 175 version: this.projectConfig.entryModuleVersion 176 }, 177 dependency: dependencyPkgInfo ? { 178 name: dependencyPkgInfo['pkgName'], 179 version: dependencyPkgInfo['pkgVersion'] 180 } : undefined, 181 modulePath: toUnixPath(middlePath) 182 }; 183 } 184 185 public setNewSoureMaps(isNewSourceMap: boolean): void { 186 this.isNewSourceMap = isNewSourceMap; 187 } 188 189 public isNewSourceMaps(): boolean { 190 return this.isNewSourceMap; 191 } 192 193 //generate sourcemap key, notice: moduleId is absolute path 194 public genKey(moduleId: string, shouldObfuscateFileName: boolean = false): string { 195 moduleId = this.getAdaptedModuleId(moduleId); 196 197 let key: string = this.keyCache.get(moduleId); 198 if (key && !shouldObfuscateFileName) { 199 return key; 200 } 201 const pkgInfo = this.getPkgInfoByModuleId(moduleId, shouldObfuscateFileName); 202 if (pkgInfo.dependency) { 203 key = `${pkgInfo.entry.name}|${pkgInfo.dependency.name}|${pkgInfo.dependency.version}|${pkgInfo.modulePath}`; 204 } else { 205 key = `${pkgInfo.entry.name}|${pkgInfo.entry.name}|${pkgInfo.entry.version}|${pkgInfo.modulePath}`; 206 } 207 if (key && !shouldObfuscateFileName) { 208 this.keyCache.set(moduleId, key); 209 } 210 return key; 211 } 212 213 private getSourceMapSavePath(): string { 214 if (this.projectConfig.compileHar && this.projectConfig.sourceMapDir && !this.projectConfig.byteCodeHar) { 215 return path.join(this.projectConfig.sourceMapDir, SOURCEMAPS); 216 } 217 return isDebug(this.projectConfig) ? path.join(this.projectConfig.aceModuleBuild, SOURCEMAPS) : 218 path.join(this.projectConfig.cachePath, SOURCEMAPS); 219 } 220 221 public formatOrigin(key: string, val: object): string { 222 return ` "${key}": ${JSON.stringify(val, null, 2).replace(/\n/g, '\n ')}`; 223 } 224 225 public formatTemp(key: string, val: object): string { 226 return `{"key": "${key}", "val": ${JSON.stringify(val)}}`; 227 } 228 229 public writeOrigin(content: string): void { 230 if (!this.originFileEdited) { 231 if (fs.existsSync(this.sourceMapPath)) { 232 fs.unlinkSync(this.sourceMapPath); 233 } 234 const sourceMapPathDir: string = path.dirname(this.sourceMapPath); 235 if (!fs.existsSync(sourceMapPathDir)) { 236 fs.mkdirSync(sourceMapPathDir, { recursive: true }); 237 } 238 this.originFileEdited = true; 239 } 240 fs.appendFileSync(this.sourceMapPath, content, 'utf8'); 241 } 242 243 public writeTemp(content: string): void { 244 if (!this.tempFileEdited) { 245 if (fs.existsSync(this.sourceMapPathTmp)) { 246 fs.unlinkSync(this.sourceMapPathTmp); 247 } 248 const sourceMapPathTmpDir: string = path.dirname(this.sourceMapPathTmp); 249 if (!fs.existsSync(sourceMapPathTmpDir)) { 250 fs.mkdirSync(sourceMapPathTmpDir, { recursive: true }); 251 } 252 this.tempFileEdited = true; 253 } 254 fs.appendFileSync(this.sourceMapPathTmp, content, 'utf8'); 255 } 256 257 public resetFileEdited(): void { 258 this.originFileEdited = false; 259 this.tempFileEdited = false; 260 } 261 262 public convertSourceMapToCache(maps: Object): string { 263 let isFirstLine: boolean = true; 264 let cacheContent: string = ''; 265 Object.keys(maps).forEach(key => { 266 let contentTmp: string = this.formatTemp(key, maps[key]); 267 if (isFirstLine) { 268 isFirstLine = false; 269 cacheContent = contentTmp; 270 } else { 271 cacheContent += `\n${contentTmp}`; 272 } 273 }); 274 return cacheContent; 275 } 276 277 public writeModifiedSourceMapToFile(parentEvent: CompileEvent | undefined): void { 278 const eventWriteCachedSourceMaps = createAndStartEvent(parentEvent, 'write source maps'); 279 Object.keys(this.sourceMaps).forEach(key => { 280 let keyChanged: string = ''; 281 if (this.sourceMapKeyMappingForObf.has(key)) { 282 keyChanged = this.sourceMapKeyMappingForObf.get(key); 283 } else { 284 keyChanged = key; 285 } 286 this.sourceMapKeys.add(keyChanged); 287 288 let contentJson: string = this.formatOrigin(keyChanged, this.sourceMaps[key]); 289 let contentTmp: string = this.formatTemp(keyChanged, this.sourceMaps[key]); 290 if (this.isFirstAppend) { 291 this.isFirstAppend = false; 292 this.writeOrigin(`{\n${contentJson}`); 293 this.writeTemp(`${contentTmp}`); 294 } else { 295 this.writeOrigin(`,\n${contentJson}`); 296 this.writeTemp(`\n${contentTmp}`); 297 } 298 }); 299 this.sourceMaps = {}; 300 this.sourceMapKeyMappingForObf.clear(); 301 stopEvent(eventWriteCachedSourceMaps); 302 } 303 304 public checkAndWriteEnd(): void { 305 if (!this.isFirstAppend) { 306 this.writeOrigin('\n}'); 307 } 308 //no collect sourcemap 309 if (!fs.existsSync(this.sourceMapPath)) { 310 this.writeOrigin('{}'); 311 } 312 if (!fs.existsSync(this.sourceMapPathTmp)) { 313 this.writeTemp(''); 314 } 315 this.resetFileEdited(); 316 if (fs.existsSync(this.cacheSourceMapPath)) { 317 fs.unlinkSync(this.cacheSourceMapPath); 318 } 319 fs.renameSync(this.sourceMapPathTmp, this.cacheSourceMapPath); 320 } 321 322 public writeUsedAndUnmodifiedSourceMapToFile(parentEvent: CompileEvent | undefined): void { 323 const eventMergeCachedSourceMaps = createAndStartEvent(parentEvent, 'merge cached source maps'); 324 let cacheSourceMapInfo: Object = this.getCacheSourceMapInfo(); 325 if (!cacheSourceMapInfo.exist) { 326 this.checkAndWriteEnd(); 327 stopEvent(eventMergeCachedSourceMaps); 328 return; 329 } 330 331 const cacheFileContent: string = fs.readFileSync(toUnixPath(cacheSourceMapInfo.path)).toString(); 332 let lines: string[] = cacheFileContent.split(/\r?\n/); 333 if (lines.length > 0) { 334 let compileFileList: Set<string> = this.getCompileFileList(); 335 for (const line of lines) { 336 if (line.trim() === '') { 337 continue; 338 } 339 let smObj: Object = JSON.parse(line.trim()); 340 if (!compileFileList.has(smObj.key) || this.sourceMapKeys.has(smObj.key)) { 341 // skip unuse or uncompile in cache 342 continue; 343 } 344 if (this.isFirstAppend) { 345 this.isFirstAppend = false; 346 this.writeOrigin(`{\n${this.formatOrigin(smObj.key, smObj.val)}`); 347 this.writeTemp(`${this.formatTemp(smObj.key, smObj.val)}`); 348 } else { 349 this.writeOrigin(`,\n${this.formatOrigin(smObj.key, smObj.val)}`); 350 this.writeTemp(`\n${this.formatTemp(smObj.key, smObj.val)}`); 351 } 352 } 353 } 354 this.checkAndWriteEnd(); 355 stopEvent(eventMergeCachedSourceMaps); 356 } 357 358 public buildModuleSourceMapInfoSingle(parentEvent: CompileEvent | undefined): void { 359 if (this.projectConfig.widgetCompile) { 360 return; 361 } 362 if (!this.isCompileSingle) { 363 return; 364 } 365 if (Object.keys(this.sourceMaps).length === 0) { 366 return; 367 } 368 this.writeModifiedSourceMapToFile(parentEvent); 369 } 370 371 public buildModuleSourceMapInfo(parentEvent: CompileEvent | undefined): void { 372 if (this.projectConfig.widgetCompile) { 373 return; 374 } 375 376 const eventUpdateCachedSourceMaps = createAndStartEvent(parentEvent, 'update cached source maps'); 377 // If hap/hsp depends on bytecode har under debug mode, the source map of bytecode har need to be merged with 378 // source map of hap/hsp. 379 if (isDebug(this.projectConfig) && !this.projectConfig.byteCodeHar && !!this.projectConfig.byteCodeHarInfo) { 380 Object.keys(this.projectConfig.byteCodeHarInfo).forEach((packageName) => { 381 const sourceMapsPath = this.projectConfig.byteCodeHarInfo[packageName].sourceMapsPath; 382 if (!sourceMapsPath && !!this.logger && !!this.logger.warn) { 383 this.logger.warn(yellow, `ArkTS:WARN Property 'sourceMapsPath' not found in '${packageName}'.`, reset); 384 } 385 if (!!sourceMapsPath) { 386 const bytecodeHarSourceMap = JSON.parse(fs.readFileSync(toUnixPath(sourceMapsPath)).toString()); 387 Object.assign(this.sourceMaps, bytecodeHarSourceMap); 388 } 389 }); 390 } 391 392 if (this.isNewSourceMap) { 393 this.writeModifiedSourceMapToFile(parentEvent); 394 this.writeUsedAndUnmodifiedSourceMapToFile(parentEvent); 395 this.checkSourceMapFormat(); 396 return; 397 } 398 399 const updateSourceRecordInfo = MemoryMonitor.recordStage(MemoryDefine.UPDATE_SOURCE_MAPS); 400 const cacheSourceMapObject: Object = this.updateCachedSourceMaps(); 401 MemoryMonitor.stopRecordStage(updateSourceRecordInfo); 402 stopEvent(eventUpdateCachedSourceMaps); 403 404 this.triggerAsync(() => { 405 const eventWriteFile = createAndStartEvent(parentEvent, 'write source map (async)', true); 406 fs.writeFile(this.sourceMapPath, JSON.stringify(cacheSourceMapObject, null, 2), 'utf-8', (err) => { 407 if (err) { 408 const errInfo: LogData = LogDataFactory.newInstance( 409 ErrorCode.ETS2BUNDLE_INTERNAL_WRITE_SOURCE_MAP_FAILED, 410 ArkTSInternalErrorDescription, 411 `Failed to write sourceMaps. ${err.message}`, 412 this.sourceMapPath 413 ); 414 this.logger.printErrorAndExit(errInfo); 415 } 416 fs.copyFileSync(this.sourceMapPath, this.cacheSourceMapPath); 417 stopEvent(eventWriteFile, true); 418 this.triggerEndSignal(); 419 }); 420 }); 421 } 422 423 public isEmptyFile(filePath: string): boolean { 424 const stats = fs.statSync(filePath); 425 return stats.size === 0; 426 } 427 428 public getCacheSourceMapInfo(): Object { 429 let existCacheSourceMap: boolean = false; 430 let cacheSourceMapPath: string = ''; 431 let cacheSourceMapPathTmp: string = ''; 432 /** 433 * bytecode obfuscation requires that the input sourceMap must be unobfuscated, 434 * the sourceMap will be saved in the cache directory before the first bytecode obfuscation, 435 * and it will as the input for merging sourceMap during incremental compilation. 436 */ 437 if (BytecodeObfuscator.enable) { 438 cacheSourceMapPathTmp = BytecodeObfuscator.getInstance().getBackupSourceMapPath(); 439 if (fs.existsSync(cacheSourceMapPathTmp) && !this.isEmptyFile(cacheSourceMapPathTmp)) { 440 existCacheSourceMap = true; 441 cacheSourceMapPath = cacheSourceMapPathTmp; 442 } 443 } 444 445 cacheSourceMapPathTmp = this.cacheSourceMapPath; 446 if (!existCacheSourceMap && fs.existsSync(cacheSourceMapPathTmp) && !this.isEmptyFile(cacheSourceMapPathTmp)) { 447 existCacheSourceMap = true; 448 cacheSourceMapPath = cacheSourceMapPathTmp; 449 } 450 451 return { exist: existCacheSourceMap, path: cacheSourceMapPath }; 452 } 453 454 //update cache sourcemap object 455 public updateCachedSourceMaps(): Object { 456 if (!this.isNewSourceMap) { 457 this.modifySourceMapKeyToCachePath(this.sourceMaps); 458 } 459 460 let cacheSourceMapObject: Object = null; 461 let cacheSourceMapInfo: Object = this.getCacheSourceMapInfo(); 462 if (!cacheSourceMapInfo.exist) { 463 cacheSourceMapObject = this.sourceMaps; 464 } else { 465 cacheSourceMapObject = JSON.parse(fs.readFileSync(cacheSourceMapInfo.path).toString().trim()); 466 // remove unused source files's sourceMap 467 let unusedFiles = []; 468 let compileFileList: Set<string> = this.getCompileFileList(); 469 470 Object.keys(cacheSourceMapObject).forEach(key => { 471 let newkeyOrOldCachePath = key; 472 if (!this.isNewSourceMap) { 473 newkeyOrOldCachePath = toUnixPath(path.join(this.projectConfig.projectRootPath, key)); 474 } 475 if (!compileFileList.has(newkeyOrOldCachePath)) { 476 unusedFiles.push(key); 477 } 478 }); 479 unusedFiles.forEach(file => { 480 delete cacheSourceMapObject[file]; 481 }) 482 483 // update sourceMap 484 Object.keys(this.sourceMaps).forEach(key => { 485 cacheSourceMapObject[key] = this.sourceMaps[key]; 486 }); 487 } 488 // update the key for filename obfuscation 489 for (let [key, newKey] of this.sourceMapKeyMappingForObf) { 490 this.updateSourceMapKeyWithObf(cacheSourceMapObject, key, newKey); 491 } 492 return cacheSourceMapObject; 493 } 494 495 public getCompileFileList(): Set<string> { 496 let compileFileList: Set<string> = new Set(); 497 for (let moduleId of SourceMapGenerator.rollupObject.getModuleIds()) { 498 // exclude .dts|.d.ets file 499 if (isCommonJsPluginVirtualFile(moduleId) || !isCurrentProjectFiles(moduleId, this.projectConfig)) { 500 continue; 501 } 502 503 if (this.isNewSourceMap) { 504 const isPackageModules = isPackageModulesFile(moduleId, this.projectConfig); 505 if (enableObfuscateFileName(isPackageModules, this.projectConfig)) { 506 compileFileList.add(this.genKey(moduleId, true)); 507 } else { 508 compileFileList.add(this.genKey(moduleId)); 509 } 510 continue; 511 } 512 513 // adapt compatibilty with hvigor 514 const projectRootPath = getProjectRootPath(moduleId, this.projectConfig, this.projectConfig?.rootPathSet); 515 let cacheModuleId = this.getIntermediateModuleId(toUnixPath(moduleId)) 516 .replace(toUnixPath(projectRootPath), toUnixPath(this.projectConfig.cachePath)); 517 518 const isPackageModules = isPackageModulesFile(moduleId, this.projectConfig); 519 if (enableObfuscateFileName(isPackageModules, this.projectConfig)) { 520 compileFileList.add(mangleFilePath(cacheModuleId)); 521 } else { 522 compileFileList.add(cacheModuleId); 523 } 524 } 525 return compileFileList; 526 } 527 528 public getSourceMaps(): Object { 529 return this.sourceMaps; 530 } 531 532 public getSourceMap(moduleId: string): Object { 533 return this.getSpecifySourceMap(this.sourceMaps, moduleId); 534 } 535 536 //get specify sourcemap, allow receive param sourcemap 537 public getSpecifySourceMap(specifySourceMap: Object, moduleId: string): Object { 538 const key = this.isNewSourceMap ? this.genKey(moduleId) : moduleId; 539 if (specifySourceMap && specifySourceMap[key]) { 540 return specifySourceMap[key]; 541 } 542 return undefined; 543 } 544 545 public updateSourceMap(moduleId: string, map: Object) { 546 if (!this.sourceMaps) { 547 this.sourceMaps = {}; 548 } 549 this.updateSpecifySourceMap(this.sourceMaps, moduleId, map); 550 } 551 552 //update specify sourcemap, allow receive param sourcemap 553 public updateSpecifySourceMap(specifySourceMap: Object, moduleId: string, sourceMap: Object) { 554 const key = this.isNewSourceMap ? this.genKey(moduleId) : moduleId; 555 specifySourceMap[key] = sourceMap; 556 } 557 558 public fillSourceMapPackageInfo(moduleId: string, sourcemap: Object) { 559 if (!this.isNewSourceMap) { 560 return; 561 } 562 563 const pkgInfo = this.getPkgInfoByModuleId(moduleId); 564 sourcemap['entry-package-info'] = `${pkgInfo.entry.name}|${pkgInfo.entry.version}`; 565 if (pkgInfo.dependency) { 566 sourcemap['package-info'] = `${pkgInfo.dependency.name}|${pkgInfo.dependency.version}`; 567 } 568 } 569 570 private getIntermediateModuleId(moduleId: string, metaInfo?: Object): string { 571 let extName: string = ""; 572 switch (path.extname(moduleId)) { 573 case EXTNAME_ETS: { 574 extName = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig, metaInfo) ? EXTNAME_JS : EXTNAME_TS; 575 break; 576 } 577 case EXTNAME_TS: { 578 extName = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig, metaInfo) ? EXTNAME_JS : ''; 579 break; 580 } 581 case EXTNAME_JS: 582 case EXTNAME_MJS: 583 case EXTNAME_CJS: { 584 extName = (moduleId.endsWith(EXTNAME_MJS) || moduleId.endsWith(EXTNAME_CJS)) ? EXTNAME_JS : ''; 585 break; 586 } 587 default: 588 break; 589 } 590 if (extName.length !== 0) { 591 return changeFileExtension(moduleId, extName); 592 } 593 return moduleId; 594 } 595 596 public setSourceMapPath(path: string): void { 597 this.sourceMapPath = path; 598 } 599 600 public modifySourceMapKeyToCachePath(sourceMap: object): void { 601 const projectConfig: object = this.projectConfig; 602 603 // modify source map keys to keep IDE tools right 604 const relativeCachePath: string = toUnixPath(projectConfig.cachePath.replace( 605 projectConfig.projectRootPath + path.sep, '')); 606 Object.keys(sourceMap).forEach(key => { 607 let newKey: string = relativeCachePath + '/' + key; 608 if (!newKey.endsWith(EXTNAME_JS)) { 609 const moduleId: string = this.projectConfig.projectRootPath + path.sep + key; 610 const extName: string = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig) ? EXTNAME_JS : EXTNAME_TS; 611 newKey = changeFileExtension(newKey, extName); 612 } 613 const isOhModules = key.startsWith('oh_modules'); 614 newKey = handleObfuscatedFilePath(newKey, isOhModules, this.projectConfig); 615 sourceMap[newKey] = sourceMap[key]; 616 delete sourceMap[key]; 617 }); 618 } 619 620 public static cleanSourceMapObject(): void { 621 if (this.instance) { 622 this.instance.resetFileEdited(); 623 this.instance.keyCache.clear(); 624 this.instance.sourceMaps = undefined; 625 this.instance = undefined; 626 } 627 if (this.rollupObject) { 628 this.rollupObject = undefined; 629 } 630 } 631 632 private updateSourceMapKeyWithObf(specifySourceMap: Object, key: string, newKey: string): void { 633 if (!specifySourceMap.hasOwnProperty(key) || key === newKey) { 634 return; 635 } 636 specifySourceMap[newKey] = specifySourceMap[key]; 637 delete specifySourceMap[key]; 638 } 639 640 public saveKeyMappingForObfFileName(originalFilePath: string): void { 641 this.sourceMapKeyMappingForObf.set(this.genKey(originalFilePath), this.genKey(originalFilePath, true)); 642 } 643 644 //use by UT 645 static initInstance(rollupObject: Object): SourceMapGenerator { 646 if (!SourceMapGenerator.instance) { 647 SourceMapGenerator.init(rollupObject); 648 } 649 return SourceMapGenerator.getInstance(); 650 } 651 652 public checkSourceMapFormat(): void { 653 if (!fs.existsSync(this.sourceMapPath)) { 654 const errInfo: LogData = LogDataFactory.newInstance( 655 ErrorCode.ETS2BUNDLE_INTERNAL_CHECK_SOURCEMAP_FORMAT_FAILED, 656 ArkTSInternalErrorDescription, 657 `SourceMap file not exist, path: ${this.sourceMapPath}` 658 ); 659 this.logger.printErrorAndExit(errInfo); 660 return; 661 } 662 663 try { 664 const content: string = fs.readFileSync(toUnixPath(this.sourceMapPath)).toString(); 665 JSON.parse(content); 666 } catch (e) { 667 const errInfo: LogData = LogDataFactory.newInstance( 668 ErrorCode.ETS2BUNDLE_INTERNAL_CHECK_SOURCEMAP_FORMAT_FAILED, 669 ArkTSInternalErrorDescription, 670 `SourceMap content format error, path: ${this.sourceMapPath}` 671 ); 672 this.logger.printErrorAndExit(errInfo); 673 } 674 } 675}