1/* 2 * Copyright (c) 2023 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 * as ts from 'typescript'; 17import fs from 'fs'; 18import path from 'path'; 19import MagicString from 'magic-string'; 20import { 21 GEN_ABC_PLUGIN_NAME, 22 PACKAGES, 23 red, 24 reset 25} from '../common/ark_define'; 26import { 27 getNormalizedOhmUrlByFilepath, 28 getOhmUrlByByteCodeHar, 29 getOhmUrlByFilepath, 30 getOhmUrlByExternalPackage, 31 getOhmUrlBySystemApiOrLibRequest 32} from '../../../ark_utils'; 33import { writeFileSyncByNode } from '../../../process_module_files'; 34import { 35 isJsonSourceFile, 36 isJsSourceFile, 37 updateSourceMap, 38 writeFileContentToTempDir 39} from '../utils'; 40import { toUnixPath } from '../../../utils'; 41import { 42 createAndStartEvent, 43 getHookEventFactory, 44 stopEvent 45} from '../../../ark_utils'; 46import { SourceMapGenerator } from '../generate_sourcemap'; 47import { 48 printObfLogger, 49 collectSourcesWhiteList, 50 handlePostObfuscationTasks, 51 writeObfuscationCaches 52} from '../common/ob_config_resolver'; 53import { ORIGIN_EXTENTION } from '../process_mock'; 54import { 55 TRANSFORMED_MOCK_CONFIG, 56 USER_DEFINE_MOCK_CONFIG 57} from '../../../pre_define'; 58import { 59 allSourceFilePaths, 60 compilerOptions, 61 localPackageSet 62} from '../../../ets_checker'; 63import { projectConfig } from '../../../../main'; 64import { 65 EventList, 66 startFilesEvent 67} from 'arkguard'; 68import { MemoryMonitor } from '../../meomry_monitor/rollup-plugin-memory-monitor'; 69import { MemoryDefine } from '../../meomry_monitor/memory_define'; 70import { 71 CommonLogger, 72 LogData, 73 LogDataFactory 74} from '../logger'; 75import { 76 ArkTSInternalErrorDescription, 77 ErrorCode 78} from '../error_code'; 79import { checkIfJsImportingArkts } from '../check_import_module'; 80const ROLLUP_IMPORT_NODE: string = 'ImportDeclaration'; 81const ROLLUP_EXPORTNAME_NODE: string = 'ExportNamedDeclaration'; 82const ROLLUP_EXPORTALL_NODE: string = 'ExportAllDeclaration'; 83const ROLLUP_DYNAMICIMPORT_NODE: string = 'ImportExpression'; 84const ROLLUP_LITERAL_NODE: string = 'Literal'; 85export const sourceFileBelongProject = new Map<string, string>(); 86 87export class ModuleSourceFile { 88 private static sourceFiles: ModuleSourceFile[] = []; 89 private moduleId: string; 90 private source: string | ts.SourceFile; 91 private metaInfo: Object; 92 private isSourceNode: boolean = false; 93 private static projectConfig: Object; 94 private static logger: CommonLogger; 95 private static mockConfigInfo: Object = {}; 96 private static mockFiles: string[] = []; 97 private static newMockConfigInfo: Object = {}; 98 private static transformedHarOrHspMockConfigInfo: Object = {}; 99 private static mockConfigKeyToModuleInfo: Object = {}; 100 private static needProcessMock: boolean = false; 101 private static moduleIdMap: Map<string, ModuleSourceFile> = new Map(); 102 private static isEnvInitialized: boolean = false; 103 private static hookEventFactory: Object; 104 105 constructor(moduleId: string, source: string | ts.SourceFile, metaInfo: Object) { 106 this.moduleId = moduleId; 107 this.source = source; 108 this.metaInfo = metaInfo; 109 if (typeof this.source !== 'string') { 110 this.isSourceNode = true; 111 } 112 } 113 114 static setProcessMock(rollupObject: Object): void { 115 // only processing mock-config.json5 in preview, OhosTest, or LocalTest mode 116 if (!(rollupObject.share.projectConfig.isPreview || rollupObject.share.projectConfig.isOhosTest || rollupObject.share.projectConfig.isLocalTest)) { 117 ModuleSourceFile.needProcessMock = false; 118 return; 119 } 120 121 // mockParams is essential, and etsSourceRootPath && mockConfigPath need to be defined in mockParams 122 // mockParams = { 123 // "decorator": "name of mock decorator", 124 // "packageName": "name of mock package", 125 // "etsSourceRootPath": "path of ets source root", 126 // "mockConfigPath": "path of mock configuration file" 127 // "mockConfigKey2ModuleInfo": "moduleInfo of mock-config key" 128 // } 129 ModuleSourceFile.needProcessMock = (rollupObject.share.projectConfig.mockParams && 130 rollupObject.share.projectConfig.mockParams.etsSourceRootPath && 131 rollupObject.share.projectConfig.mockParams.mockConfigPath) ? true : false; 132 } 133 134 static collectMockConfigInfo(rollupObject: Object): void { 135 if (!!rollupObject.share.projectConfig.mockParams.mockConfigKey2ModuleInfo) { 136 ModuleSourceFile.mockConfigKeyToModuleInfo = rollupObject.share.projectConfig.mockParams.mockConfigKey2ModuleInfo; 137 } 138 ModuleSourceFile.mockConfigInfo = require('json5').parse( 139 fs.readFileSync(rollupObject.share.projectConfig.mockParams.mockConfigPath, 'utf-8')); 140 for (let mockedTarget in ModuleSourceFile.mockConfigInfo) { 141 if (ModuleSourceFile.mockConfigInfo[mockedTarget].source) { 142 ModuleSourceFile.mockFiles.push(ModuleSourceFile.mockConfigInfo[mockedTarget].source); 143 if (ModuleSourceFile.mockConfigKeyToModuleInfo && ModuleSourceFile.mockConfigKeyToModuleInfo[mockedTarget]) { 144 ModuleSourceFile.generateTransformedMockInfo(ModuleSourceFile.mockConfigKeyToModuleInfo[mockedTarget], 145 ModuleSourceFile.mockConfigInfo[mockedTarget].source, mockedTarget, rollupObject); 146 } 147 } 148 } 149 } 150 151 static addMockConfig(mockConfigInfo: Object, key: string, src: string): void { 152 if (Object.prototype.hasOwnProperty.call(mockConfigInfo, key)) { 153 return; 154 } 155 156 mockConfigInfo[key] = {'source': src}; 157 } 158 159 static generateTransformedMockInfo(mockModuleInfo: Object, src: string, originKey: string, rollupObject: Object): void { 160 let useNormalizedOHMUrl: boolean = false; 161 if (!!rollupObject.share.projectConfig.useNormalizedOHMUrl) { 162 useNormalizedOHMUrl = rollupObject.share.projectConfig.useNormalizedOHMUrl; 163 } 164 let transformedMockTarget: string | undefined = getOhmUrlByExternalPackage(originKey, ModuleSourceFile.projectConfig, 165 ModuleSourceFile.logger, useNormalizedOHMUrl); 166 if (transformedMockTarget !== undefined) { 167 ModuleSourceFile.addMockConfig(ModuleSourceFile.transformedHarOrHspMockConfigInfo, transformedMockTarget, src); 168 return; 169 } 170 if (mockModuleInfo.filePath) { 171 if (useNormalizedOHMUrl) { 172 transformedMockTarget = ModuleSourceFile.spliceNormalizedOhmurl(mockModuleInfo, mockModuleInfo.filePath, undefined); 173 ModuleSourceFile.addMockConfig(ModuleSourceFile.transformedHarOrHspMockConfigInfo, transformedMockTarget, src); 174 return; 175 } 176 transformedMockTarget = getOhmUrlByFilepath(mockModuleInfo.filePath, ModuleSourceFile.projectConfig, 177 ModuleSourceFile.logger, originKey); 178 transformedMockTarget = transformedMockTarget.startsWith(PACKAGES) ? `@package:${transformedMockTarget}` : 179 `@bundle:${transformedMockTarget}`; 180 ModuleSourceFile.addMockConfig(ModuleSourceFile.transformedHarOrHspMockConfigInfo, transformedMockTarget, src); 181 return; 182 } else { 183 const errInfo: LogData = LogDataFactory.newInstance( 184 ErrorCode.ETS2BUNDLE_INTERNAL_MOCK_CONFIG_KEY_TO_OHM_URL_CONVERSION_FAILED, 185 ArkTSInternalErrorDescription, 186 'Failed to convert the key in mock-config to ohmurl, ' + 187 'because the file path corresponding to the key in mock-config is empty.' 188 ); 189 ModuleSourceFile.logger.printError(errInfo); 190 } 191 } 192 193 static generateNewMockInfo(originKey: string, transKey: string, rollupObject: Object, importerFile?: string): void { 194 if (!Object.prototype.hasOwnProperty.call(ModuleSourceFile.transformedHarOrHspMockConfigInfo, transKey) && 195 !Object.prototype.hasOwnProperty.call(ModuleSourceFile.mockConfigInfo, originKey)) { 196 return; 197 } 198 199 let useNormalizedOHMUrl = false; 200 if (!!rollupObject.share.projectConfig.useNormalizedOHMUrl) { 201 useNormalizedOHMUrl = rollupObject.share.projectConfig.useNormalizedOHMUrl; 202 } 203 let mockFile: string = ModuleSourceFile.transformedHarOrHspMockConfigInfo[transKey] ? 204 ModuleSourceFile.transformedHarOrHspMockConfigInfo[transKey].source : 205 ModuleSourceFile.mockConfigInfo[originKey].source; 206 let mockFilePath: string = `${toUnixPath(rollupObject.share.projectConfig.modulePath)}/${mockFile}`; 207 let mockFileOhmUrl: string = ''; 208 if (useNormalizedOHMUrl) { 209 // For file A that imports file B, the mock file of file B will be located in the same package of file A. So the 210 // moduleInfo for mock file should be the same with file A. 211 const targetModuleInfo: Object = rollupObject.getModuleInfo(importerFile); 212 mockFileOhmUrl = ModuleSourceFile.spliceNormalizedOhmurl(targetModuleInfo, mockFilePath, importerFile); 213 } else { 214 mockFileOhmUrl = getOhmUrlByFilepath(mockFilePath, 215 ModuleSourceFile.projectConfig, 216 ModuleSourceFile.logger, 217 rollupObject.share.projectConfig.entryModuleName, 218 importerFile); 219 mockFileOhmUrl = mockFileOhmUrl.startsWith(PACKAGES) ? `@package:${mockFileOhmUrl}` : `@bundle:${mockFileOhmUrl}`; 220 } 221 222 // record mock target mapping for incremental compilation 223 ModuleSourceFile.addMockConfig(ModuleSourceFile.newMockConfigInfo, transKey, mockFileOhmUrl); 224 } 225 226 static isMockFile(file: string, rollupObject: Object): boolean { 227 if (!ModuleSourceFile.needProcessMock) { 228 return false; 229 } 230 231 for (let mockFile of ModuleSourceFile.mockFiles) { 232 let absoluteMockFilePath: string = `${toUnixPath(rollupObject.share.projectConfig.modulePath)}/${mockFile}`; 233 if (toUnixPath(absoluteMockFilePath) === toUnixPath(file)) { 234 return true; 235 } 236 } 237 238 return false; 239 } 240 241 static generateMockConfigFile(rollupObject: Object): void { 242 let transformedMockConfigCache: string = 243 path.resolve(rollupObject.share.projectConfig.cachePath, `./${TRANSFORMED_MOCK_CONFIG}`); 244 let transformedMockConfig: string = 245 path.resolve(rollupObject.share.projectConfig.aceModuleJsonPath, `../${TRANSFORMED_MOCK_CONFIG}`); 246 let userDefinedMockConfigCache: string = 247 path.resolve(rollupObject.share.projectConfig.cachePath, `./${USER_DEFINE_MOCK_CONFIG}`); 248 // full compilation 249 if (!fs.existsSync(transformedMockConfigCache) || !fs.existsSync(userDefinedMockConfigCache)) { 250 fs.writeFileSync(transformedMockConfig, JSON.stringify(ModuleSourceFile.newMockConfigInfo)); 251 fs.copyFileSync(transformedMockConfig, transformedMockConfigCache); 252 fs.copyFileSync(rollupObject.share.projectConfig.mockParams.mockConfigPath, userDefinedMockConfigCache); 253 return; 254 } 255 256 // incremental compilation 257 const cachedMockConfigInfo: Object = 258 require('json5').parse(fs.readFileSync(userDefinedMockConfigCache, 'utf-8')); 259 // If mock-config.json5 is modified, incremental compilation will be disabled 260 if (JSON.stringify(ModuleSourceFile.mockConfigInfo) !== JSON.stringify(cachedMockConfigInfo)) { 261 fs.writeFileSync(transformedMockConfig, JSON.stringify(ModuleSourceFile.newMockConfigInfo)); 262 fs.copyFileSync(transformedMockConfig, transformedMockConfigCache); 263 fs.copyFileSync(rollupObject.share.projectConfig.mockParams.mockConfigPath, userDefinedMockConfigCache); 264 return; 265 } 266 // During incremental compilation, only at this point is the mocked file imported. 267 // At this time, the newMockConfigInfo does not match the mockConfig in the cache, 268 // so the mockConfig in the cache needs to be updated. 269 const cachedTransformedMockConfigInfo: Object = 270 require('json5').parse(fs.readFileSync(transformedMockConfigCache, 'utf-8')); 271 if (JSON.stringify(ModuleSourceFile.newMockConfigInfo) !== JSON.stringify(cachedTransformedMockConfigInfo)) { 272 ModuleSourceFile.updataCachedTransformedMockConfigInfo(ModuleSourceFile.newMockConfigInfo, cachedTransformedMockConfigInfo, 273 transformedMockConfigCache, transformedMockConfig); 274 return; 275 } 276 277 // if mock-config.json5 is not modified, use the cached mock config mapping file 278 fs.copyFileSync(transformedMockConfigCache, transformedMockConfig); 279 } 280 281 static updataCachedTransformedMockConfigInfo(newMockConfig: Object, cachedTransMockConfigInfo: Object, 282 transMockConfigCachePath: string, transMockConfigPath: string): void { 283 for (const key in newMockConfig) { 284 if (!Object.prototype.hasOwnProperty.call(cachedTransMockConfigInfo, key)) { 285 cachedTransMockConfigInfo[key] = newMockConfig[key]; 286 } 287 } 288 fs.writeFileSync(transMockConfigPath, JSON.stringify(cachedTransMockConfigInfo)); 289 fs.copyFileSync(transMockConfigPath, transMockConfigCachePath); 290 } 291 292 static removePotentialMockConfigCache(rollupObject: Object): void { 293 const transformedMockConfigCache: string = 294 path.resolve(rollupObject.share.projectConfig.cachePath, `./${TRANSFORMED_MOCK_CONFIG}`); 295 const userDefinedMockConfigCache: string = 296 path.resolve(rollupObject.share.projectConfig.cachePath, `./${USER_DEFINE_MOCK_CONFIG}`); 297 if (fs.existsSync(transformedMockConfigCache)) { 298 fs.rmSync(transformedMockConfigCache); 299 } 300 301 if (fs.existsSync(userDefinedMockConfigCache)) { 302 fs.rmSync(userDefinedMockConfigCache); 303 } 304 } 305 306 static newSourceFile(moduleId: string, source: string | ts.SourceFile, metaInfo: Object, singleFileEmit: boolean): void { 307 if (singleFileEmit) { 308 ModuleSourceFile.moduleIdMap.set(moduleId, new ModuleSourceFile(moduleId, source, metaInfo)); 309 } else { 310 ModuleSourceFile.sourceFiles.push(new ModuleSourceFile(moduleId, source, metaInfo)); 311 } 312 } 313 314 static getSourceFileById(moduleId: string): ModuleSourceFile | undefined { 315 return ModuleSourceFile.moduleIdMap.get(moduleId); 316 } 317 318 static getSourceFiles(): ModuleSourceFile[] { 319 return ModuleSourceFile.sourceFiles; 320 } 321 322 static async processSingleModuleSourceFile(rollupObject: Object, moduleId: string): Promise<void> { 323 if (!ModuleSourceFile.isEnvInitialized) { 324 this.initPluginEnv(rollupObject); 325 326 ModuleSourceFile.hookEventFactory = getHookEventFactory(rollupObject.share, 'genAbc', 'moduleParsed'); 327 ModuleSourceFile.setProcessMock(rollupObject); 328 if (ModuleSourceFile.needProcessMock) { 329 ModuleSourceFile.collectMockConfigInfo(rollupObject); 330 } else { 331 ModuleSourceFile.removePotentialMockConfigCache(rollupObject); 332 } 333 ModuleSourceFile.isEnvInitialized = true; 334 } 335 336 if (ModuleSourceFile.moduleIdMap.has(moduleId)) { 337 let moduleSourceFile = ModuleSourceFile.moduleIdMap.get(moduleId); 338 ModuleSourceFile.moduleIdMap.delete(moduleId); 339 340 if (compilerOptions.needDoArkTsLinter) { 341 checkIfJsImportingArkts(rollupObject, moduleSourceFile); 342 } 343 344 if (!rollupObject.share.projectConfig.compileHar || ModuleSourceFile.projectConfig.byteCodeHar) { 345 //compileHar: compile closed source har of project, which convert .ets to .d.ts and js, doesn't transform module request. 346 const eventBuildModuleSourceFile = createAndStartEvent(ModuleSourceFile.hookEventFactory, 'build module source files'); 347 await moduleSourceFile.processModuleRequest(rollupObject, eventBuildModuleSourceFile); 348 stopEvent(eventBuildModuleSourceFile); 349 } 350 const eventWriteSourceFile = createAndStartEvent(ModuleSourceFile.hookEventFactory, 'write source file'); 351 await moduleSourceFile.writeSourceFile(eventWriteSourceFile); 352 stopEvent(eventWriteSourceFile); 353 } 354 } 355 356 static async processModuleSourceFiles(rollupObject: Object, parentEvent: Object): Promise<void> { 357 this.initPluginEnv(rollupObject); 358 359 // collect mockConfigInfo 360 ModuleSourceFile.setProcessMock(rollupObject); 361 if (ModuleSourceFile.needProcessMock) { 362 ModuleSourceFile.collectMockConfigInfo(rollupObject); 363 } else { 364 ModuleSourceFile.removePotentialMockConfigCache(rollupObject); 365 } 366 367 const sourceProjectConfig = ModuleSourceFile.projectConfig; 368 collectSourcesWhiteList(rollupObject, allSourceFilePaths, sourceProjectConfig, ModuleSourceFile.sourceFiles); 369 370 startFilesEvent(EventList.ALL_FILES_OBFUSCATION); 371 let byteCodeHar = false; 372 if (Object.prototype.hasOwnProperty.call(sourceProjectConfig, 'byteCodeHar')) { 373 byteCodeHar = sourceProjectConfig.byteCodeHar; 374 } 375 const allFilesObfuscationRecordInfo = MemoryMonitor.recordStage(MemoryDefine.ALL_FILES_OBFUSCATION); 376 // Sort the collection by file name to ensure binary consistency. 377 ModuleSourceFile.sortSourceFilesByModuleId(); 378 sourceProjectConfig.localPackageSet = localPackageSet; 379 for (const source of ModuleSourceFile.sourceFiles) { 380 const filesForEach = MemoryMonitor.recordStage(MemoryDefine.FILES_FOR_EACH); 381 sourceFileBelongProject.set(toUnixPath(source.moduleId), source.metaInfo?.belongProjectPath); 382 if (!rollupObject.share.projectConfig.compileHar || byteCodeHar) { 383 // compileHar: compile closed source har of project, which convert .ets to .d.ts and js, doesn't transform module request. 384 const eventBuildModuleSourceFile = createAndStartEvent(parentEvent, 'build module source files'); 385 await source.processModuleRequest(rollupObject, eventBuildModuleSourceFile); 386 stopEvent(eventBuildModuleSourceFile); 387 } 388 const eventWriteSourceFile = createAndStartEvent(parentEvent, 'write source file'); 389 await source.writeSourceFile(eventWriteSourceFile); 390 stopEvent(eventWriteSourceFile); 391 MemoryMonitor.stopRecordStage(filesForEach); 392 } 393 394 await handlePostObfuscationTasks(sourceProjectConfig, ModuleSourceFile.projectConfig, rollupObject, printObfLogger); 395 MemoryMonitor.stopRecordStage(allFilesObfuscationRecordInfo); 396 397 writeObfuscationCaches(sourceProjectConfig, parentEvent); 398 399 const eventGenerateMockConfigFile = createAndStartEvent(parentEvent, 'generate mock config file'); 400 if (ModuleSourceFile.needProcessMock) { 401 ModuleSourceFile.generateMockConfigFile(rollupObject); 402 } 403 stopEvent(eventGenerateMockConfigFile); 404 405 ModuleSourceFile.sourceFiles = []; 406 } 407 408 getModuleId(): string { 409 return this.moduleId; 410 } 411 412 private async writeSourceFile(parentEvent: Object): Promise<void> { 413 if (this.isSourceNode && !isJsSourceFile(this.moduleId)) { 414 await writeFileSyncByNode(<ts.SourceFile> this.source, ModuleSourceFile.projectConfig, this.metaInfo, 415 this.moduleId, parentEvent, printObfLogger); 416 } else { 417 await writeFileContentToTempDir(this.moduleId, <string> this.source, ModuleSourceFile.projectConfig, 418 printObfLogger, parentEvent, this.metaInfo); 419 } 420 } 421 422 private getOhmUrl(rollupObject: Object, moduleRequest: string, filePath: string | undefined, 423 importerFile?: string): string | undefined { 424 let useNormalizedOHMUrl = false; 425 if (!!rollupObject.share.projectConfig.useNormalizedOHMUrl) { 426 useNormalizedOHMUrl = rollupObject.share.projectConfig.useNormalizedOHMUrl; 427 } 428 let systemOrLibOhmUrl = getOhmUrlBySystemApiOrLibRequest(moduleRequest, ModuleSourceFile.projectConfig, 429 ModuleSourceFile.logger, importerFile, useNormalizedOHMUrl); 430 if (systemOrLibOhmUrl !== undefined) { 431 if (ModuleSourceFile.needProcessMock) { 432 ModuleSourceFile.generateNewMockInfo(moduleRequest, systemOrLibOhmUrl, rollupObject, importerFile); 433 } 434 return systemOrLibOhmUrl; 435 } 436 const externalPkgOhmurl: string | undefined = getOhmUrlByExternalPackage(moduleRequest, 437 ModuleSourceFile.projectConfig, ModuleSourceFile.logger, useNormalizedOHMUrl); 438 if (externalPkgOhmurl !== undefined) { 439 if (ModuleSourceFile.needProcessMock) { 440 ModuleSourceFile.generateNewMockInfo(moduleRequest, externalPkgOhmurl, rollupObject, importerFile); 441 } 442 return externalPkgOhmurl; 443 } 444 const byteCodeHarOhmurl: string | undefined = getOhmUrlByByteCodeHar(moduleRequest, ModuleSourceFile.projectConfig, 445 ModuleSourceFile.logger); 446 if (byteCodeHarOhmurl !== undefined) { 447 if (ModuleSourceFile.needProcessMock) { 448 ModuleSourceFile.generateNewMockInfo(moduleRequest, byteCodeHarOhmurl, rollupObject, importerFile); 449 } 450 return byteCodeHarOhmurl; 451 } 452 if (filePath) { 453 const targetModuleInfo: Object = rollupObject.getModuleInfo(filePath); 454 if (!targetModuleInfo) { 455 const errInfo: LogData = LogDataFactory.newInstance( 456 ErrorCode.ETS2BUNDLE_INTERNAL_GET_MODULE_INFO_FAILED, 457 ArkTSInternalErrorDescription, 458 `Failed to get ModuleInfo, moduleId: ${filePath}` 459 ); 460 ModuleSourceFile.logger.printError(errInfo); 461 return undefined; 462 } 463 if (!targetModuleInfo.meta) { 464 const errInfo: LogData = LogDataFactory.newInstance( 465 ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_MODULE_INFO_META, 466 ArkTSInternalErrorDescription, 467 `Failed to get ModuleInfo properties 'meta', moduleId: ${filePath}` 468 ); 469 ModuleSourceFile.logger.printError(errInfo); 470 return undefined; 471 } 472 let res: string = ''; 473 if (useNormalizedOHMUrl) { 474 res = ModuleSourceFile.spliceNormalizedOhmurl(targetModuleInfo, filePath, importerFile); 475 } else { 476 const moduleName: string = targetModuleInfo.meta.moduleName; 477 const ohmUrl: string = 478 getOhmUrlByFilepath(filePath, ModuleSourceFile.projectConfig, ModuleSourceFile.logger, moduleName, importerFile); 479 res = ohmUrl.startsWith(PACKAGES) ? `@package:${ohmUrl}` : `@bundle:${ohmUrl}`; 480 } 481 if (ModuleSourceFile.needProcessMock) { 482 // processing cases of har or lib mock targets 483 ModuleSourceFile.generateNewMockInfo(moduleRequest, res, rollupObject, importerFile); 484 // processing cases of user-defined mock targets 485 let mockedTarget: string = toUnixPath(filePath). 486 replace(toUnixPath(rollupObject.share.projectConfig.modulePath), ''). 487 replace(`/${rollupObject.share.projectConfig.mockParams.etsSourceRootPath}/`, ''); 488 ModuleSourceFile.generateNewMockInfo(mockedTarget, res, rollupObject, importerFile); 489 } 490 return res; 491 } 492 return undefined; 493 } 494 495 private static spliceNormalizedOhmurl(moduleInfo: Object, filePath: string, importerFile?: string): string { 496 const pkgParams = { 497 pkgName: moduleInfo.meta.pkgName, 498 pkgPath: moduleInfo.meta.pkgPath, 499 isRecordName: false 500 }; 501 const ohmUrl: string = 502 getNormalizedOhmUrlByFilepath(filePath, ModuleSourceFile.projectConfig, ModuleSourceFile.logger, pkgParams, 503 importerFile); 504 return `@normalized:${ohmUrl}`; 505 } 506 507 private processJsModuleRequest(rollupObject: Object): void { 508 const moduleInfo: Object = rollupObject.getModuleInfo(this.moduleId); 509 const importMap: Object = moduleInfo.importedIdMaps; 510 const REG_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]([^'"]+)['"]|(?:import)(?:\s*)\(['"]([^'"]+)['"]\)/g; 511 this.source = (<string> this.source).replace(REG_DEPENDENCY, (item, staticModuleRequest, dynamicModuleRequest) => { 512 const moduleRequest: string = staticModuleRequest || dynamicModuleRequest; 513 const ohmUrl: string | undefined = this.getOhmUrl(rollupObject, moduleRequest, importMap[moduleRequest], this.moduleId); 514 if (ohmUrl !== undefined) { 515 item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { 516 return quotation + ohmUrl + quotation; 517 }); 518 } 519 return item; 520 }); 521 this.processJsResourceRequest(); 522 } 523 524 private processJsResourceRequest(): void { 525 this.source = (this.source as string) 526 .replace(/\b__harDefaultBundleName__\b/gi, projectConfig.integratedHsp ? '' : projectConfig.bundleName) 527 .replace(/\b__harDefaultModuleName__\b/gi, projectConfig.moduleName) 528 .replace(/\b__harDefaultIntegratedHspType__\b/gi, projectConfig.integratedHsp ? 'true' : 'false') 529 .replace(/\b__harDefaultPagePath__\b/gi, path.relative(projectConfig.projectPath || '', this.moduleId).replace(/\\/g, '/').replace(/\.js$/, '')); 530 } 531 532 private async processTransformedJsModuleRequest(rollupObject: Object): Promise<void> { 533 const moduleInfo: Object = rollupObject.getModuleInfo(this.moduleId); 534 const importMap: Object = moduleInfo.importedIdMaps; 535 const code: MagicString = new MagicString(<string> this.source); 536 // The data collected by moduleNodeMap represents the node dataset of related types. 537 // The data is processed based on the AST collected during the transform stage. 538 const moduleNodeMap: Map<string, any> = 539 moduleInfo.getNodeByType(ROLLUP_IMPORT_NODE, ROLLUP_EXPORTNAME_NODE, ROLLUP_EXPORTALL_NODE, 540 ROLLUP_DYNAMICIMPORT_NODE); 541 542 let hasDynamicImport: boolean = false; 543 if (rollupObject.share.projectConfig.needCoverageInsert && moduleInfo.ast.program) { 544 // In coverage instrumentation scenario, 545 // ast from rollup because the data of ast and moduleNodeMap are inconsistent. 546 moduleInfo.ast.program.body.forEach((node) => { 547 if (!hasDynamicImport && node.type === ROLLUP_DYNAMICIMPORT_NODE) { 548 hasDynamicImport = true; 549 } 550 if ((node.type === ROLLUP_IMPORT_NODE || node.type === ROLLUP_EXPORTNAME_NODE || 551 node.type === ROLLUP_EXPORTALL_NODE) && node.source) { 552 const ohmUrl: string | undefined = 553 this.getOhmUrl(rollupObject, node.source.value, importMap[node.source.value], this.moduleId); 554 if (ohmUrl !== undefined) { 555 code.update(node.source.start, node.source.end, `'${ohmUrl}'`); 556 } 557 } 558 }); 559 } else { 560 for (let nodeSet of moduleNodeMap.values()) { 561 nodeSet.forEach(node => { 562 if (!hasDynamicImport && node.type === ROLLUP_DYNAMICIMPORT_NODE) { 563 hasDynamicImport = true; 564 } 565 if (node.source) { 566 if (node.source.type === ROLLUP_LITERAL_NODE) { 567 const ohmUrl: string | undefined = 568 this.getOhmUrl(rollupObject, node.source.value, importMap[node.source.value], this.moduleId); 569 if (ohmUrl !== undefined) { 570 code.update(node.source.start, node.source.end, `'${ohmUrl}'`); 571 } 572 } 573 } 574 }); 575 } 576 } 577 578 if (hasDynamicImport) { 579 // update sourceMap 580 const relativeSourceFilePath: string = this.moduleId.startsWith(ModuleSourceFile.projectConfig.projectRootPath) ? 581 toUnixPath(this.moduleId.replace(ModuleSourceFile.projectConfig.projectRootPath + path.sep, '')) : 582 toUnixPath(this.moduleId.replace(this.metaInfo.belongProjectPath, '')); 583 const updatedMap: Object = code.generateMap({ 584 source: relativeSourceFilePath, 585 file: `${path.basename(this.moduleId)}`, 586 includeContent: false, 587 hires: true 588 }); 589 const sourceMapGenerator = SourceMapGenerator.getInstance(); 590 const key = sourceMapGenerator.isNewSourceMaps() ? this.moduleId : relativeSourceFilePath; 591 const sourcemap = await updateSourceMap(sourceMapGenerator.getSourceMap(key), updatedMap); 592 sourceMapGenerator.fillSourceMapPackageInfo(this.moduleId, sourcemap); 593 sourceMapGenerator.updateSourceMap(key, sourcemap); 594 } 595 596 this.source = code.toString(); 597 } 598 599 private processTransformedTsModuleRequest(rollupObject: Object): void { 600 const moduleInfo: Object = rollupObject.getModuleInfo(this.moduleId); 601 const importMap: Object = moduleInfo.importedIdMaps; 602 let isMockFile: boolean = ModuleSourceFile.isMockFile(this.moduleId, rollupObject); 603 604 const moduleNodeTransformer: ts.TransformerFactory<ts.SourceFile> = context => { 605 const visitor: ts.Visitor = node => { 606 node = ts.visitEachChild(node, visitor, context); 607 // staticImport node 608 if (ts.isImportDeclaration(node) || (ts.isExportDeclaration(node) && node.moduleSpecifier)) { 609 // moduleSpecifier.getText() returns string carrying on quotation marks which the importMap's key does not, 610 // so we need to remove the quotation marks from moduleRequest. 611 const moduleRequest: string = (node.moduleSpecifier! as ts.StringLiteral).text.replace(/'|"/g, ''); 612 let ohmUrl: string | undefined = this.getOhmUrl(rollupObject, moduleRequest, importMap[moduleRequest], this.moduleId); 613 if (ohmUrl !== undefined) { 614 // the import module are added with ".origin" at the end of the ohm url in every mock file. 615 const realOhmUrl: string = isMockFile ? `${ohmUrl}${ORIGIN_EXTENTION}` : ohmUrl; 616 if (isMockFile) { 617 ModuleSourceFile.addMockConfig(ModuleSourceFile.newMockConfigInfo, realOhmUrl, ohmUrl); 618 } 619 const modifiers: readonly ts.Modifier[] = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; 620 if (ts.isImportDeclaration(node)) { 621 return ts.factory.createImportDeclaration(modifiers, 622 node.importClause, ts.factory.createStringLiteral(realOhmUrl)); 623 } else { 624 return ts.factory.createExportDeclaration(modifiers, 625 node.isTypeOnly, node.exportClause, ts.factory.createStringLiteral(realOhmUrl)); 626 } 627 } 628 } 629 // dynamicImport node 630 if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) { 631 const moduleRequest: string = node.arguments[0].getText().replace(/'|"/g, ''); 632 const ohmUrl: string | undefined = this.getOhmUrl(rollupObject, moduleRequest, importMap[moduleRequest], this.moduleId); 633 if (ohmUrl !== undefined) { 634 const args: ts.Expression[] = [...node.arguments]; 635 args[0] = ts.factory.createStringLiteral(ohmUrl); 636 return ts.factory.createCallExpression(node.expression, node.typeArguments, args); 637 } 638 } 639 return node; 640 }; 641 return node => ts.visitNode(node, visitor); 642 }; 643 644 const result: ts.TransformationResult<ts.SourceFile> = 645 ts.transform(<ts.SourceFile> this.source!, [moduleNodeTransformer]); 646 647 this.source = result.transformed[0]; 648 } 649 650 // Replace each module request in source file to a unique representation which is called 'ohmUrl'. 651 // This 'ohmUrl' will be the same as the record name for each file, to make sure runtime can find the corresponding 652 // record based on each module request. 653 async processModuleRequest(rollupObject: Object, parentEvent: Object): Promise<void> { 654 if (isJsonSourceFile(this.moduleId)) { 655 return; 656 } 657 if (isJsSourceFile(this.moduleId)) { 658 const eventProcessJsModuleRequest = createAndStartEvent(parentEvent, 'process Js module request'); 659 this.processJsModuleRequest(rollupObject); 660 stopEvent(eventProcessJsModuleRequest); 661 return; 662 } 663 664 665 // Only when files were transformed to ts, the corresponding ModuleSourceFile were initialized with sourceFile node, 666 // if files were transformed to js, ModuleSourceFile were initialized with srouce string. 667 if (this.isSourceNode) { 668 const eventProcessTransformedTsModuleRequest = createAndStartEvent(parentEvent, 'process transformed Ts module request'); 669 this.processTransformedTsModuleRequest(rollupObject); 670 stopEvent(eventProcessTransformedTsModuleRequest); 671 } else { 672 const eventProcessTransformedJsModuleRequest = createAndStartEvent(parentEvent, 'process transformed Js module request'); 673 await this.processTransformedJsModuleRequest(rollupObject); 674 stopEvent(eventProcessTransformedJsModuleRequest); 675 } 676 } 677 678 private static initPluginEnv(rollupObject: Object): void { 679 this.projectConfig = Object.assign(rollupObject.share.arkProjectConfig, rollupObject.share.projectConfig); 680 this.logger = CommonLogger.getInstance(rollupObject); 681 } 682 683 public static sortSourceFilesByModuleId(): void { 684 ModuleSourceFile.sourceFiles.sort((a, b) => a.moduleId.localeCompare(b.moduleId)); 685 } 686 687 public static cleanUpObjects(): void { 688 ModuleSourceFile.sourceFiles = []; 689 ModuleSourceFile.projectConfig = undefined; 690 ModuleSourceFile.logger = undefined; 691 ModuleSourceFile.mockConfigInfo = {}; 692 ModuleSourceFile.mockFiles = []; 693 ModuleSourceFile.newMockConfigInfo = {}; 694 ModuleSourceFile.transformedHarOrHspMockConfigInfo = {}; 695 ModuleSourceFile.mockConfigKeyToModuleInfo = {}; 696 ModuleSourceFile.needProcessMock = false; 697 ModuleSourceFile.moduleIdMap = new Map(); 698 ModuleSourceFile.isEnvInitialized = false; 699 ModuleSourceFile.hookEventFactory = undefined; 700 } 701} 702 703