1/* 2 * Copyright (c) 2021 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 path from 'path'; 17import ts from 'typescript'; 18import fs from 'fs'; 19import os from 'os'; 20import uglifyJS from 'uglify-js'; 21 22import { 23 partialUpdateConfig, 24 projectConfig, 25 globalProgram 26} from '../main'; 27import { createHash } from 'crypto'; 28import { 29 AUXILIARY, 30 EXTNAME_ETS, 31 EXTNAME_CJS, 32 EXTNAME_MJS, 33 EXTNAME_JS, 34 MAIN, 35 FAIL, 36 TEMPORARY, 37 ESMODULE, 38 $$, 39 EXTEND_DECORATORS, 40 COMPONENT_EXTEND_DECORATOR, 41 COMPONENT_ANIMATABLE_EXTEND_DECORATOR 42} from './pre_define'; 43 44export enum LogType { 45 ERROR = 'ERROR', 46 WARN = 'WARN', 47 NOTE = 'NOTE' 48} 49export const TEMPORARYS: string = 'temporarys'; 50export const BUILD: string = 'build'; 51export const SRC_MAIN: string = 'src/main'; 52 53const red: string = '\u001b[31m'; 54const reset: string = '\u001b[39m'; 55 56const WINDOWS: string = 'Windows_NT'; 57const LINUX: string = 'Linux'; 58const MAC: string = 'Darwin'; 59 60export interface LogInfo { 61 type: LogType, 62 message: string, 63 pos?: number, 64 line?: number, 65 column?: number, 66 fileName?: string 67} 68 69export const repeatLog: Map<string, LogInfo> = new Map(); 70 71export class FileLog { 72 private _sourceFile: ts.SourceFile; 73 private _errors: LogInfo[] = []; 74 75 public get sourceFile() { 76 return this._sourceFile; 77 } 78 79 public set sourceFile(newValue: ts.SourceFile) { 80 this._sourceFile = newValue; 81 } 82 83 public get errors() { 84 return this._errors; 85 } 86 87 public set errors(newValue: LogInfo[]) { 88 this._errors = newValue; 89 } 90} 91 92export function emitLogInfo(loader: any, infos: LogInfo[], fastBuild: boolean = false, 93 resourcePath: string = null): void { 94 if (infos && infos.length) { 95 infos.forEach((item) => { 96 switch (item.type) { 97 case LogType.ERROR: 98 fastBuild ? loader.error('\u001b[31m' + getMessage(item.fileName || resourcePath, item, true)) : 99 loader.emitError(getMessage(item.fileName || loader.resourcePath, item)); 100 break; 101 case LogType.WARN: 102 fastBuild ? loader.warn('\u001b[33m' + getMessage(item.fileName || resourcePath, item, true)) : 103 loader.emitWarning(getMessage(item.fileName || loader.resourcePath, item)); 104 break; 105 case LogType.NOTE: 106 fastBuild ? loader.info('\u001b[34m' + getMessage(item.fileName || resourcePath, item, true)) : 107 loader.emitWarning(getMessage(loader.resourcePath, item)); 108 break; 109 } 110 }); 111 } 112} 113 114export function addLog(type: LogType, message: string, pos: number, log: LogInfo[], 115 sourceFile: ts.SourceFile) { 116 const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(pos); 117 log.push({ 118 type: type, 119 message: message, 120 line: posOfNode.line + 1, 121 column: posOfNode.character + 1, 122 fileName: sourceFile.fileName 123 }); 124} 125 126export function getMessage(fileName: string, info: LogInfo, fastBuild: boolean = false): string { 127 let message: string; 128 if (info.line && info.column) { 129 message = `BUILD${info.type} File: ${fileName}:${info.line}:${info.column}\n ${info.message}`; 130 } else { 131 message = `BUILD${info.type} File: ${fileName}\n ${info.message}`; 132 } 133 if (fastBuild) { 134 message = message.replace(/^BUILD/, 'ArkTS:'); 135 } 136 return message; 137} 138 139export function getTransformLog(transformLog: FileLog): LogInfo[] { 140 const sourceFile: ts.SourceFile = transformLog.sourceFile; 141 const logInfos: LogInfo[] = transformLog.errors.map((item) => { 142 if (item.pos) { 143 if (!item.column || !item.line) { 144 const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(item.pos); 145 item.line = posOfNode.line + 1; 146 item.column = posOfNode.character + 1; 147 } 148 } else { 149 item.line = item.line || undefined; 150 item.column = item.column || undefined; 151 } 152 if (!item.fileName) { 153 item.fileName = sourceFile.fileName; 154 } 155 return item; 156 }); 157 return logInfos; 158} 159 160class ComponentInfo { 161 private _id: number = 0; 162 private _componentNames: Set<string> = new Set(['ForEach']); 163 public set id(id: number) { 164 this._id = id; 165 } 166 public get id() { 167 return this._id; 168 } 169 public set componentNames(componentNames: Set<string>) { 170 this._componentNames = componentNames; 171 } 172 public get componentNames() { 173 return this._componentNames; 174 } 175} 176 177export const componentInfo: ComponentInfo = new ComponentInfo(); 178 179export function hasDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration | 180 ts.StructDeclaration | ts.ClassDeclaration, decortorName: string, 181 customBuilder: ts.Decorator[] = null, log: LogInfo[] = null): boolean { 182 if (node.decorators && node.decorators.length) { 183 const extendResult = { 184 Extend: false, 185 AnimatableExtend: false 186 } 187 for (let i = 0; i < node.decorators.length; i++) { 188 const originalDecortor: string = node.decorators[i].getText().replace(/\(.*\)$/, '').trim(); 189 if (log && EXTEND_DECORATORS.includes(decortorName)) { 190 if (originalDecortor === COMPONENT_EXTEND_DECORATOR) { 191 extendResult.Extend = true; 192 } 193 if (originalDecortor === COMPONENT_ANIMATABLE_EXTEND_DECORATOR) { 194 extendResult.AnimatableExtend = true; 195 } 196 } else { 197 if (originalDecortor === decortorName) { 198 if (customBuilder) { 199 customBuilder.push(...node.decorators.slice(i + 1), ...node.decorators.slice(0, i)); 200 } 201 return true; 202 } 203 } 204 } 205 if (log && extendResult.Extend && extendResult.AnimatableExtend) { 206 log.push({ 207 type: LogType.ERROR, 208 message: `The function can not be decorated by '@Extend' and '@AnimatableExtend' at the same time.`, 209 pos: node.getStart() 210 }); 211 } 212 return (decortorName === COMPONENT_EXTEND_DECORATOR && extendResult.Extend) 213 || (decortorName === COMPONENT_ANIMATABLE_EXTEND_DECORATOR && extendResult.AnimatableExtend); 214 } 215 return false; 216} 217 218const STATEMENT_EXPECT: number = 1128; 219const SEMICOLON_EXPECT: number = 1005; 220const STATESTYLES_EXPECT: number = 1003; 221export const IGNORE_ERROR_CODE: number[] = [STATEMENT_EXPECT, SEMICOLON_EXPECT, STATESTYLES_EXPECT]; 222 223export function readFile(dir: string, utFiles: string[]) { 224 try { 225 const files: string[] = fs.readdirSync(dir); 226 files.forEach((element) => { 227 const filePath: string = path.join(dir, element); 228 const status: fs.Stats = fs.statSync(filePath); 229 if (status.isDirectory()) { 230 readFile(filePath, utFiles); 231 } else { 232 utFiles.push(filePath); 233 } 234 }); 235 } catch (e) { 236 console.error(red, 'ArkTS ERROR: ' + e, reset); 237 } 238} 239 240export function circularFile(inputPath: string, outputPath: string): void { 241 if (!inputPath || !outputPath) { 242 return; 243 } 244 fs.readdir(inputPath, function(err, files) { 245 if (!files) { 246 return; 247 } 248 files.forEach(file => { 249 const inputFile: string = path.resolve(inputPath, file); 250 const outputFile: string = path.resolve(outputPath, file); 251 const fileStat: fs.Stats = fs.statSync(inputFile); 252 if (fileStat.isFile()) { 253 copyFile(inputFile, outputFile); 254 } else { 255 circularFile(inputFile, outputFile); 256 } 257 }); 258 }); 259} 260 261function copyFile(inputFile: string, outputFile: string): void { 262 try { 263 const parent: string = path.join(outputFile, '..'); 264 if (!(fs.existsSync(parent) && fs.statSync(parent).isDirectory())) { 265 mkDir(parent); 266 } 267 if (fs.existsSync(outputFile)) { 268 return; 269 } 270 const readStream: fs.ReadStream = fs.createReadStream(inputFile); 271 const writeStream: fs.WriteStream = fs.createWriteStream(outputFile); 272 readStream.pipe(writeStream); 273 readStream.on('close', function() { 274 writeStream.end(); 275 }); 276 } catch (err) { 277 throw err.message; 278 } 279} 280 281export function mkDir(path_: string): void { 282 const parent: string = path.join(path_, '..'); 283 if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { 284 mkDir(parent); 285 } 286 fs.mkdirSync(path_); 287} 288 289export function toUnixPath(data: string): string { 290 if (/^win/.test(require('os').platform())) { 291 const fileTmps: string[] = data.split(path.sep); 292 const newData: string = path.posix.join(...fileTmps); 293 return newData; 294 } 295 return data; 296} 297 298export function toHashData(path: string): any { 299 const content: string = fs.readFileSync(path).toString(); 300 const hash: any = createHash('sha256'); 301 hash.update(content); 302 return hash.digest('hex'); 303} 304 305export function writeFileSync(filePath: string, content: string): void { 306 if (!fs.existsSync(filePath)) { 307 const parent: string = path.join(filePath, '..'); 308 if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { 309 mkDir(parent); 310 } 311 } 312 fs.writeFileSync(filePath, content); 313} 314 315export function genTemporaryPath(filePath: string, projectPath: string, buildPath: string, 316 projectConfig: any, buildInHar: boolean = false): string { 317 filePath = toUnixPath(filePath).replace(/\.[cm]js$/, EXTNAME_JS); 318 projectPath = toUnixPath(projectPath); 319 320 if (process.env.compileTool === 'rollup') { 321 const projectRootPath: string = toUnixPath(buildInHar ? projectPath : projectConfig.projectRootPath); 322 const relativeFilePath: string = filePath.replace(projectRootPath, ''); 323 const output: string = path.join(buildPath, relativeFilePath); 324 return output; 325 } 326 327 if (isPackageModulesFile(filePath, projectConfig)) { 328 const packageDir: string = projectConfig.packageDir; 329 const fakePkgModulesPath: string = toUnixPath(path.join(projectConfig.projectRootPath, packageDir)); 330 let output: string = ''; 331 if (filePath.indexOf(fakePkgModulesPath) === -1) { 332 const hapPath: string = toUnixPath(projectConfig.projectRootPath); 333 const tempFilePath: string = filePath.replace(hapPath, ''); 334 const relativeFilePath: string = tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1); 335 output = path.join(buildPath, buildInHar ? '' : TEMPORARY, packageDir, MAIN, relativeFilePath); 336 } else { 337 output = filePath.replace(fakePkgModulesPath, 338 path.join(buildPath, buildInHar ? '' : TEMPORARY, packageDir, AUXILIARY)); 339 } 340 return output; 341 } 342 343 if (filePath.indexOf(projectPath) !== -1) { 344 const relativeFilePath: string = filePath.replace(projectPath, ''); 345 const output: string = path.join(buildPath, buildInHar ? '' : TEMPORARY, relativeFilePath); 346 return output; 347 } 348 349 return ''; 350} 351 352export function isPackageModulesFile(filePath: string, projectConfig: any): boolean { 353 filePath = toUnixPath(filePath); 354 const hapPath: string = toUnixPath(projectConfig.projectRootPath); 355 const tempFilePath: string = filePath.replace(hapPath, ''); 356 const packageDir: string = projectConfig.packageDir; 357 if (tempFilePath.indexOf(packageDir) !== -1) { 358 const fakePkgModulesPath: string = toUnixPath(path.resolve(projectConfig.projectRootPath, packageDir)); 359 if (filePath.indexOf(fakePkgModulesPath) !== -1) { 360 return true; 361 } 362 if (projectConfig.modulePathMap) { 363 for (const key in projectConfig.modulePathMap) { 364 const value: string = projectConfig.modulePathMap[key]; 365 const fakeModulePkgModulesPath: string = toUnixPath(path.resolve(value, packageDir)); 366 if (filePath.indexOf(fakeModulePkgModulesPath) !== -1) { 367 return true; 368 } 369 } 370 } 371 } 372 373 return false; 374} 375 376export function generateSourceFilesInHar(sourcePath: string, sourceContent: string, suffix: string, projectConfig: any) { 377 // compileShared: compile shared har of project 378 let jsFilePath: string = genTemporaryPath(sourcePath, 379 projectConfig.compileShared ? projectConfig.projectRootPath : projectConfig.moduleRootPath, 380 projectConfig.compileShared ? path.resolve(projectConfig.aceModuleBuild, '../etsFortgz') : projectConfig.cachePath, 381 projectConfig, projectConfig.compileShared); 382 if (!jsFilePath.match(new RegExp(projectConfig.packageDir))) { 383 jsFilePath = jsFilePath.replace(/\.ets$/, suffix).replace(/\.ts$/, suffix); 384 mkdirsSync(path.dirname(jsFilePath)); 385 if (projectConfig.obfuscateHarType === 'uglify' && suffix === '.js') { 386 sourceContent = uglifyJS.minify(sourceContent).code; 387 } 388 fs.writeFileSync(jsFilePath, sourceContent); 389 } 390} 391 392export function mkdirsSync(dirname: string): boolean { 393 if (fs.existsSync(dirname)) { 394 return true; 395 } else if (mkdirsSync(path.dirname(dirname))) { 396 fs.mkdirSync(dirname); 397 return true; 398 } 399 400 return false; 401} 402 403export function nodeLargeOrEqualTargetVersion(targetVersion: number): boolean { 404 const currentNodeVersion: number = parseInt(process.versions.node.split('.')[0]); 405 if (currentNodeVersion >= targetVersion) { 406 return true; 407 } 408 409 return false; 410} 411 412export function removeDir(dirName: string): void { 413 if (fs.existsSync(dirName)) { 414 if (nodeLargeOrEqualTargetVersion(16)) { 415 fs.rmSync(dirName, { recursive: true}); 416 } else { 417 fs.rmdirSync(dirName, { recursive: true}); 418 } 419 } 420} 421 422export function parseErrorMessage(message: string): string { 423 const messageArrary: string[] = message.split('\n'); 424 let logContent: string = ''; 425 messageArrary.forEach(element => { 426 if (!(/^at/.test(element.trim()))) { 427 logContent = logContent + element + '\n'; 428 } 429 }); 430 return logContent; 431} 432 433export function isWindows(): boolean { 434 return os.type() === WINDOWS; 435} 436 437export function isLinux(): boolean { 438 return os.type() === LINUX; 439} 440 441export function isMac(): boolean { 442 return os.type() === MAC; 443} 444 445export function maxFilePathLength(): number { 446 if (isWindows()) { 447 return 32766; 448 } else if (isLinux()) { 449 return 4095; 450 } else if (isMac()) { 451 return 1016; 452 } else { 453 return -1; 454 } 455} 456 457export function validateFilePathLength(filePath: string, logger: any): boolean { 458 if (maxFilePathLength() < 0) { 459 logger.error(red, "Unknown OS platform", reset); 460 process.exitCode = FAIL; 461 return false; 462 } else if (filePath.length > 0 && filePath.length <= maxFilePathLength()) { 463 return true; 464 } else if (filePath.length > maxFilePathLength()) { 465 logger.error(red, `The length of ${filePath} exceeds the limitation of current platform, which is ` + 466 `${maxFilePathLength()}. Please try moving the project folder to avoid deeply nested file path and try again`, 467 reset); 468 process.exitCode = FAIL; 469 return false; 470 } else { 471 logger.error(red, "Validate file path failed", reset); 472 process.exitCode = FAIL; 473 return false; 474 } 475} 476 477export function validateFilePathLengths(filePaths: Array<string>, logger: any): boolean { 478 filePaths.forEach((filePath) => { 479 if (!validateFilePathLength(filePath, logger)) { 480 return false; 481 } 482 }) 483 return true; 484} 485 486export function unlinkSync(filePath: string): void { 487 if (fs.existsSync(filePath)) { 488 fs.unlinkSync(filePath); 489 } 490} 491 492export function getExtensionIfUnfullySpecifiedFilepath(filePath: string): string { 493 if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { 494 return ""; 495 } 496 497 let extension: string = EXTNAME_ETS; 498 if (fs.existsSync(filePath + '.ts') && fs.statSync(filePath + '.ts').isFile()) { 499 extension = '.ts'; 500 } else if (fs.existsSync(filePath + '.d.ts') && fs.statSync(filePath + '.d.ts').isFile()) { 501 extension = '.d.ts'; 502 } else if (fs.existsSync(filePath + '.d.ets') && fs.statSync(filePath + '.d.ets').isFile()) { 503 extension = '.d.ets'; 504 } else if (fs.existsSync(filePath + '.js') && fs.statSync(filePath + '.js').isFile()) { 505 extension = '.js'; 506 } else if (fs.existsSync(filePath + '.json') && fs.statSync(filePath + '.json').isFile()) { 507 extension = '.json'; 508 } 509 510 return extension; 511} 512 513export function shouldWriteChangedList(watchModifiedFiles: string[], 514 watchRemovedFiles: string[]): boolean { 515 if (projectConfig.compileMode === ESMODULE && process.env.watchMode === 'true' && !projectConfig.isPreview && 516 projectConfig.changedFileList && (watchRemovedFiles.length + watchModifiedFiles.length)) { 517 if (process.env.compileTool !== 'rollup') { 518 if (!(watchModifiedFiles.length === 1 && 519 watchModifiedFiles[0] === projectConfig.projectPath && !watchRemovedFiles.length)) { 520 return true; 521 } else { 522 return false; 523 } 524 } 525 return true; 526 } 527 return false; 528} 529 530interface HotReloadIncrementalTime { 531 hotReloadIncrementalStartTime: string; 532 hotReloadIncrementalEndTime: string; 533} 534 535export const hotReloadIncrementalTime: HotReloadIncrementalTime = { 536 hotReloadIncrementalStartTime: '', 537 hotReloadIncrementalEndTime: '' 538}; 539 540interface FilesObj { 541 modifiedFiles: string[], 542 removedFiles: string[] 543} 544 545let allModifiedFiles: Set<string> = new Set(); 546 547export function getHotReloadFiles(watchModifiedFiles: string[], 548 watchRemovedFiles: string[], hotReloadSupportFiles: Set<string>): FilesObj { 549 hotReloadIncrementalTime.hotReloadIncrementalStartTime = new Date().getTime().toString(); 550 watchRemovedFiles = watchRemovedFiles.map(file => path.relative(projectConfig.projectPath, file)); 551 allModifiedFiles = new Set([...allModifiedFiles, ...watchModifiedFiles 552 .filter(file => fs.statSync(file).isFile() && 553 (hotReloadSupportFiles.has(file) || !['.ets', '.ts', '.js'].includes(path.extname(file)))) 554 .map(file => path.relative(projectConfig.projectPath, file))] 555 .filter(file => !watchRemovedFiles.includes(file))); 556 return { 557 modifiedFiles: [...allModifiedFiles], 558 removedFiles: [...watchRemovedFiles] 559 }; 560} 561 562export function getResolveModules(projectPath: string, faMode: boolean): string[] { 563 if (faMode) { 564 return [ 565 path.resolve(projectPath, '../../../../../'), 566 path.resolve(projectPath, '../../../../' + projectConfig.packageDir), 567 path.resolve(projectPath, '../../../../../' + projectConfig.packageDir), 568 path.resolve(projectPath, '../../') 569 ]; 570 } else { 571 return [ 572 path.resolve(projectPath, '../../../../'), 573 path.resolve(projectPath, '../../../' + projectConfig.packageDir), 574 path.resolve(projectPath, '../../../../' + projectConfig.packageDir), 575 path.resolve(projectPath, '../') 576 ]; 577 } 578} 579 580export function writeUseOSFiles(useOSFiles: Set<string>): void { 581 let info: string = ''; 582 if (!fs.existsSync(projectConfig.aceSoPath)) { 583 const parent: string = path.resolve(projectConfig.aceSoPath, '..'); 584 if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { 585 mkDir(parent); 586 } 587 } else { 588 info = fs.readFileSync(projectConfig.aceSoPath, 'utf-8') + '\n'; 589 } 590 fs.writeFileSync(projectConfig.aceSoPath, info + Array.from(useOSFiles).join('\n')); 591} 592 593 594export function writeCollectionFile(cachePath: string, appCollection: Map<string, Set<string>>, 595 allComponentsOrModules: Map<string, Array<string>>, fileName: string, allFiles: Set<string> = null) { 596 for (let key of appCollection.keys()) { 597 if (appCollection.get(key).size === 0) { 598 allComponentsOrModules.delete(key); 599 continue; 600 } 601 if (allFiles && !allFiles.has(key)) { 602 continue; 603 } 604 allComponentsOrModules.set(key, Array.from(appCollection.get(key))); 605 } 606 const content: string = JSON.stringify(Object.fromEntries(allComponentsOrModules), null, 2); 607 writeFileSync(path.resolve(cachePath, fileName), content); 608} 609 610export function getAllComponentsOrModules(allFiles: Set<string>, 611 cacheCollectionFileName: string): Map<string, Array<string>> { 612 const cacheCollectionFilePath: string = path.resolve(projectConfig.cachePath, cacheCollectionFileName); 613 const allComponentsOrModules: Map<string, Array<string>> = new Map(); 614 if (!fs.existsSync(cacheCollectionFilePath)) { 615 return allComponentsOrModules; 616 } 617 const lastComponentsOrModules = require(cacheCollectionFilePath); 618 for (let key in lastComponentsOrModules) { 619 if (allFiles.has(key)) { 620 allComponentsOrModules.set(key, lastComponentsOrModules[key]); 621 } 622 } 623 return allComponentsOrModules; 624} 625 626export function getPossibleBuilderTypeParameter(parameters: ts.ParameterDeclaration[]): string[] { 627 const parameterNames: string[] = []; 628 if (!partialUpdateConfig.builderCheck) { 629 if (is$$Parameter(parameters)) { 630 parameters[0].type.members.forEach((member) => { 631 if (member.name && ts.isIdentifier(member.name)) { 632 parameterNames.push(member.name.escapedText.toString()); 633 } 634 }); 635 } else { 636 parameters.forEach((parameter) => { 637 if (parameter.name && ts.isIdentifier(parameter.name)) { 638 parameterNames.push(parameter.name.escapedText.toString()); 639 } 640 }); 641 } 642 } 643 return parameterNames; 644} 645 646function is$$Parameter(parameters: ts.ParameterDeclaration[]): boolean { 647 return parameters.length === 1 && parameters[0].name && ts.isIdentifier(parameters[0].name) && 648 parameters[0].name.escapedText.toString() === $$ && parameters[0].type && ts.isTypeLiteralNode(parameters[0].type) && 649 parameters[0].type.members && parameters[0].type.members.length > 0; 650} 651 652interface ChildrenCacheFile { 653 fileName: string, 654 mtimeMs: number, 655} 656 657export interface CacheFile { 658 mtimeMs: number, 659 children: Array<ChildrenCacheFile>, 660} 661 662// Global Information & Method 663class ProcessFileInfo { 664 buildStart: boolean = true; 665 wholeFileInfo: {[id: string]: SpecialArkTSFileInfo | TSFileInfo} = {}; // Save ArkTS & TS file's infomation 666 transformedFiles: Set<string> = new Set(); // ArkTS & TS Files which should be transformed in this compilation 667 cachedFiles: string[] = []; // ArkTS & TS Files which should not be transformed in this compilation 668 shouldHaveEntry: string[] = []; // Which file should have @Entry decorator 669 resourceToFile: {[resource: string]: Set<string>} = {}; // Resource is used by which file 670 lastResourceList: Set<string> = new Set(); 671 resourceList: Set<string> = new Set(); // Whole project resource 672 shouldInvalidFiles: Set<string> = new Set(); 673 resourceTableChanged: boolean = false; 674 currentArkTsFile: SpecialArkTSFileInfo; 675 reUseProgram: boolean = false; 676 resourcesArr: Set<string> = new Set(); 677 lastResourcesSet: Set<string> = new Set(); 678 transformCacheFiles: {[fileName: string]: CacheFile} = {}; 679 newTsProgram: ts.Program; 680 changeFiles: string[] = []; 681 isFirstBuild: boolean = true; 682 683 addGlobalCacheInfo(resourceListCacheInfo: string[], 684 resourceToFileCacheInfo: {[resource: string]: Set<string>}) { 685 if (this.buildStart) { 686 for (const element in resourceToFileCacheInfo) { 687 this.resourceToFile[element] = new Set(resourceToFileCacheInfo[element]); 688 } 689 this.lastResourceList = new Set(resourceListCacheInfo); 690 } 691 if (this.resourceTableChanged) { 692 this.compareResourceDiff(); 693 } 694 } 695 696 addFileCacheInfo(id: string, fileCacheInfo: fileInfo) { 697 if (fileCacheInfo && process.env.compileMode === 'moduleJson') { 698 if (Array.isArray(fileCacheInfo.fileToResourceList)) { 699 fileCacheInfo.fileToResourceList = new Set(fileCacheInfo.fileToResourceList); 700 } else { 701 fileCacheInfo.fileToResourceList = new Set(); 702 } 703 } 704 if (id.match(/(?<!\.d)\.(ets)$/)) { 705 this.wholeFileInfo[id] = new SpecialArkTSFileInfo(fileCacheInfo); 706 } else if (id.match(/(?<!\.d)\.(ts)$/) && process.env.compileMode === 'moduleJson') { 707 this.wholeFileInfo[id] = new TSFileInfo(fileCacheInfo); 708 } 709 } 710 711 collectTransformedFiles(id: string) { 712 if (id.match(process.env.compileMode === 'moduleJson' ? /(?<!\.d)\.(ets|ts)$/ : /(?<!\.d)\.(ets)$/)) { 713 this.transformedFiles.add(id); 714 } 715 } 716 717 collectCachedFiles(id: string) { 718 if (id.match(process.env.compileMode === 'moduleJson' ? /(?<!\.d)\.(ets|ts)$/ : /(?<!\.d)\.(ets)$/)) { 719 this.cachedFiles.push(id); 720 } 721 } 722 723 judgeShouldHaveEntryFiles(entryFileWithoutEntryDecorator: string[]): void { 724 this.shouldHaveEntry = Object.values(projectConfig.entryObj as string[]).filter((item) => { 725 return !entryFileWithoutEntryDecorator.includes(item.toLowerCase()) && item.match(/(?<!\.d)\.(ets)$/); 726 }); 727 } 728 729 saveCacheFileInfo(cache) { 730 if (process.env.compileMode === 'moduleJson') { 731 const fileCacheInfo: {[id: string]: fileInfo | tsFileInfo} = cache.get('fileCacheInfo') || {}; 732 const resourceToFileCacheInfo = cache.get('resourceToFileCacheInfo') || {}; 733 for (const i in resourceToFileCacheInfo) { 734 resourceToFileCacheInfo[i] = new Set(resourceToFileCacheInfo[i]); 735 } 736 const resourceToFile: {[resource: string]: Set<string> | string[]} = Object.assign(resourceToFileCacheInfo, this.resourceToFile); 737 for (const id of this.transformedFiles) { 738 fileCacheInfo[id] = this.wholeFileInfo[id].fileInfo; 739 for (const resource of this.wholeFileInfo[id].newFileToResourceList) { 740 if (!(fileCacheInfo[id].fileToResourceList as Set<string>).has(resource)) { 741 if (!resourceToFile[resource]) { 742 resourceToFile[resource] = new Set(); 743 } 744 (resourceToFile[resource] as Set<string>).add(id); 745 } 746 } 747 for (const resource of fileCacheInfo[id].fileToResourceList) { 748 if (!this.wholeFileInfo[id].newFileToResourceList.has(resource)) { 749 (resourceToFile[resource] as Set<string>).delete(id); 750 } 751 } 752 fileCacheInfo[id].fileToResourceList = [...this.wholeFileInfo[id].newFileToResourceList]; 753 } 754 for (const id of this.cachedFiles) { 755 fileCacheInfo[id].fileToResourceList = [...fileCacheInfo[id].fileToResourceList]; 756 } 757 this.resourceToFile = resourceToFile as {[resource: string]: Set<string>}; 758 for (const resource in resourceToFile) { 759 resourceToFile[resource] = [...resourceToFile[resource]]; 760 } 761 cache.set('fileCacheInfo', fileCacheInfo); 762 cache.set('resourceListCacheInfo', [...this.resourceList]); 763 cache.set('resourceToFileCacheInfo', resourceToFile); 764 } else { 765 const cacheInfo: {[id: string]: fileInfo} = cache.get('fileCacheInfo') || {}; 766 for (const id of this.transformedFiles) { 767 cacheInfo[id] = this.wholeFileInfo[id].fileInfo; 768 } 769 cache.set('fileCacheInfo', cacheInfo); 770 } 771 } 772 773 updateResourceList(resource: string) { 774 this.resourceList.add(resource); 775 } 776 777 compareResourceDiff() { 778 // delete resource 779 for (const resource of this.lastResourceList) { 780 if (!this.resourceList.has(resource) && this.resourceToFile[resource]) { 781 this.resourceToFile[resource].forEach(file => { 782 this.shouldInvalidFiles.add(file); 783 }); 784 } 785 } 786 // create resource 787 for (const resource of this.resourceList) { 788 if (!this.resourceToFile[resource]) { 789 this.resourceToFile[resource] = new Set(); 790 } 791 if (!this.lastResourceList.has(resource)) { 792 this.resourceToFile[resource].forEach(file => { 793 this.shouldInvalidFiles.add(file); 794 }); 795 } 796 } 797 } 798 799 collectResourceInFile(resource: string, file: string) { 800 this.wholeFileInfo[file].newFileToResourceList.add(resource); 801 } 802 803 clearCollectedInfo(cache) { 804 this.buildStart = false; 805 this.resourceTableChanged = false; 806 this.saveCacheFileInfo(cache); 807 this.transformedFiles = new Set(); 808 this.cachedFiles = []; 809 this.lastResourceList = new Set([...this.resourceList]); 810 this.shouldInvalidFiles.clear(); 811 this.resourcesArr.clear(); 812 } 813 setCurrentArkTsFile(): void { 814 this.currentArkTsFile = new SpecialArkTSFileInfo(); 815 } 816 getCurrentArkTsFile(): SpecialArkTSFileInfo { 817 return this.currentArkTsFile; 818 } 819} 820 821export const storedFileInfo: ProcessFileInfo = new ProcessFileInfo(); 822 823export interface fileInfo extends tsFileInfo { 824 hasEntry: boolean; // Has @Entry decorator or not 825} 826 827export interface tsFileInfo { 828 fileToResourceList: Set<string> | string[]; // How much Resource is used 829} 830 831// Save single TS file information 832class TSFileInfo { 833 fileInfo: tsFileInfo = { 834 fileToResourceList: new Set() 835 } 836 newFileToResourceList: Set<string> = new Set() 837 constructor(cacheInfo: fileInfo, etsFile?: boolean) { 838 if (!etsFile) { 839 this.fileInfo = cacheInfo || this.fileInfo; 840 } 841 } 842} 843 844// Save single ArkTS file information 845class SpecialArkTSFileInfo extends TSFileInfo { 846 fileInfo: fileInfo = { 847 hasEntry: false, 848 fileToResourceList: new Set() 849 } 850 recycleComponents: Set<string> = new Set([]); 851 compFromDETS: Set<string> = new Set(); 852 animatableExtendAttribute: Map<string, Set<string>> = new Map(); 853 854 constructor(cacheInfo?: fileInfo) { 855 super(cacheInfo, true); 856 this.fileInfo = cacheInfo || this.fileInfo; 857 } 858 859 get hasEntry() { 860 return this.fileInfo.hasEntry; 861 } 862 set hasEntry(value: boolean) { 863 this.fileInfo.hasEntry = value; 864 } 865} 866 867export function setChecker(): void { 868 if (globalProgram.program) { 869 globalProgram.checker = globalProgram.program.getTypeChecker(); 870 } else if (globalProgram.watchProgram) { 871 globalProgram.checker = globalProgram.watchProgram.getCurrentProgram().getProgram().getTypeChecker(); 872 } 873} 874export interface ExtendResult { 875 decoratorName: string; 876 componentName: string; 877} 878 879export function resourcesRawfile(rawfilePath: string, resourcesArr: Set<string>, resourceName: string = ''): void { 880 if (fs.existsSync(process.env.rawFileResource) && fs.statSync(rawfilePath).isDirectory()) { 881 const files: string[] = fs.readdirSync(rawfilePath); 882 files.forEach((file: string) => { 883 if (fs.statSync(path.join(rawfilePath, file)).isDirectory()) { 884 resourcesRawfile(path.join(rawfilePath, file), resourcesArr, resourceName ? resourceName + '/' + file : file); 885 } else { 886 if (resourceName) { 887 resourcesArr.add(resourceName + '/' + file); 888 } else { 889 resourcesArr.add(file); 890 } 891 } 892 }); 893 } 894} 895 896export function differenceResourcesRawfile(oldRawfile: Set<string>, newRawfile: Set<string>): boolean { 897 if (oldRawfile.size !== 0 && oldRawfile.size === newRawfile.size) { 898 for (const singleRawfiles of oldRawfile.values()) { 899 if (!newRawfile.has(singleRawfiles)) { 900 return true; 901 } 902 } 903 return false; 904 } else if (oldRawfile.size === 0 && oldRawfile.size === newRawfile.size) { 905 return false; 906 } else { 907 return true; 908 } 909} 910