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 652// Global Information & Method 653class ProcessFileInfo { 654 buildStart: boolean = true; 655 wholeFileInfo: {[id: string]: SpecialArkTSFileInfo | TSFileInfo} = {}; // Save ArkTS & TS file's infomation 656 transformedFiles: Set<string> = new Set(); // ArkTS & TS Files which should be transformed in this compilation 657 cachedFiles: string[] = []; // ArkTS & TS Files which should not be transformed in this compilation 658 shouldHaveEntry: string[] = []; // Which file should have @Entry decorator 659 resourceToFile: {[resource: string]: Set<string>} = {}; // Resource is used by which file 660 lastResourceList: Set<string> = new Set(); 661 resourceList: Set<string> = new Set(); // Whole project resource 662 shouldInvalidFiles: Set<string> = new Set(); 663 resourceTableChanged: boolean = false; 664 currentArkTsFile: SpecialArkTSFileInfo; 665 reUseProgram: boolean = false; 666 resourcesArr: Set<string> = new Set(); 667 lastResourcesSet: Set<string> = new Set(); 668 669 addGlobalCacheInfo(resourceListCacheInfo: string[], 670 resourceToFileCacheInfo: {[resource: string]: Set<string>}) { 671 if (this.buildStart) { 672 for (const element in resourceToFileCacheInfo) { 673 this.resourceToFile[element] = new Set(resourceToFileCacheInfo[element]); 674 } 675 this.lastResourceList = new Set(resourceListCacheInfo); 676 } 677 if (this.resourceTableChanged) { 678 this.compareResourceDiff(); 679 } 680 } 681 682 addFileCacheInfo(id: string, fileCacheInfo: fileInfo) { 683 if (fileCacheInfo && process.env.compileMode === 'moduleJson') { 684 if (Array.isArray(fileCacheInfo.fileToResourceList)) { 685 fileCacheInfo.fileToResourceList = new Set(fileCacheInfo.fileToResourceList); 686 } else { 687 fileCacheInfo.fileToResourceList = new Set(); 688 } 689 } 690 if (id.match(/(?<!\.d)\.(ets)$/)) { 691 this.wholeFileInfo[id] = new SpecialArkTSFileInfo(fileCacheInfo); 692 } else if (id.match(/(?<!\.d)\.(ts)$/) && process.env.compileMode === 'moduleJson') { 693 this.wholeFileInfo[id] = new TSFileInfo(fileCacheInfo); 694 } 695 } 696 697 collectTransformedFiles(id: string) { 698 if (id.match(process.env.compileMode === 'moduleJson' ? /(?<!\.d)\.(ets|ts)$/ : /(?<!\.d)\.(ets)$/)) { 699 this.transformedFiles.add(id); 700 } 701 } 702 703 collectCachedFiles(id: string) { 704 if (id.match(process.env.compileMode === 'moduleJson' ? /(?<!\.d)\.(ets|ts)$/ : /(?<!\.d)\.(ets)$/)) { 705 this.cachedFiles.push(id); 706 } 707 } 708 709 judgeShouldHaveEntryFiles(entryFileWithoutEntryDecorator: string[]): void { 710 this.shouldHaveEntry = Object.values(projectConfig.entryObj as string[]).filter((item) => { 711 return !entryFileWithoutEntryDecorator.includes(item.toLowerCase()) && item.match(/(?<!\.d)\.(ets)$/); 712 }); 713 } 714 715 saveCacheFileInfo(cache) { 716 if (process.env.compileMode === 'moduleJson') { 717 const fileCacheInfo: {[id: string]: fileInfo | tsFileInfo} = cache.get('fileCacheInfo') || {}; 718 const resourceToFileCacheInfo = cache.get('resourceToFileCacheInfo') || {}; 719 for (const i in resourceToFileCacheInfo) { 720 resourceToFileCacheInfo[i] = new Set(resourceToFileCacheInfo[i]); 721 } 722 const resourceToFile: {[resource: string]: Set<string> | string[]} = Object.assign(resourceToFileCacheInfo, this.resourceToFile); 723 for (const id of this.transformedFiles) { 724 fileCacheInfo[id] = this.wholeFileInfo[id].fileInfo; 725 for (const resource of this.wholeFileInfo[id].newFileToResourceList) { 726 if (!(fileCacheInfo[id].fileToResourceList as Set<string>).has(resource)) { 727 if (!resourceToFile[resource]) { 728 resourceToFile[resource] = new Set(); 729 } 730 (resourceToFile[resource] as Set<string>).add(id); 731 } 732 } 733 for (const resource of fileCacheInfo[id].fileToResourceList) { 734 if (!this.wholeFileInfo[id].newFileToResourceList.has(resource)) { 735 (resourceToFile[resource] as Set<string>).delete(id); 736 } 737 } 738 fileCacheInfo[id].fileToResourceList = [...this.wholeFileInfo[id].newFileToResourceList]; 739 } 740 for (const id of this.cachedFiles) { 741 fileCacheInfo[id].fileToResourceList = [...fileCacheInfo[id].fileToResourceList]; 742 } 743 this.resourceToFile = resourceToFile as {[resource: string]: Set<string>}; 744 for (const resource in resourceToFile) { 745 resourceToFile[resource] = [...resourceToFile[resource]]; 746 } 747 cache.set('fileCacheInfo', fileCacheInfo); 748 cache.set('resourceListCacheInfo', [...this.resourceList]); 749 cache.set('resourceToFileCacheInfo', resourceToFile); 750 } else { 751 const cacheInfo: {[id: string]: fileInfo} = cache.get('fileCacheInfo') || {}; 752 for (const id of this.transformedFiles) { 753 cacheInfo[id] = this.wholeFileInfo[id].fileInfo; 754 } 755 cache.set('fileCacheInfo', cacheInfo); 756 } 757 } 758 759 updateResourceList(resource: string) { 760 this.resourceList.add(resource); 761 } 762 763 compareResourceDiff() { 764 // delete resource 765 for (const resource of this.lastResourceList) { 766 if (!this.resourceList.has(resource) && this.resourceToFile[resource]) { 767 this.resourceToFile[resource].forEach(file => { 768 this.shouldInvalidFiles.add(file); 769 }); 770 } 771 } 772 // create resource 773 for (const resource of this.resourceList) { 774 if (!this.resourceToFile[resource]) { 775 this.resourceToFile[resource] = new Set(); 776 } 777 if (!this.lastResourceList.has(resource)) { 778 this.resourceToFile[resource].forEach(file => { 779 this.shouldInvalidFiles.add(file); 780 }); 781 } 782 } 783 } 784 785 collectResourceInFile(resource: string, file: string) { 786 this.wholeFileInfo[file].newFileToResourceList.add(resource); 787 } 788 789 clearCollectedInfo(cache) { 790 this.buildStart = false; 791 this.resourceTableChanged = false; 792 this.saveCacheFileInfo(cache); 793 this.transformedFiles = new Set(); 794 this.cachedFiles = []; 795 this.lastResourceList = new Set([...this.resourceList]); 796 this.shouldInvalidFiles.clear(); 797 this.resourcesArr.clear(); 798 } 799 setCurrentArkTsFile(): void { 800 this.currentArkTsFile = new SpecialArkTSFileInfo(); 801 } 802 getCurrentArkTsFile(): SpecialArkTSFileInfo { 803 return this.currentArkTsFile; 804 } 805} 806 807export const storedFileInfo: ProcessFileInfo = new ProcessFileInfo(); 808 809export interface fileInfo extends tsFileInfo { 810 hasEntry: boolean; // Has @Entry decorator or not 811} 812 813export interface tsFileInfo { 814 fileToResourceList: Set<string> | string[]; // How much Resource is used 815} 816 817// Save single TS file information 818class TSFileInfo { 819 fileInfo: tsFileInfo = { 820 fileToResourceList: new Set() 821 } 822 newFileToResourceList: Set<string> = new Set() 823 constructor(cacheInfo: fileInfo, etsFile?: boolean) { 824 if (!etsFile) { 825 this.fileInfo = cacheInfo || this.fileInfo; 826 } 827 } 828} 829 830// Save single ArkTS file information 831class SpecialArkTSFileInfo extends TSFileInfo { 832 fileInfo: fileInfo = { 833 hasEntry: false, 834 fileToResourceList: new Set() 835 } 836 recycleComponents: Set<string> = new Set([]); 837 compFromDETS: Set<string> = new Set(); 838 animatableExtendAttribute: Map<string, Set<string>> = new Map(); 839 840 constructor(cacheInfo?: fileInfo) { 841 super(cacheInfo, true); 842 this.fileInfo = cacheInfo || this.fileInfo; 843 } 844 845 get hasEntry() { 846 return this.fileInfo.hasEntry; 847 } 848 set hasEntry(value: boolean) { 849 this.fileInfo.hasEntry = value; 850 } 851} 852 853export function setChecker(): void { 854 if (globalProgram.program) { 855 globalProgram.checker = globalProgram.program.getTypeChecker(); 856 } else if (globalProgram.watchProgram) { 857 globalProgram.checker = globalProgram.watchProgram.getCurrentProgram().getProgram().getTypeChecker(); 858 } 859} 860export interface ExtendResult { 861 decoratorName: string; 862 componentName: string; 863} 864 865export function resourcesRawfile(rawfilePath: string, resourcesArr: Set<string>, resourceName: string = ''): void { 866 if (fs.existsSync(process.env.rawFileResource) && fs.statSync(rawfilePath).isDirectory()) { 867 const files: string[] = fs.readdirSync(rawfilePath); 868 files.forEach((file: string) => { 869 if (fs.statSync(path.join(rawfilePath, file)).isDirectory()) { 870 resourcesRawfile(path.join(rawfilePath, file), resourcesArr, file); 871 } else { 872 if (resourceName) { 873 resourcesArr.add(resourceName + '/' + file); 874 } else { 875 resourcesArr.add(file); 876 } 877 } 878 }); 879 } 880} 881 882export function differenceResourcesRawfile(oldRawfile: Set<string>, newRawfile: Set<string>): boolean { 883 if (oldRawfile.size !== 0 && oldRawfile.size === newRawfile.size) { 884 for (const singleRawfiles of oldRawfile.values()) { 885 if (!newRawfile.has(singleRawfiles)) { 886 return true; 887 } 888 } 889 return false; 890 } else if (oldRawfile.size === 0 && oldRawfile.size === newRawfile.size) { 891 return false; 892 } else { 893 return true; 894 } 895} 896