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