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