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 this 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 ts from 'typescript'; 17import path from 'path'; 18import fs from 'fs'; 19import { createFilter } from '@rollup/pluginutils'; 20import MagicString from 'magic-string'; 21import nodeEvents from 'node:events'; 22 23import { 24 LogInfo, 25 componentInfo, 26 emitLogInfo, 27 getTransformLog, 28 genTemporaryPath, 29 writeFileSync, 30 getAllComponentsOrModules, 31 writeCollectionFile, 32 storedFileInfo, 33 fileInfo, 34 resourcesRawfile, 35 differenceResourcesRawfile, 36 CacheFile, 37 startTimeStatisticsLocation, 38 stopTimeStatisticsLocation, 39 CompilationTimeStatistics, 40 genLoaderOutPathOfHar, 41 harFilesRecord, 42 resetUtils, 43 getResolveModules, 44 toUnixPath, 45 getBelongModuleInfo 46} from '../../utils'; 47import { 48 preprocessExtend, 49 preprocessNewExtend, 50 validateUISyntax, 51 propertyCollection, 52 linkCollection, 53 resetComponentCollection, 54 componentCollection, 55 resetValidateUiSyntax 56} from '../../validate_ui_syntax'; 57import { 58 processUISyntax, 59 resetLog, 60 transformLog, 61 resetProcessUiSyntax 62} from '../../process_ui_syntax'; 63import { 64 projectConfig, 65 abilityPagesFullPath, 66 globalProgram, 67 resetMain, 68 globalModulePaths 69} from '../../../main'; 70import { 71 appComponentCollection, 72 compilerOptions as etsCheckerCompilerOptions, 73 resolveModuleNames, 74 resolveTypeReferenceDirectives, 75 resetEtsCheck, 76 collectAllFiles, 77 allModuleIds, 78 resetEtsCheckTypeScript 79} from '../../ets_checker'; 80import { 81 CUSTOM_BUILDER_METHOD, 82 GLOBAL_CUSTOM_BUILDER_METHOD, 83 INNER_CUSTOM_BUILDER_METHOD, 84 resetComponentMap, 85 INNER_CUSTOM_LOCALBUILDER_METHOD 86} from '../../component_map'; 87import { 88 kitTransformLog, 89 processKitImport, 90 checkHasKeepTs, 91 resetKitImportLog 92} from '../../process_kit_import'; 93import { resetProcessComponentMember } from '../../process_component_member'; 94import { mangleFilePath, resetObfuscation } from '../ark_compiler/common/ob_config_resolver'; 95import arkoalaProgramTransform, { ArkoalaPluginOptions } from './arkoala-plugin'; 96import processStructComponentV2 from '../../process_struct_componentV2'; 97import { resetlogMessageCollection } from '../../log_message_collection'; 98 99const filter: any = createFilter(/(?<!\.d)\.(ets|ts)$/); 100 101const shouldEmitJsFlagMap: Map<string, boolean> = new Map(); 102let shouldDisableCache: boolean = false; 103interface ShouldEnableDebugLineType { 104 enableDebugLine: boolean; 105} 106 107export const ShouldEnableDebugLine: ShouldEnableDebugLineType = { 108 enableDebugLine: false 109}; 110 111let disableCacheOptions = { 112 bundleName: 'default', 113 entryModuleName: 'default', 114 runtimeOS: 'default', 115 resourceTableHash: 'default', 116 etsLoaderVersion: 'default' 117}; 118 119export function etsTransform() { 120 const allFilesInHar: Map<string, string> = new Map(); 121 let cacheFile: { [fileName: string]: CacheFile }; 122 return { 123 name: 'etsTransform', 124 transform: transform, 125 buildStart() { 126 const compilationTime: CompilationTimeStatistics = new CompilationTimeStatistics(this.share, 'etsTransform', 'buildStart'); 127 startTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformBuildStartTime : undefined); 128 judgeCacheShouldDisabled.call(this); 129 if (process.env.compileMode === 'moduleJson') { 130 cacheFile = this.cache.get('transformCacheFiles'); 131 storedFileInfo.addGlobalCacheInfo(this.cache.get('resourceListCacheInfo'), 132 this.cache.get('resourceToFileCacheInfo'), cacheFile); 133 if (this.cache.get('lastResourcesArr')) { 134 storedFileInfo.lastResourcesSet = new Set([...this.cache.get('lastResourcesArr')]); 135 } 136 if (process.env.rawFileResource) { 137 resourcesRawfile(process.env.rawFileResource, storedFileInfo.resourcesArr); 138 this.share.rawfilechanged = differenceResourcesRawfile(storedFileInfo.lastResourcesSet, storedFileInfo.resourcesArr); 139 } 140 } 141 if (!!this.cache.get('enableDebugLine') !== projectConfig.enableDebugLine) { 142 ShouldEnableDebugLine.enableDebugLine = true; 143 } 144 stopTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformBuildStartTime : undefined); 145 }, 146 load(id: string) { 147 let fileCacheInfo: fileInfo; 148 const compilationTime: CompilationTimeStatistics = new CompilationTimeStatistics(this.share, 'etsTransform', 'load'); 149 startTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformLoadTime : undefined); 150 if (this.cache.get('fileCacheInfo')) { 151 fileCacheInfo = this.cache.get('fileCacheInfo')[path.resolve(id)]; 152 } 153 // Exclude Component Preview page 154 if (projectConfig.isPreview && !projectConfig.checkEntry && id.match(/(?<!\.d)\.(ets)$/)) { 155 abilityPagesFullPath.add(path.resolve(id).toLowerCase()); 156 storedFileInfo.judgeShouldHaveEntryFiles(abilityPagesFullPath); 157 } 158 storedFileInfo.addFileCacheInfo(path.resolve(id), fileCacheInfo); 159 storedFileInfo.setCurrentArkTsFile(); 160 stopTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformLoadTime : undefined); 161 }, 162 shouldInvalidCache(options) { 163 const fileName: string = path.resolve(options.id); 164 let shouldDisable: boolean = shouldDisableCache || disableNonEntryFileCache(fileName) || ShouldEnableDebugLine.enableDebugLine; 165 if (process.env.compileMode === 'moduleJson') { 166 shouldDisable = shouldDisable || storedFileInfo.shouldInvalidFiles.has(fileName) || this.share.rawfilechanged; 167 if (cacheFile && cacheFile[fileName] && cacheFile[fileName].children.length) { 168 for (let child of cacheFile[fileName].children) { 169 const newTimeMs: number = fs.existsSync(child.fileName) ? fs.statSync(child.fileName).mtimeMs : -1; 170 if (newTimeMs !== child.mtimeMs) { 171 shouldDisable = true; 172 break; 173 } 174 } 175 } 176 } 177 // If a file import an const enum object from other changed file, this file also need to be transformed. 178 shouldDisable = shouldDisable || checkRelateToConstEnum(options.id); 179 if (!shouldDisable) { 180 storedFileInfo.collectCachedFiles(fileName); 181 } 182 return shouldDisable; 183 }, 184 buildEnd(): void { 185 if (process.env.watchMode !== 'true' && !projectConfig.hotReload && !projectConfig.isPreview) { 186 resetEtsCheckTypeScript(); 187 } 188 }, 189 afterBuildEnd() { 190 // Copy the cache files in the compileArkTS directory to the loader_out directory 191 if (projectConfig.compileHar && !projectConfig.byteCodeHar) { 192 for (let moduleInfoId of allModuleIds.keys()) { 193 const moduleInfo: Object = this.getModuleInfo(moduleInfoId); 194 if (!moduleInfo && !moduleInfoId.match(/\.d\.e?ts$/)) { 195 continue; 196 } 197 if (moduleInfoId && !moduleInfoId.match(new RegExp(projectConfig.packageDir)) && 198 !moduleInfoId.startsWith('\x00') && 199 path.resolve(moduleInfoId).startsWith(projectConfig.moduleRootPath + path.sep)) { 200 let filePath: string = moduleInfoId; 201 let metaInfo: Object; 202 if (filePath.match(/\.d\.e?ts$/)) { 203 metaInfo = getBelongModuleInfo(moduleInfoId, projectConfig.modulePathMap, projectConfig.projectRootPath); 204 } else { 205 metaInfo = moduleInfo.meta; 206 } 207 if (this.share.arkProjectConfig?.obfuscationMergedObConfig?.options?.enableFileNameObfuscation) { 208 filePath = mangleFilePath(filePath); 209 } 210 211 const cacheFilePath: string = genTemporaryPath(filePath, projectConfig.moduleRootPath, 212 process.env.cachePath, projectConfig, metaInfo); 213 const buildFilePath: string = genTemporaryPath(filePath, projectConfig.moduleRootPath, 214 projectConfig.buildPath, projectConfig, metaInfo, true); 215 if (filePath.match(/\.e?ts$/)) { 216 setIncrementalFileInHar(cacheFilePath, buildFilePath, allFilesInHar); 217 } else { 218 allFilesInHar.set(cacheFilePath, buildFilePath); 219 } 220 } 221 } 222 223 allFilesInHar.forEach((buildFilePath, cacheFilePath) => { 224 // if the ts or ets file code only contain interface, it doesn't have js file. 225 if (fs.existsSync(cacheFilePath)) { 226 const sourceCode: string = fs.readFileSync(cacheFilePath, 'utf-8'); 227 writeFileSync(buildFilePath, sourceCode); 228 } 229 }); 230 } 231 shouldDisableCache = false; 232 this.cache.set('disableCacheOptions', disableCacheOptions); 233 this.cache.set('lastResourcesArr', [...storedFileInfo.resourcesArr]); 234 if (projectConfig.enableDebugLine) { 235 this.cache.set('enableDebugLine', true); 236 } else { 237 this.cache.set('enableDebugLine', false); 238 } 239 storedFileInfo.clearCollectedInfo(this.cache); 240 this.cache.set('transformCacheFiles', storedFileInfo.transformCacheFiles); 241 }, 242 cleanUp(): void { 243 resetMain(); 244 resetComponentMap(); 245 resetEtsCheck(); 246 resetEtsTransform(); 247 resetProcessComponentMember(); 248 resetProcessUiSyntax(); 249 resetUtils(); 250 resetValidateUiSyntax(); 251 resetObfuscation(); 252 resetlogMessageCollection(); 253 } 254 }; 255} 256 257export function shouldEmitJsFlagById(id: string): boolean { 258 return shouldEmitJsFlagMap.get(id); 259} 260 261// If a ArkTS file don't have @Entry decorator but it is entry file this time 262function disableNonEntryFileCache(filePath: string): boolean { 263 return storedFileInfo.buildStart && filePath.match(/(?<!\.d)\.(ets)$/) && 264 !storedFileInfo.wholeFileInfo[filePath].hasEntry && 265 storedFileInfo.shouldHaveEntry.includes(filePath); 266} 267 268function judgeCacheShouldDisabled(): void { 269 for (const key in disableCacheOptions) { 270 if (this.cache.get('disableCacheOptions') && this.share && 271 this.share.projectConfig && this.share.projectConfig[key] && 272 this.cache.get('disableCacheOptions')[key] !== this.share.projectConfig[key]) { 273 if (key === 'resourceTableHash' && process.env.compileMode === 'moduleJson') { 274 storedFileInfo.resourceTableChanged = true; 275 } else if (!shouldDisableCache) { 276 shouldDisableCache = true; 277 } 278 } 279 if (this.share && this.share.projectConfig && this.share.projectConfig[key]) { 280 disableCacheOptions[key] = this.share.projectConfig[key]; 281 } 282 storedFileInfo.judgeShouldHaveEntryFiles(abilityPagesFullPath); 283 } 284} 285 286interface EmitResult { 287 outputText: string, 288 sourceMapText: string, 289} 290 291function getCompilerHost(): ts.CompilerHost { 292 const compilerHost: ts.CompilerHost = ts.createCompilerHost(etsCheckerCompilerOptions); 293 compilerHost.writeFile = () => {}; 294 compilerHost.resolveModuleNames = resolveModuleNames; 295 compilerHost.getCurrentDirectory = () => process.cwd(); 296 compilerHost.getDefaultLibFileName = options => ts.getDefaultLibFilePath(options); 297 compilerHost.resolveTypeReferenceDirectives = resolveTypeReferenceDirectives; 298 return compilerHost; 299} 300 301let compilerHost: ts.CompilerHost = null; 302 303if (!compilerHost) { 304 compilerHost = getCompilerHost(); 305} 306 307const arkoalaTsProgramCache: WeakMap<ts.Program, ts.Program> = new WeakMap(); 308 309function getArkoalaTsProgram(program: ts.Program): ts.Program { 310 let extendedProgram = arkoalaTsProgramCache.get(program); 311 if (!extendedProgram) { 312 const pluginOptions: ArkoalaPluginOptions = {}; 313 // This is a stub for the interface generated by ts-patch. 314 // Probably we can use the reported diagnostics in the output 315 const pluginTransformExtras: Object = { 316 diagnostics: [], 317 addDiagnostic(): number {return 0}, 318 removeDiagnostic(): void {}, 319 ts: ts, 320 library: 'typescript', 321 }; 322 extendedProgram = arkoalaProgramTransform(program, compilerHost, pluginOptions, pluginTransformExtras); 323 arkoalaTsProgramCache.set(program, extendedProgram); 324 } 325 return extendedProgram; 326} 327 328async function transform(code: string, id: string) { 329 const compilationTime: CompilationTimeStatistics = new CompilationTimeStatistics(this.share, 'etsTransform', 'transform'); 330 if (!filter(id)) { 331 return null; 332 } 333 334 storedFileInfo.collectTransformedFiles(path.resolve(id)); 335 336 const logger = this.share.getLogger('etsTransform'); 337 338 if (projectConfig.compileMode !== 'esmodule') { 339 const compilerOptions = ts.readConfigFile( 340 path.resolve(__dirname, '../../../tsconfig.json'), ts.sys.readFile).config.compilerOptions; 341 compilerOptions.moduleResolution = 'nodenext'; 342 compilerOptions.module = 'es2020'; 343 const newContent: string = jsBundlePreProcess(code, id, this.getModuleInfo(id).isEntry, logger); 344 const result: ts.TranspileOutput = ts.transpileModule(newContent, { 345 compilerOptions: compilerOptions, 346 fileName: id, 347 transformers: { before: [processUISyntax(null)] } 348 }); 349 350 resetCollection(); 351 if (transformLog && transformLog.errors.length && !projectConfig.ignoreWarning) { 352 emitLogInfo(logger, getTransformLog(transformLog), true, id); 353 resetLog(); 354 } 355 356 return { 357 code: result.outputText, 358 map: result.sourceMapText ? JSON.parse(result.sourceMapText) : new MagicString(code).generateMap() 359 }; 360 } 361 362 let tsProgram: ts.Program = globalProgram.program; 363 let targetSourceFile: ts.SourceFile | undefined = tsProgram.getSourceFile(id); 364 365 // createProgram from the file which does not have corresponding ast from ets-checker's program 366 // by those following cases: 367 // 1. .ets/.ts imported by .js file with tsc's `allowJS` option is false. 368 // 2. .ets/.ts imported by .js file with same name '.d.ts' file which is prior to .js by tsc default resolving 369 if (!targetSourceFile) { 370 startTimeStatisticsLocation(compilationTime ? compilationTime.noSourceFileRebuildProgramTime : undefined); 371 if (storedFileInfo.isFirstBuild && storedFileInfo.changeFiles) { 372 storedFileInfo.newTsProgram = ts.createProgram(storedFileInfo.changeFiles, etsCheckerCompilerOptions, compilerHost); 373 storedFileInfo.isFirstBuild = false; 374 } 375 stopTimeStatisticsLocation(compilationTime ? compilationTime.noSourceFileRebuildProgramTime : undefined); 376 if (storedFileInfo.newTsProgram && storedFileInfo.newTsProgram.getSourceFile(id)) { 377 tsProgram = storedFileInfo.newTsProgram; 378 } else { 379 await CreateProgramMoment.block(id); 380 CreateProgramMoment.release(id); 381 if (storedFileInfo.newTsProgram && storedFileInfo.newTsProgram.getSourceFile(id)) { 382 tsProgram = storedFileInfo.newTsProgram; 383 } else { 384 startTimeStatisticsLocation(compilationTime ? compilationTime.noSourceFileRebuildProgramTime : undefined); 385 tsProgram = ts.createProgram(CreateProgramMoment.getRoots(id), etsCheckerCompilerOptions, compilerHost); 386 storedFileInfo.newTsProgram = tsProgram; 387 stopTimeStatisticsLocation(compilationTime ? compilationTime.noSourceFileRebuildProgramTime : undefined); 388 } 389 } 390 391 // init TypeChecker to run binding 392 globalProgram.checker = tsProgram.getTypeChecker(); 393 globalProgram.strictChecker = tsProgram.getLinterTypeChecker(); 394 targetSourceFile = tsProgram.getSourceFile(id)!; 395 storedFileInfo.reUseProgram = false; 396 collectAllFiles(tsProgram); 397 } else { 398 if (!storedFileInfo.reUseProgram) { 399 globalProgram.checker = globalProgram.program.getTypeChecker(); 400 globalProgram.strictChecker = globalProgram.program.getLinterTypeChecker(); 401 } 402 storedFileInfo.reUseProgram = true; 403 } 404 setPkgNameForFile(this.getModuleInfo(id)); 405 startTimeStatisticsLocation(compilationTime ? compilationTime.validateEtsTime : undefined); 406 validateEts(code, id, this.getModuleInfo(id).isEntry, logger, targetSourceFile); 407 stopTimeStatisticsLocation(compilationTime ? compilationTime.validateEtsTime : undefined); 408 const emitResult: EmitResult = { outputText: '', sourceMapText: '' }; 409 const writeFile: ts.WriteFileCallback = (fileName: string, data: string) => { 410 if (/.map$/.test(fileName)) { 411 emitResult.sourceMapText = data; 412 } else { 413 emitResult.outputText = data; 414 } 415 }; 416 417 // close `noEmit` to make invoking emit() effective. 418 tsProgram.getCompilerOptions().noEmit = false; 419 const metaInfo: Object = this.getModuleInfo(id).meta; 420 const autoLazyImport: boolean = this.share.projectConfig?.autoLazyImport; 421 // use `try finally` to restore `noEmit` when error thrown by `processUISyntax` in preview mode 422 startTimeStatisticsLocation(compilationTime ? compilationTime.shouldEmitJsTime : undefined); 423 const shouldEmitJsFlag: boolean = getShouldEmitJs(projectConfig.shouldEmitJs, targetSourceFile, id); 424 shouldEmitJsFlagMap.set(id, shouldEmitJsFlag); 425 stopTimeStatisticsLocation(compilationTime ? compilationTime.shouldEmitJsTime : undefined); 426 let transformResult: ts.TransformationResult<ts.SourceFile> = null; 427 try { 428 startTimeStatisticsLocation(compilationTime ? compilationTime.tsProgramEmitTime : undefined); 429 if (projectConfig.useArkoala) { 430 tsProgram = getArkoalaTsProgram(tsProgram); 431 targetSourceFile = tsProgram.getSourceFile(id); 432 } 433 if (shouldEmitJsFlag) { 434 startTimeStatisticsLocation(compilationTime ? compilationTime.emitTime : undefined); 435 tsProgram.emit(targetSourceFile, writeFile, undefined, undefined, 436 { 437 before: [ 438 processUISyntax(null, false, compilationTime, id), 439 processKitImport(id, metaInfo, compilationTime, true, autoLazyImport) 440 ] 441 } 442 ); 443 stopTimeStatisticsLocation(compilationTime ? compilationTime.emitTime : undefined); 444 } else { 445 startTimeStatisticsLocation(compilationTime ? compilationTime.transformNodesTime : undefined); 446 const emitResolver: ts.EmitResolver = globalProgram.checker.getEmitResolver(outFile(tsProgram.getCompilerOptions()) ? 447 undefined : targetSourceFile, undefined); 448 transformResult = ts.transformNodes(emitResolver, tsProgram.getEmitHost?.(), ts.factory, 449 tsProgram.getCompilerOptions(), [targetSourceFile], 450 [processUISyntax(null, false, compilationTime, id), 451 processKitImport(id, metaInfo, compilationTime, false, autoLazyImport)], false); 452 stopTimeStatisticsLocation(compilationTime ? compilationTime.transformNodesTime : undefined); 453 } 454 stopTimeStatisticsLocation(compilationTime ? compilationTime.tsProgramEmitTime : undefined); 455 } finally { 456 // restore `noEmit` to prevent tsc's watchService emitting automatically. 457 tsProgram.getCompilerOptions().noEmit = true; 458 } 459 460 resetCollection(); 461 processStructComponentV2.resetStructMapInEts(); 462 if (((transformLog && transformLog.errors.length) || (kitTransformLog && kitTransformLog.errors.length)) && 463 !projectConfig.ignoreWarning) { 464 emitLogInfo(logger, [...getTransformLog(kitTransformLog), ...getTransformLog(transformLog)], true, id); 465 resetLog(); 466 resetKitImportLog(); 467 } 468 469 return shouldEmitJsFlag ? { 470 code: emitResult.outputText, 471 // Use magicString to generate sourceMap because of Typescript do not emit sourceMap in some cases 472 map: emitResult.sourceMapText ? JSON.parse(emitResult.sourceMapText) : new MagicString(code).generateMap(), 473 meta: { 474 shouldEmitJs: true 475 } 476 } : printSourceFile(transformResult.transformed[0], compilationTime); 477} 478 479function printSourceFile(sourceFile: ts.SourceFile, compilationTime: CompilationTimeStatistics): string | null { 480 if (sourceFile) { 481 startTimeStatisticsLocation(compilationTime ? compilationTime.printNodeTime : undefined); 482 const printer: ts.Printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); 483 const sourceCode: string = printer.printNode(ts.EmitHint.Unspecified, sourceFile, sourceFile); 484 stopTimeStatisticsLocation(compilationTime ? compilationTime.printNodeTime : undefined); 485 return sourceCode; 486 } 487 return null; 488} 489 490function outFile(options: ts.CompilerOptions): string { 491 return options.outFile || options.out; 492} 493 494function getShouldEmitJs(shouldEmitJs: boolean, targetSourceFile: ts.SourceFile, id: string): boolean { 495 let shouldEmitJsFlag: boolean = true; 496 let hasKeepTs: boolean = false; 497 if (!projectConfig.processTs) { 498 return shouldEmitJsFlag; 499 } 500 if (projectConfig.complieHar) { 501 if (!projectConfig.UseTsHar && !projectConfig.byteCodeHar) { 502 return shouldEmitJsFlag; 503 } 504 } else { 505 hasKeepTs = checkHasKeepTs(targetSourceFile); 506 } 507 // FA model/code coverage instrumentation/default situation 508 // These three situations require calling the emit interface, while in other cases 'shouldEmitJs' be false. 509 // The 'shouldEmitJS' variable is obtained through 'this.share.sprojectConfig'. 510 if (shouldEmitJs !== undefined) { 511 // ark es2abc 512 shouldEmitJsFlag = shouldEmitJs || ts.hasTsNoCheckOrTsIgnoreFlag(targetSourceFile) && !hasKeepTs; 513 } 514 return shouldEmitJsFlag; 515} 516 517function setPkgNameForFile(moduleInfo: Object): void { 518 if (moduleInfo && moduleInfo.meta && moduleInfo.meta.pkgName) { 519 storedFileInfo.getCurrentArkTsFile().pkgName = moduleInfo.meta.pkgName; 520 } 521} 522 523function validateEts(code: string, id: string, isEntry: boolean, logger: any, sourceFile: ts.SourceFile) { 524 if (/\.ets$/.test(id)) { 525 clearCollection(); 526 const fileQuery: string = isEntry && !abilityPagesFullPath.has(path.resolve(id).toLowerCase()) ? '?entry' : ''; 527 const log: LogInfo[] = validateUISyntax(code, code, id, fileQuery, sourceFile); 528 if (log.length && !projectConfig.ignoreWarning) { 529 emitLogInfo(logger, log, true, id); 530 } 531 } 532} 533 534function jsBundlePreProcess(code: string, id: string, isEntry: boolean, logger: any): string { 535 if (/\.ets$/.test(id)) { 536 clearCollection(); 537 let content = preprocessExtend(code); 538 content = preprocessNewExtend(content); 539 const fileQuery: string = isEntry && !abilityPagesFullPath.has(path.resolve(id).toLowerCase()) ? '?entry' : ''; 540 const log: LogInfo[] = validateUISyntax(code, content, id, fileQuery); 541 if (log.length && !projectConfig.ignoreWarning) { 542 emitLogInfo(logger, log, true, id); 543 } 544 return content; 545 } 546 return code; 547} 548 549function clearCollection(): void { 550 componentCollection.customComponents.clear(); 551 CUSTOM_BUILDER_METHOD.clear(); 552 INNER_CUSTOM_LOCALBUILDER_METHOD.clear(); 553 GLOBAL_CUSTOM_BUILDER_METHOD.clear(); 554 INNER_CUSTOM_BUILDER_METHOD.clear(); 555 storedFileInfo.getCurrentArkTsFile().compFromDETS.clear(); 556} 557 558function resetCollection() { 559 componentInfo.id = 0; 560 propertyCollection.clear(); 561 linkCollection.clear(); 562 resetComponentCollection(); 563 storedFileInfo.hasLocalBuilderInFile = false; 564} 565 566function resetEtsTransform(): void { 567 ShouldEnableDebugLine.enableDebugLine = false; 568 projectConfig.ignoreWarning = false; 569 projectConfig.widgetCompile = false; 570 compilerHost = null; 571 disableCacheOptions = { 572 bundleName: 'default', 573 entryModuleName: 'default', 574 runtimeOS: 'default', 575 resourceTableHash: 'default', 576 etsLoaderVersion: 'default' 577 }; 578} 579 580function findArkoalaRoot(): string { 581 let arkoalaSdkRoot: string; 582 if (process.env.ARKOALA_SDK_ROOT) { 583 arkoalaSdkRoot = process.env.ARKOALA_SDK_ROOT; 584 if (!isDir(arkoalaSdkRoot)) { 585 throw new Error('Arkoala SDK not found in ' + arkoalaSdkRoot); 586 } 587 } else { 588 const arkoalaPossiblePaths: string[] = globalModulePaths.map(dir => path.join(dir, '../../arkoala')); 589 arkoalaSdkRoot = arkoalaPossiblePaths.find(possibleRootDir => isDir(possibleRootDir)) ?? ''; 590 if (!arkoalaSdkRoot) { 591 throw new Error('Arkoala SDK not found in ' + arkoalaPossiblePaths.join(';')); 592 } 593 } 594 595 return arkoalaSdkRoot; 596} 597 598function isDir(filePath: string): boolean { 599 try { 600 let stat: fs.Stats = fs.statSync(filePath); 601 return stat.isDirectory(); 602 } catch (e) { 603 return false; 604 } 605} 606 607function setIncrementalFileInHar(cacheFilePath: string, buildFilePath: string, allFilesInHar: Map<string, string>): void { 608 if (cacheFilePath.match(/\.d.e?ts$/)) { 609 allFilesInHar.set(cacheFilePath, buildFilePath); 610 return; 611 } 612 let extName = projectConfig.useTsHar ? '.ts' : '.js'; 613 allFilesInHar.set(cacheFilePath.replace(/\.ets$/, '.d.ets').replace(/\.ts$/, '.d.ts'), 614 buildFilePath.replace(/\.ets$/, '.d.ets').replace(/\.ts$/, '.d.ts')); 615 allFilesInHar.set(cacheFilePath.replace(/\.e?ts$/, extName), buildFilePath.replace(/\.e?ts$/, extName)); 616} 617 618function checkRelateToConstEnum(id: string): boolean { 619 let tsProgram: ts.Program = globalProgram.builderProgram; 620 let targetSourceFile: ts.SourceFile | undefined = tsProgram ? tsProgram.getSourceFile(id) : undefined; 621 if (!targetSourceFile) { 622 return false; 623 } 624 if (!tsProgram.isFileUpdateInConstEnumCache) { 625 return false; 626 } 627 return tsProgram.isFileUpdateInConstEnumCache(targetSourceFile); 628} 629 630interface moduleInfoType { 631 id: string; 632}; 633interface optionsType { 634 id: string; 635}; 636class CreateProgramMoment { 637 static transFileCollect: Set<string> = new Set(); 638 static awaitFileCollect: Set<string> = new Set(); 639 static moduleParsedFileCollect: Set<string> = new Set(); 640 static promise: Promise<void> = undefined; 641 static emitter = undefined; 642 static roots: Set<string> = new Set(); 643 644 static init(): void { 645 if (CreateProgramMoment.promise) { 646 return; 647 } 648 CreateProgramMoment.roots.add(path.resolve(__dirname, '../../../declarations/common.d.ts')); 649 CreateProgramMoment.emitter = new nodeEvents.EventEmitter(); 650 CreateProgramMoment.promise = new Promise<void>(resolve => { 651 CreateProgramMoment.emitter.on('checkPrefCreateProgramId', () => { 652 if (CreateProgramMoment.awaitFileCollect.size + CreateProgramMoment.moduleParsedFileCollect.size === 653 CreateProgramMoment.transFileCollect.size) { 654 resolve(); 655 } 656 }); 657 }); 658 } 659 660 static getPlugin() { 661 return { 662 name: 'createProgramPlugin', 663 load: { 664 order: 'pre', 665 handler(id: string): void { 666 CreateProgramMoment.transFileCollect.add(id); 667 } 668 }, 669 670 moduleParsed(moduleInfo: moduleInfoType): void { 671 CreateProgramMoment.moduleParsedFileCollect.add(moduleInfo.id); 672 CreateProgramMoment.emitter?.emit('checkPrefCreateProgramId'); 673 }, 674 cleanUp(): void { 675 shouldEmitJsFlagMap.clear(); 676 CreateProgramMoment.reset(); 677 } 678 }; 679 } 680 681 static async block(id: string): Promise<void> { 682 CreateProgramMoment.init(); 683 CreateProgramMoment.awaitFileCollect.add(id); 684 CreateProgramMoment.roots.add(id); 685 CreateProgramMoment.emitter.emit('checkPrefCreateProgramId'); 686 return CreateProgramMoment.promise; 687 } 688 689 static release(id: string): void { 690 CreateProgramMoment.awaitFileCollect.delete(id); 691 } 692 693 static reset(): void { 694 CreateProgramMoment.transFileCollect.clear(); 695 CreateProgramMoment.awaitFileCollect.clear(); 696 CreateProgramMoment.moduleParsedFileCollect.clear(); 697 CreateProgramMoment.promise = undefined; 698 CreateProgramMoment.emitter = undefined; 699 CreateProgramMoment.roots.clear(); 700 } 701 702 static getRoots(id: string): string[] { 703 const res: string[] = []; 704 CreateProgramMoment.roots.forEach(id => res.push(id)); 705 CreateProgramMoment.promise = undefined; 706 CreateProgramMoment.emitter = undefined; 707 CreateProgramMoment.roots.clear(); 708 if (res.length === 0) { 709 return [id]; 710 } 711 return res; 712 } 713} 714 715exports.createProgramPlugin = CreateProgramMoment.getPlugin; 716