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 type { Hash } from 'crypto'; 29import { 30 AUXILIARY, 31 EXTNAME_ETS, 32 EXTNAME_JS, 33 MAIN, 34 FAIL, 35 TEMPORARY, 36 ESMODULE, 37 $$, 38 EXTEND_DECORATORS, 39 COMPONENT_EXTEND_DECORATOR, 40 COMPONENT_ANIMATABLE_EXTEND_DECORATOR, 41 COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU, 42 GET_SHARED, 43 COMPONENT_CONSTRUCTOR_UNDEFINED, 44 USE_SHARED_STORAGE, 45 STORAGE 46} from './pre_define'; 47 48export enum LogType { 49 ERROR = 'ERROR', 50 WARN = 'WARN', 51 NOTE = 'NOTE' 52} 53export const TEMPORARYS: string = 'temporarys'; 54export const BUILD: string = 'build'; 55export const SRC_MAIN: string = 'src/main'; 56 57const red: string = '\u001b[31m'; 58const reset: string = '\u001b[39m'; 59 60const WINDOWS: string = 'Windows_NT'; 61const LINUX: string = 'Linux'; 62const MAC: string = 'Darwin'; 63 64export interface LogInfo { 65 type: LogType, 66 message: string, 67 pos?: number, 68 line?: number, 69 column?: number, 70 fileName?: string 71} 72 73export const repeatLog: Map<string, LogInfo> = new Map(); 74 75export class FileLog { 76 private _sourceFile: ts.SourceFile | undefined; 77 private _errors: LogInfo[] = []; 78 79 public get sourceFile() { 80 return this._sourceFile; 81 } 82 83 public set sourceFile(newValue: ts.SourceFile) { 84 this._sourceFile = newValue; 85 } 86 87 public get errors() { 88 return this._errors; 89 } 90 91 public set errors(newValue: LogInfo[]) { 92 this._errors = newValue; 93 } 94 95 public cleanUp(): void { 96 this._sourceFile = undefined; 97 this._errors = []; 98 } 99} 100 101export function emitLogInfo(loader: any, infos: LogInfo[], fastBuild: boolean = false, 102 resourcePath: string = null): void { 103 if (infos && infos.length) { 104 infos.forEach((item) => { 105 switch (item.type) { 106 case LogType.ERROR: 107 fastBuild ? loader.error('\u001b[31m' + getMessage(item.fileName || resourcePath, item, true)) : 108 loader.emitError(getMessage(item.fileName || loader.resourcePath, item)); 109 break; 110 case LogType.WARN: 111 fastBuild ? loader.warn('\u001b[33m' + getMessage(item.fileName || resourcePath, item, true)) : 112 loader.emitWarning(getMessage(item.fileName || loader.resourcePath, item)); 113 break; 114 case LogType.NOTE: 115 fastBuild ? loader.info('\u001b[34m' + getMessage(item.fileName || resourcePath, item, true)) : 116 loader.emitWarning(getMessage(loader.resourcePath, item)); 117 break; 118 } 119 }); 120 } 121} 122 123export function addLog(type: LogType, message: string, pos: number, log: LogInfo[], 124 sourceFile: ts.SourceFile) { 125 const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(pos); 126 log.push({ 127 type: type, 128 message: message, 129 line: posOfNode.line + 1, 130 column: posOfNode.character + 1, 131 fileName: sourceFile.fileName 132 }); 133} 134 135export function getMessage(fileName: string, info: LogInfo, fastBuild: boolean = false): string { 136 let message: string; 137 if (info.line && info.column) { 138 message = `BUILD${info.type} File: ${fileName}:${info.line}:${info.column}\n ${info.message}`; 139 } else { 140 message = `BUILD${info.type} File: ${fileName}\n ${info.message}`; 141 } 142 if (fastBuild) { 143 message = message.replace(/^BUILD/, 'ArkTS:'); 144 } 145 return message; 146} 147 148export function getTransformLog(transformLog: FileLog): LogInfo[] { 149 const sourceFile: ts.SourceFile = transformLog.sourceFile; 150 const logInfos: LogInfo[] = transformLog.errors.map((item) => { 151 if (item.pos) { 152 if (!item.column || !item.line) { 153 const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(item.pos); 154 item.line = posOfNode.line + 1; 155 item.column = posOfNode.character + 1; 156 } 157 } else { 158 item.line = item.line || undefined; 159 item.column = item.column || undefined; 160 } 161 if (!item.fileName) { 162 item.fileName = sourceFile.fileName; 163 } 164 return item; 165 }); 166 return logInfos; 167} 168 169class ComponentInfo { 170 private _id: number = 0; 171 private _componentNames: Set<string> = new Set(['ForEach']); 172 public set id(id: number) { 173 this._id = id; 174 } 175 public get id() { 176 return this._id; 177 } 178 public set componentNames(componentNames: Set<string>) { 179 this._componentNames = componentNames; 180 } 181 public get componentNames() { 182 return this._componentNames; 183 } 184} 185 186export let componentInfo: ComponentInfo = new ComponentInfo(); 187 188export function hasDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration | 189 ts.StructDeclaration | ts.ClassDeclaration, decortorName: string, 190 customBuilder: ts.Decorator[] = null, log: LogInfo[] = null): boolean { 191 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 192 if (decorators && decorators.length) { 193 const extendResult = { 194 Extend: false, 195 AnimatableExtend: false 196 }; 197 for (let i = 0; i < decorators.length; i++) { 198 const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').replace(/\s*/g, '').trim(); 199 if (log && EXTEND_DECORATORS.includes(decortorName)) { 200 if (originalDecortor === COMPONENT_EXTEND_DECORATOR) { 201 extendResult.Extend = true; 202 } 203 if (originalDecortor === COMPONENT_ANIMATABLE_EXTEND_DECORATOR) { 204 extendResult.AnimatableExtend = true; 205 } 206 } else { 207 if (originalDecortor === decortorName) { 208 if (customBuilder) { 209 customBuilder.push(...decorators.slice(i + 1), ...decorators.slice(0, i)); 210 } 211 return true; 212 } 213 } 214 } 215 if (log && extendResult.Extend && extendResult.AnimatableExtend) { 216 log.push({ 217 type: LogType.ERROR, 218 message: `The function can not be decorated by '@Extend' and '@AnimatableExtend' at the same time.`, 219 pos: node.getStart() 220 }); 221 } 222 return (decortorName === COMPONENT_EXTEND_DECORATOR && extendResult.Extend) || 223 (decortorName === COMPONENT_ANIMATABLE_EXTEND_DECORATOR && extendResult.AnimatableExtend); 224 } 225 return false; 226} 227 228const STATEMENT_EXPECT: number = 1128; 229const SEMICOLON_EXPECT: number = 1005; 230const STATESTYLES_EXPECT: number = 1003; 231export const IGNORE_ERROR_CODE: number[] = [STATEMENT_EXPECT, SEMICOLON_EXPECT, STATESTYLES_EXPECT]; 232 233export function readFile(dir: string, utFiles: string[]): void { 234 try { 235 const files: string[] = fs.readdirSync(dir); 236 files.forEach((element) => { 237 const filePath: string = path.join(dir, element); 238 const status: fs.Stats = fs.statSync(filePath); 239 if (status.isDirectory()) { 240 readFile(filePath, utFiles); 241 } else { 242 utFiles.push(filePath); 243 } 244 }); 245 } catch (e) { 246 console.error(red, 'ArkTS ERROR: ' + e, reset); 247 } 248} 249 250export function circularFile(inputPath: string, outputPath: string): void { 251 if (!inputPath || !outputPath) { 252 return; 253 } 254 fs.readdir(inputPath, function (err, files) { 255 if (!files) { 256 return; 257 } 258 files.forEach(file => { 259 const inputFile: string = path.resolve(inputPath, file); 260 const outputFile: string = path.resolve(outputPath, file); 261 const fileStat: fs.Stats = fs.statSync(inputFile); 262 if (fileStat.isFile()) { 263 copyFile(inputFile, outputFile); 264 } else { 265 circularFile(inputFile, outputFile); 266 } 267 }); 268 }); 269} 270 271function copyFile(inputFile: string, outputFile: string): void { 272 try { 273 const parent: string = path.join(outputFile, '..'); 274 if (!(fs.existsSync(parent) && fs.statSync(parent).isDirectory())) { 275 mkDir(parent); 276 } 277 if (fs.existsSync(outputFile)) { 278 return; 279 } 280 const readStream: fs.ReadStream = fs.createReadStream(inputFile); 281 const writeStream: fs.WriteStream = fs.createWriteStream(outputFile); 282 readStream.pipe(writeStream); 283 readStream.on('close', function () { 284 writeStream.end(); 285 }); 286 } catch (err) { 287 throw err.message; 288 } 289} 290 291export function mkDir(path_: string): void { 292 const parent: string = path.join(path_, '..'); 293 if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { 294 mkDir(parent); 295 } 296 fs.mkdirSync(path_); 297} 298 299export function toUnixPath(data: string): string { 300 if (/^win/.test(require('os').platform())) { 301 const fileTmps: string[] = data.split(path.sep); 302 const newData: string = path.posix.join(...fileTmps); 303 return newData; 304 } 305 return data; 306} 307 308export function tryToLowerCasePath(filePath: string): string { 309 return toUnixPath(filePath).toLowerCase(); 310} 311 312export function toHashData(path: string): string { 313 const content: string = fs.readFileSync(path).toString(); 314 const hash: Hash = createHash('sha256'); 315 hash.update(content); 316 return hash.digest('hex'); 317} 318 319export function writeFileSync(filePath: string, content: string): void { 320 if (!fs.existsSync(filePath)) { 321 const parent: string = path.join(filePath, '..'); 322 if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { 323 mkDir(parent); 324 } 325 } 326 fs.writeFileSync(filePath, content); 327} 328export function genLoaderOutPathOfHar(filePath: string, cachePath: string, buildPath: string, moduleRootPath: string, projectRootPath): string { 329 filePath = toUnixPath(filePath); 330 buildPath = toUnixPath(buildPath); 331 const cacheRootPath: string = toUnixPath(cachePath); 332 const moduleName = toUnixPath(moduleRootPath).replace(toUnixPath(projectRootPath), ''); 333 const relativeFilePath: string = filePath.replace(cacheRootPath, '').replace(moduleName, ''); 334 const output: string = path.join(buildPath, relativeFilePath); 335 return output; 336} 337 338export function genTemporaryPath(filePath: string, projectPath: string, buildPath: string, 339 projectConfig: Object, buildInHar: boolean = false): string { 340 filePath = toUnixPath(filePath).replace(/\.[cm]js$/, EXTNAME_JS); 341 projectPath = toUnixPath(projectPath); 342 343 if (process.env.compileTool === 'rollup') { 344 const projectRootPath: string = toUnixPath(buildInHar ? projectPath : projectConfig.projectRootPath); 345 const relativeFilePath: string = filePath.replace(projectRootPath, ''); 346 const output: string = path.join(buildPath, relativeFilePath); 347 return output; 348 } 349 350 if (isPackageModulesFile(filePath, projectConfig)) { 351 const packageDir: string = projectConfig.packageDir; 352 const fakePkgModulesPath: string = toUnixPath(path.join(projectConfig.projectRootPath, packageDir)); 353 let output: string = ''; 354 if (filePath.indexOf(fakePkgModulesPath) === -1) { 355 const hapPath: string = toUnixPath(projectConfig.projectRootPath); 356 const tempFilePath: string = filePath.replace(hapPath, ''); 357 const relativeFilePath: string = tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1); 358 output = path.join(buildPath, buildInHar ? '' : TEMPORARY, packageDir, MAIN, relativeFilePath); 359 } else { 360 output = filePath.replace(fakePkgModulesPath, 361 path.join(buildPath, buildInHar ? '' : TEMPORARY, packageDir, AUXILIARY)); 362 } 363 return output; 364 } 365 366 if (filePath.indexOf(projectPath) !== -1) { 367 const relativeFilePath: string = filePath.replace(projectPath, ''); 368 const output: string = path.join(buildPath, buildInHar ? '' : TEMPORARY, relativeFilePath); 369 return output; 370 } 371 372 return ''; 373} 374 375export function isPackageModulesFile(filePath: string, projectConfig: Object): boolean { 376 filePath = toUnixPath(filePath); 377 const hapPath: string = toUnixPath(projectConfig.projectRootPath); 378 const tempFilePath: string = filePath.replace(hapPath, ''); 379 const packageDir: string = projectConfig.packageDir; 380 if (tempFilePath.indexOf(packageDir) !== -1) { 381 const fakePkgModulesPath: string = toUnixPath(path.resolve(projectConfig.projectRootPath, packageDir)); 382 if (filePath.indexOf(fakePkgModulesPath) !== -1) { 383 return true; 384 } 385 if (projectConfig.modulePathMap) { 386 for (const key in projectConfig.modulePathMap) { 387 const value: string = projectConfig.modulePathMap[key]; 388 const fakeModulePkgModulesPath: string = toUnixPath(path.resolve(value, packageDir)); 389 if (filePath.indexOf(fakeModulePkgModulesPath) !== -1) { 390 return true; 391 } 392 } 393 } 394 } 395 396 return false; 397} 398 399export interface GeneratedFileInHar { 400 sourcePath: string; 401 sourceCachePath?: string; 402 obfuscatedSourceCachePath?: string; 403 originalDeclarationCachePath?: string; 404 originalDeclarationContent?: string; 405 obfuscatedDeclarationCachePath?: string; 406} 407 408export const harFilesRecord: Map<string, GeneratedFileInHar> = new Map(); 409 410export function generateSourceFilesInHar(sourcePath: string, sourceContent: string, suffix: string, projectConfig: any) { 411 // compileShared: compile shared har of project 412 let jsFilePath: string = genTemporaryPath(sourcePath, 413 projectConfig.compileShared ? projectConfig.projectRootPath : projectConfig.moduleRootPath, 414 projectConfig.compileShared ? path.resolve(projectConfig.aceModuleBuild, '../etsFortgz') : projectConfig.cachePath, 415 projectConfig, projectConfig.compileShared); 416 if (!jsFilePath.match(new RegExp(projectConfig.packageDir))) { 417 jsFilePath = jsFilePath.replace(/\.ets$/, suffix).replace(/\.ts$/, suffix); 418 if (projectConfig.obfuscateHarType === 'uglify' && suffix === '.js') { 419 sourceContent = uglifyJS.minify(sourceContent).code; 420 } 421 // collect the declaration files for obfuscation 422 if (projectConfig.compileMode === ESMODULE && (/\.d\.e?ts$/).test(jsFilePath)) { 423 sourcePath = toUnixPath(sourcePath); 424 const genFilesInHar: GeneratedFileInHar = { sourcePath: sourcePath, originalDeclarationCachePath: jsFilePath, originalDeclarationContent: sourceContent }; 425 harFilesRecord.set(sourcePath, genFilesInHar); 426 return; 427 } else { 428 mkdirsSync(path.dirname(jsFilePath)); 429 } 430 fs.writeFileSync(jsFilePath, sourceContent); 431 } 432} 433 434export function mkdirsSync(dirname: string): boolean { 435 if (fs.existsSync(dirname)) { 436 return true; 437 } else if (mkdirsSync(path.dirname(dirname))) { 438 fs.mkdirSync(dirname); 439 return true; 440 } 441 442 return false; 443} 444 445export function nodeLargeOrEqualTargetVersion(targetVersion: number): boolean { 446 const currentNodeVersion: number = parseInt(process.versions.node.split('.')[0]); 447 if (currentNodeVersion >= targetVersion) { 448 return true; 449 } 450 451 return false; 452} 453 454export function removeDir(dirName: string): void { 455 if (fs.existsSync(dirName)) { 456 if (nodeLargeOrEqualTargetVersion(16)) { 457 fs.rmSync(dirName, { recursive: true }); 458 } else { 459 fs.rmdirSync(dirName, { recursive: true }); 460 } 461 } 462} 463 464export function parseErrorMessage(message: string): string { 465 const messageArrary: string[] = message.split('\n'); 466 let logContent: string = ''; 467 messageArrary.forEach(element => { 468 if (!(/^at/.test(element.trim()))) { 469 logContent = logContent + element + '\n'; 470 } 471 }); 472 return logContent; 473} 474 475export function isWindows(): boolean { 476 return os.type() === WINDOWS; 477} 478 479export function isLinux(): boolean { 480 return os.type() === LINUX; 481} 482 483export function isMac(): boolean { 484 return os.type() === MAC; 485} 486 487export function maxFilePathLength(): number { 488 if (isWindows()) { 489 return 32766; 490 } else if (isLinux()) { 491 return 4095; 492 } else if (isMac()) { 493 return 1016; 494 } else { 495 return -1; 496 } 497} 498 499export function validateFilePathLength(filePath: string, logger: Object): boolean { 500 if (maxFilePathLength() < 0) { 501 logger.error(red, 'Unknown OS platform', reset); 502 process.exitCode = FAIL; 503 return false; 504 } else if (filePath.length > 0 && filePath.length <= maxFilePathLength()) { 505 return true; 506 } else if (filePath.length > maxFilePathLength()) { 507 logger.error(red, `The length of ${filePath} exceeds the limitation of current platform, which is ` + 508 `${maxFilePathLength()}. Please try moving the project folder to avoid deeply nested file path and try again`, 509 reset); 510 process.exitCode = FAIL; 511 return false; 512 } else { 513 logger.error(red, 'Validate file path failed', reset); 514 process.exitCode = FAIL; 515 return false; 516 } 517} 518 519export function validateFilePathLengths(filePaths: Array<string>, logger: any): boolean { 520 filePaths.forEach((filePath) => { 521 if (!validateFilePathLength(filePath, logger)) { 522 return false; 523 } 524 }); 525 return true; 526} 527 528export function unlinkSync(filePath: string): void { 529 if (fs.existsSync(filePath)) { 530 fs.unlinkSync(filePath); 531 } 532} 533 534export function getExtensionIfUnfullySpecifiedFilepath(filePath: string): string { 535 if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { 536 return ""; 537 } 538 539 let extension: string = EXTNAME_ETS; 540 if (fs.existsSync(filePath + '.ts') && fs.statSync(filePath + '.ts').isFile()) { 541 extension = '.ts'; 542 } else if (fs.existsSync(filePath + '.d.ts') && fs.statSync(filePath + '.d.ts').isFile()) { 543 extension = '.d.ts'; 544 } else if (fs.existsSync(filePath + '.d.ets') && fs.statSync(filePath + '.d.ets').isFile()) { 545 extension = '.d.ets'; 546 } else if (fs.existsSync(filePath + '.js') && fs.statSync(filePath + '.js').isFile()) { 547 extension = '.js'; 548 } else if (fs.existsSync(filePath + '.json') && fs.statSync(filePath + '.json').isFile()) { 549 extension = '.json'; 550 } 551 552 return extension; 553} 554 555export function shouldWriteChangedList(watchModifiedFiles: string[], 556 watchRemovedFiles: string[]): boolean { 557 if (projectConfig.compileMode === ESMODULE && process.env.watchMode === 'true' && !projectConfig.isPreview && 558 projectConfig.changedFileList && (watchRemovedFiles.length + watchModifiedFiles.length)) { 559 if (process.env.compileTool !== 'rollup') { 560 if (!(watchModifiedFiles.length === 1 && 561 watchModifiedFiles[0] === projectConfig.projectPath && !watchRemovedFiles.length)) { 562 return true; 563 } else { 564 return false; 565 } 566 } 567 return true; 568 } 569 return false; 570} 571 572interface HotReloadIncrementalTime { 573 hotReloadIncrementalStartTime: string; 574 hotReloadIncrementalEndTime: string; 575} 576 577export const hotReloadIncrementalTime: HotReloadIncrementalTime = { 578 hotReloadIncrementalStartTime: '', 579 hotReloadIncrementalEndTime: '' 580}; 581 582interface FilesObj { 583 modifiedFiles: string[], 584 removedFiles: string[] 585} 586 587let allModifiedFiles: Set<string> = new Set(); 588 589export function getHotReloadFiles(watchModifiedFiles: string[], 590 watchRemovedFiles: string[], hotReloadSupportFiles: Set<string>): FilesObj { 591 hotReloadIncrementalTime.hotReloadIncrementalStartTime = new Date().getTime().toString(); 592 watchRemovedFiles = watchRemovedFiles.map(file => path.relative(projectConfig.projectPath, file)); 593 allModifiedFiles = new Set([...allModifiedFiles, ...watchModifiedFiles 594 .filter(file => fs.statSync(file).isFile() && 595 (hotReloadSupportFiles.has(file) || !['.ets', '.ts', '.js'].includes(path.extname(file)))) 596 .map(file => path.relative(projectConfig.projectPath, file))] 597 .filter(file => !watchRemovedFiles.includes(file))); 598 return { 599 modifiedFiles: [...allModifiedFiles], 600 removedFiles: [...watchRemovedFiles] 601 }; 602} 603 604export function getResolveModules(projectPath: string, faMode: boolean): string[] { 605 if (faMode) { 606 return [ 607 path.resolve(projectPath, '../../../../../'), 608 path.resolve(projectPath, '../../../../' + projectConfig.packageDir), 609 path.resolve(projectPath, '../../../../../' + projectConfig.packageDir), 610 path.resolve(projectPath, '../../') 611 ]; 612 } else { 613 return [ 614 path.resolve(projectPath, '../../../../'), 615 path.resolve(projectPath, '../../../' + projectConfig.packageDir), 616 path.resolve(projectPath, '../../../../' + projectConfig.packageDir), 617 path.resolve(projectPath, '../') 618 ]; 619 } 620} 621 622export function writeUseOSFiles(useOSFiles: Set<string>): void { 623 let info: string = ''; 624 if (!fs.existsSync(projectConfig.aceSoPath)) { 625 const parent: string = path.resolve(projectConfig.aceSoPath, '..'); 626 if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { 627 mkDir(parent); 628 } 629 } else { 630 info = fs.readFileSync(projectConfig.aceSoPath, 'utf-8') + '\n'; 631 } 632 fs.writeFileSync(projectConfig.aceSoPath, info + Array.from(useOSFiles).join('\n')); 633} 634 635 636export function writeCollectionFile(cachePath: string, appCollection: Map<string, Set<string>>, 637 allComponentsOrModules: Map<string, Array<string>>, fileName: string, allFiles: Set<string> = null, 638 widgetPath: string = undefined) { 639 for (let key of appCollection.keys()) { 640 if (appCollection.get(key).size === 0) { 641 allComponentsOrModules.delete(key); 642 continue; 643 } 644 if (allFiles && !allFiles.has(key)) { 645 continue; 646 } 647 const newKey: string = projectConfig.projectRootPath ? path.relative(projectConfig.projectRootPath, key) : key; 648 allComponentsOrModules.set(newKey, Array.from(appCollection.get(key))); 649 } 650 const content: string = JSON.stringify(Object.fromEntries(allComponentsOrModules), null, 2); 651 writeFileSync(path.resolve(cachePath, fileName), content); 652 if (widgetPath) { 653 writeFileSync(path.resolve(widgetPath, fileName), content); 654 } 655} 656 657export function getAllComponentsOrModules(allFiles: Set<string>, 658 cacheCollectionFileName: string): Map<string, Array<string>> { 659 const cacheCollectionFilePath: string = path.resolve(projectConfig.cachePath, cacheCollectionFileName); 660 const allComponentsOrModules: Map<string, Array<string>> = new Map(); 661 if (!fs.existsSync(cacheCollectionFilePath)) { 662 return allComponentsOrModules; 663 } 664 const lastComponentsOrModules = require(cacheCollectionFilePath); 665 for (let key in lastComponentsOrModules) { 666 if (allFiles.has(key)) { 667 allComponentsOrModules.set(key, lastComponentsOrModules[key]); 668 } 669 } 670 return allComponentsOrModules; 671} 672 673export function getPossibleBuilderTypeParameter(parameters: ts.ParameterDeclaration[]): string[] { 674 const parameterNames: string[] = []; 675 if (!partialUpdateConfig.builderCheck) { 676 if (isDollarParameter(parameters)) { 677 parameters[0].type.members.forEach((member) => { 678 if (member.name && ts.isIdentifier(member.name)) { 679 parameterNames.push(member.name.escapedText.toString()); 680 } 681 }); 682 } else { 683 parameters.forEach((parameter) => { 684 if (parameter.name && ts.isIdentifier(parameter.name)) { 685 parameterNames.push(parameter.name.escapedText.toString()); 686 } 687 }); 688 } 689 } 690 return parameterNames; 691} 692 693function isDollarParameter(parameters: ts.ParameterDeclaration[]): boolean { 694 return parameters.length === 1 && parameters[0].name && ts.isIdentifier(parameters[0].name) && 695 parameters[0].name.escapedText.toString() === $$ && parameters[0].type && ts.isTypeLiteralNode(parameters[0].type) && 696 parameters[0].type.members && parameters[0].type.members.length > 0; 697} 698 699interface ChildrenCacheFile { 700 fileName: string, 701 mtimeMs: number, 702} 703 704export interface CacheFile { 705 mtimeMs: number, 706 children: Array<ChildrenCacheFile>, 707} 708 709export interface RouterInfo { 710 name: string, 711 buildFunction: string, 712} 713 714// Global Information & Method 715export class ProcessFileInfo { 716 buildStart: boolean = true; 717 wholeFileInfo: { [id: string]: SpecialArkTSFileInfo | TSFileInfo } = {}; // Save ArkTS & TS file's infomation 718 transformedFiles: Set<string> = new Set(); // ArkTS & TS Files which should be transformed in this compilation 719 cachedFiles: string[] = []; // ArkTS & TS Files which should not be transformed in this compilation 720 shouldHaveEntry: string[] = []; // Which file should have @Entry decorator 721 resourceToFile: { [resource: string]: Set<string> } = {}; // Resource is used by which file 722 lastResourceList: Set<string> = new Set(); 723 resourceList: Set<string> = new Set(); // Whole project resource 724 shouldInvalidFiles: Set<string> = new Set(); 725 resourceTableChanged: boolean = false; 726 currentArkTsFile: SpecialArkTSFileInfo; 727 reUseProgram: boolean = false; 728 resourcesArr: Set<string> = new Set(); 729 lastResourcesSet: Set<string> = new Set(); 730 transformCacheFiles: { [fileName: string]: CacheFile } = {}; 731 processBuilder: boolean = false; 732 processGlobalBuilder: boolean = false; 733 builderLikeCollection: Set<string> = new Set(); 734 newTsProgram: ts.Program; 735 changeFiles: string[] = []; 736 isFirstBuild: boolean = true; 737 processForEach: number = 0; 738 processLazyForEach: number = 0; 739 isAsPageImport: boolean = false; 740 overallObjectLinkCollection: Map<string, Set<string>> = new Map(); 741 overallLinkCollection: Map<string, Set<string>> = new Map(); 742 lazyForEachInfo: { 743 forEachParameters: ts.ParameterDeclaration, 744 isDependItem: boolean 745 } = { 746 forEachParameters: null, 747 isDependItem: false 748 }; 749 routerInfo: Map<string, Array<RouterInfo>> = new Map(); 750 751 addGlobalCacheInfo(resourceListCacheInfo: string[], 752 resourceToFileCacheInfo: { [resource: string]: Set<string> }) { 753 if (this.buildStart) { 754 for (const element in resourceToFileCacheInfo) { 755 this.resourceToFile[element] = new Set(resourceToFileCacheInfo[element]); 756 } 757 this.lastResourceList = new Set(resourceListCacheInfo); 758 } 759 if (this.resourceTableChanged) { 760 this.compareResourceDiff(); 761 } 762 } 763 764 addFileCacheInfo(id: string, fileCacheInfo: fileInfo) { 765 if (fileCacheInfo && process.env.compileMode === 'moduleJson') { 766 if (Array.isArray(fileCacheInfo.fileToResourceList)) { 767 fileCacheInfo.fileToResourceList = new Set(fileCacheInfo.fileToResourceList); 768 } else { 769 fileCacheInfo.fileToResourceList = new Set(); 770 } 771 } 772 if (id.match(/(?<!\.d)\.(ets)$/)) { 773 this.wholeFileInfo[id] = new SpecialArkTSFileInfo(fileCacheInfo); 774 } else if (id.match(/(?<!\.d)\.(ts)$/) && process.env.compileMode === 'moduleJson') { 775 this.wholeFileInfo[id] = new TSFileInfo(fileCacheInfo); 776 } 777 } 778 779 collectTransformedFiles(id: string) { 780 if (id.match(process.env.compileMode === 'moduleJson' ? /(?<!\.d)\.(ets|ts)$/ : /(?<!\.d)\.(ets)$/)) { 781 this.transformedFiles.add(id); 782 } 783 } 784 785 collectCachedFiles(id: string) { 786 if (id.match(process.env.compileMode === 'moduleJson' ? /(?<!\.d)\.(ets|ts)$/ : /(?<!\.d)\.(ets)$/)) { 787 this.cachedFiles.push(id); 788 } 789 } 790 791 judgeShouldHaveEntryFiles(entryFileWithoutEntryDecorator: string[]): void { 792 this.shouldHaveEntry = Object.values(projectConfig.entryObj as string[]).filter((item) => { 793 return !entryFileWithoutEntryDecorator.includes(item.toLowerCase()) && item.match(/(?<!\.d)\.(ets)$/); 794 }); 795 } 796 797 saveCacheFileInfo(cache): void { 798 if (process.env.compileMode === 'moduleJson') { 799 const fileCacheInfo: { [id: string]: fileInfo | tsFileInfo } = cache.get('fileCacheInfo') || {}; 800 const resourceToFileCacheInfo = cache.get('resourceToFileCacheInfo') || {}; 801 for (const i in resourceToFileCacheInfo) { 802 resourceToFileCacheInfo[i] = new Set(resourceToFileCacheInfo[i]); 803 } 804 const resourceToFile: { [resource: string]: Set<string> | string[] } = Object.assign(resourceToFileCacheInfo, this.resourceToFile); 805 for (const id of this.transformedFiles) { 806 fileCacheInfo[id] = this.wholeFileInfo[id].fileInfo; 807 for (const resource of this.wholeFileInfo[id].newFileToResourceList) { 808 if (!(fileCacheInfo[id].fileToResourceList as Set<string>).has(resource)) { 809 this.resourceToFileBecomeSet(resourceToFile, resource, id); 810 } 811 } 812 for (const resource of fileCacheInfo[id].fileToResourceList) { 813 if (!this.wholeFileInfo[id].newFileToResourceList.has(resource)) { 814 (resourceToFile[resource] as Set<string>).delete(id); 815 } 816 } 817 fileCacheInfo[id].fileToResourceList = [...this.wholeFileInfo[id].newFileToResourceList]; 818 } 819 for (const id of this.cachedFiles) { 820 fileCacheInfo[id].fileToResourceList = [...fileCacheInfo[id].fileToResourceList]; 821 } 822 this.resourceToFile = resourceToFile as { [resource: string]: Set<string> }; 823 for (const resource in resourceToFile) { 824 resourceToFile[resource] = [...resourceToFile[resource]]; 825 } 826 cache.set('fileCacheInfo', fileCacheInfo); 827 cache.set('resourceListCacheInfo', [...this.resourceList]); 828 cache.set('resourceToFileCacheInfo', resourceToFile); 829 } else { 830 const cacheInfo: { [id: string]: fileInfo } = cache.get('fileCacheInfo') || {}; 831 for (const id of this.transformedFiles) { 832 cacheInfo[id] = this.wholeFileInfo[id].fileInfo; 833 } 834 cache.set('fileCacheInfo', cacheInfo); 835 } 836 } 837 838 resourceToFileBecomeSet(resourceToFile: { [resource: string]: Set<string> | string[] }, resource: string, id: string): void { 839 if (!resourceToFile[resource]) { 840 resourceToFile[resource] = new Set(); 841 } 842 if (resourceToFile[resource] instanceof Set) { 843 resourceToFile[resource].add(id); 844 } else if (Array.isArray(resourceToFile[resource])) { 845 resourceToFile[resource] = new Set(resourceToFile[resource]); 846 resourceToFile[resource].add(id); 847 } else { 848 return; 849 } 850 } 851 852 updateResourceList(resource: string) { 853 this.resourceList.add(resource); 854 } 855 856 compareResourceDiff() { 857 // delete resource 858 for (const resource of this.lastResourceList) { 859 if (!this.resourceList.has(resource) && this.resourceToFile[resource]) { 860 this.resourceToFile[resource].forEach(file => { 861 this.shouldInvalidFiles.add(file); 862 }); 863 } 864 } 865 // create resource 866 for (const resource of this.resourceList) { 867 if (!this.resourceToFile[resource]) { 868 this.resourceToFile[resource] = new Set(); 869 } 870 if (!this.lastResourceList.has(resource)) { 871 this.resourceToFile[resource].forEach(file => { 872 this.shouldInvalidFiles.add(file); 873 }); 874 } 875 } 876 } 877 878 collectResourceInFile(resource: string, file: string) { 879 if (this.wholeFileInfo[file]) { 880 this.wholeFileInfo[file].newFileToResourceList.add(resource); 881 } 882 } 883 884 clearCollectedInfo(cache) { 885 this.buildStart = false; 886 this.resourceTableChanged = false; 887 this.isAsPageImport = false; 888 this.saveCacheFileInfo(cache); 889 this.transformedFiles = new Set(); 890 this.cachedFiles = []; 891 this.lastResourceList = new Set([...this.resourceList]); 892 this.shouldInvalidFiles.clear(); 893 this.resourcesArr.clear(); 894 } 895 setCurrentArkTsFile(): void { 896 this.currentArkTsFile = new SpecialArkTSFileInfo(); 897 } 898 getCurrentArkTsFile(): SpecialArkTSFileInfo { 899 return this.currentArkTsFile; 900 } 901} 902 903export let storedFileInfo: ProcessFileInfo = new ProcessFileInfo(); 904 905export interface fileInfo extends tsFileInfo { 906 hasEntry: boolean; // Has @Entry decorator or not 907} 908 909export interface tsFileInfo { 910 fileToResourceList: Set<string> | string[]; // How much Resource is used 911} 912 913// Save single TS file information 914class TSFileInfo { 915 fileInfo: tsFileInfo = { 916 fileToResourceList: new Set() 917 }; 918 newFileToResourceList: Set<string> = new Set(); 919 constructor(cacheInfo: fileInfo, etsFile?: boolean) { 920 if (!etsFile) { 921 this.fileInfo = cacheInfo || this.fileInfo; 922 } 923 } 924} 925 926// Save single ArkTS file information 927class SpecialArkTSFileInfo extends TSFileInfo { 928 fileInfo: fileInfo = { 929 hasEntry: false, 930 fileToResourceList: new Set() 931 }; 932 recycleComponents: Set<string> = new Set([]); 933 compFromDETS: Set<string> = new Set(); 934 animatableExtendAttribute: Map<string, Set<string>> = new Map(); 935 936 constructor(cacheInfo?: fileInfo) { 937 super(cacheInfo, true); 938 this.fileInfo = cacheInfo || this.fileInfo; 939 } 940 941 get hasEntry() { 942 return this.fileInfo.hasEntry; 943 } 944 set hasEntry(value: boolean) { 945 this.fileInfo.hasEntry = value; 946 } 947} 948 949export function setChecker(): void { 950 if (globalProgram.program) { 951 globalProgram.checker = globalProgram.program.getTypeChecker(); 952 } else if (globalProgram.watchProgram) { 953 globalProgram.checker = globalProgram.watchProgram.getCurrentProgram().getProgram().getTypeChecker(); 954 } 955} 956export interface ExtendResult { 957 decoratorName: string; 958 componentName: string; 959} 960 961export function resourcesRawfile(rawfilePath: string, resourcesArr: Set<string>, resourceName: string = ''): void { 962 if (fs.existsSync(process.env.rawFileResource) && fs.statSync(rawfilePath).isDirectory()) { 963 const files: string[] = fs.readdirSync(rawfilePath); 964 files.forEach((file: string) => { 965 if (fs.statSync(path.join(rawfilePath, file)).isDirectory()) { 966 resourcesRawfile(path.join(rawfilePath, file), resourcesArr, resourceName ? resourceName + '/' + file : file); 967 } else { 968 if (resourceName) { 969 resourcesArr.add(resourceName + '/' + file); 970 } else { 971 resourcesArr.add(file); 972 } 973 } 974 }); 975 } 976} 977 978export function differenceResourcesRawfile(oldRawfile: Set<string>, newRawfile: Set<string>): boolean { 979 if (oldRawfile.size !== 0 && oldRawfile.size === newRawfile.size) { 980 for (const singleRawfiles of oldRawfile.values()) { 981 if (!newRawfile.has(singleRawfiles)) { 982 return true; 983 } 984 } 985 return false; 986 } else if (oldRawfile.size === 0 && oldRawfile.size === newRawfile.size) { 987 return false; 988 } else { 989 return true; 990 } 991} 992 993export function isString(text: unknown): text is string { 994 return typeof text === 'string'; 995} 996 997function getRollupCacheStoreKey(projectConfig: object): string { 998 let keyInfo: string[] = [projectConfig.compileSdkVersion, projectConfig.compatibleSdkVersion, projectConfig.runtimeOS, 999 projectConfig.etsLoaderPath]; 1000 return keyInfo.join('#'); 1001} 1002 1003function getRollupCacheKey(projectConfig: object): string { 1004 let isWidget: string = projectConfig.widgetCompile ? 'widget' : 'non-widget'; 1005 let ohosTestInfo: string = 'non-ohosTest'; 1006 if (projectConfig.testFrameworkPar) { 1007 ohosTestInfo = JSON.stringify(projectConfig.testFrameworkPar); 1008 } 1009 1010 let keyInfo: string[] = [projectConfig.entryModuleName, projectConfig.targetName, isWidget, ohosTestInfo, 1011 projectConfig.needCoverageInsert, projectConfig.isOhosTest]; 1012 return keyInfo.join('#'); 1013} 1014 1015function clearRollupCacheStore(cacheStoreManager: object, currentKey: string): void { 1016 if (!cacheStoreManager) { 1017 return; 1018 } 1019 1020 for (let key of cacheStoreManager.keys()) { 1021 if (key !== currentKey) { 1022 cacheStoreManager.unmount(key); 1023 } 1024 } 1025} 1026 1027export function startTimeStatisticsLocation(startTimeEvent: CompileEvent): void { 1028 if (startTimeEvent) { 1029 startTimeEvent.start(); 1030 } 1031} 1032 1033export function stopTimeStatisticsLocation(stopTimeEvent: CompileEvent): void { 1034 if (stopTimeEvent) { 1035 stopTimeEvent.stop(); 1036 } 1037} 1038export let resolveModuleNamesTime: CompileEvent; 1039export class CompilationTimeStatistics { 1040 hookEventFactory: HookEventFactoryType; 1041 createProgramTime: CompileEvent; 1042 runArkTSLinterTime: CompileEvent; 1043 diagnosticTime: CompileEvent; 1044 scriptSnapshotTime: CompileEvent; 1045 processImportTime: CompileEvent; 1046 processComponentClassTime: CompileEvent; 1047 validateEtsTime: CompileEvent; 1048 tsProgramEmitTime: CompileEvent; 1049 noSourceFileRebuildProgramTime: CompileEvent; 1050 etsTransformBuildStartTime: CompileEvent; 1051 etsTransformLoadTime: CompileEvent; 1052 constructor(share: Record<string, any>, pluginName: string, hookName: string) { 1053 if (share && share.getHookEventFactory) { 1054 if (pluginName === 'etsChecker' && hookName === 'buildStart' && share.getHookEventFactory(pluginName, hookName)) { 1055 this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); 1056 this.createProgramTime = this.hookEventFactory.createEvent('createProgram'); 1057 this.runArkTSLinterTime = this.hookEventFactory.createEvent('arkTSLinter'); 1058 this.diagnosticTime = this.hookEventFactory.createEvent('diagnostic'); 1059 this.scriptSnapshotTime = this.hookEventFactory.createEvent('scriptSnapshot'); 1060 resolveModuleNamesTime = this.hookEventFactory.createEvent('resolveModuleNames'); 1061 } else if (pluginName === 'etsTransform' && hookName === 'transform' && share.getHookEventFactory(pluginName, hookName)) { 1062 this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); 1063 this.processImportTime = this.hookEventFactory.createEvent('processImport'); 1064 this.processComponentClassTime = this.hookEventFactory.createEvent('processComponentClass'); 1065 this.validateEtsTime = this.hookEventFactory.createEvent('validateEts'); 1066 this.tsProgramEmitTime = this.hookEventFactory.createEvent('tsProgramEmit'); 1067 this.noSourceFileRebuildProgramTime = this.hookEventFactory.createEvent('noSourceFileRebuildProgram'); 1068 } else if (pluginName === 'etsTransform' && hookName === 'buildStart' && share.getHookEventFactory(pluginName, hookName)) { 1069 this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); 1070 this.etsTransformBuildStartTime = this.hookEventFactory.createEvent('etsTransformBuildStart'); 1071 } else if (pluginName === 'etsTransform' && hookName === 'load' && share.getHookEventFactory(pluginName, hookName)) { 1072 this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); 1073 this.etsTransformLoadTime = this.hookEventFactory.createEvent('etsTransformLoad'); 1074 } 1075 } 1076 } 1077} 1078 1079interface HookEventFactoryType { 1080 createEvent(name: string): CompileEvent | undefined; 1081} 1082 1083type CompileEventState = 'created' | 'beginning' | 'running' | 'failed' | 'success' | 'warn'; 1084interface CompileEvent { 1085 start(time?: number): CompileEvent; 1086 stop(state?: CompileEventState, time?: number): void; 1087 startAsyncEvent(time: number): CompileEvent; 1088 stopAsyncEvent(state?: CompileEventState, TIME?: number): void; 1089 createSubEvent(name: string): CompileEvent; 1090} 1091 1092export function resetUtils(): void { 1093 componentInfo = new ComponentInfo(); 1094 harFilesRecord.clear(); 1095 storedFileInfo = new ProcessFileInfo(); 1096} 1097 1098export function getStoredFileInfo(): ProcessFileInfo { 1099 return storedFileInfo; 1100} 1101 1102export class EntryOptionValue { 1103 routeName: ts.Expression; 1104 storage: ts.Expression; 1105 useSharedStorage: ts.Expression; 1106} 1107 1108function sharedNode(): ts.CallExpression { 1109 return ts.factory.createCallExpression( 1110 ts.factory.createPropertyAccessExpression( 1111 ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU), 1112 ts.factory.createIdentifier(GET_SHARED) 1113 ), 1114 undefined, 1115 [] 1116 ); 1117} 1118 1119export function createGetShared(entryOptionValue: EntryOptionValue): ts.ConditionalExpression { 1120 return ts.factory.createConditionalExpression( 1121 entryOptionValue.useSharedStorage, 1122 ts.factory.createToken(ts.SyntaxKind.QuestionToken), 1123 sharedNode(), 1124 ts.factory.createToken(ts.SyntaxKind.ColonToken), 1125 entryOptionValue.storage || ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) 1126 ); 1127} 1128 1129export function createGetSharedForVariable(entryOptionNode: ts.Expression, isShare: boolean = true): ts.ConditionalExpression { 1130 return ts.factory.createConditionalExpression( 1131 ts.factory.createPropertyAccessExpression( 1132 entryOptionNode, 1133 ts.factory.createIdentifier(USE_SHARED_STORAGE) 1134 ), 1135 ts.factory.createToken(ts.SyntaxKind.QuestionToken), 1136 sharedNode(), 1137 ts.factory.createToken(ts.SyntaxKind.ColonToken), 1138 isShare ? ts.factory.createPropertyAccessExpression( 1139 entryOptionNode, ts.factory.createIdentifier(STORAGE)) : 1140 ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) 1141 ); 1142} 1143 1144export function judgeUseSharedStorageForExpresion(entryOptionNode: ts.Expression): ts.BinaryExpression { 1145 return ts.factory.createBinaryExpression( 1146 entryOptionNode, 1147 ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), 1148 ts.factory.createBinaryExpression( 1149 ts.factory.createPropertyAccessExpression( 1150 entryOptionNode, 1151 ts.factory.createIdentifier(USE_SHARED_STORAGE) 1152 ), 1153 ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsToken), 1154 ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) 1155 ) 1156 ); 1157} 1158 1159export function getRollupCache(rollupShareObject: object, projectConfig: object, key: string): object | undefined { 1160 if (!rollupShareObject) { 1161 return undefined; 1162 } 1163 1164 // Preferentially get cache object from the rollup’s cache interface. 1165 if (rollupShareObject.cache) { 1166 // Only the cache object’s name as the cache key is required. 1167 return rollupShareObject.cache.get(key); 1168 } 1169 1170 // Try to get cache object from the rollup's cacheStoreManager interface. 1171 if (rollupShareObject.cacheStoreManager) { 1172 // The cache under cacheStoreManager is divided into two layers. The key for the first layer of cache is determined 1173 // by the SDK and runtimeOS, accessed through the `mount` interface. The key for the second layer of cache is 1174 // determined by the compilation task and the name of the cache object, 1175 // accessed through `getCache` or `setCache` interface. 1176 const cacheStoreKey: string = getRollupCacheStoreKey(projectConfig); 1177 const cacheServiceKey: string = getRollupCacheKey(projectConfig) + '#' + key; 1178 1179 // Clear the cache if the SDK path or runtimeOS changed 1180 clearRollupCacheStore(rollupShareObject.cacheStoreManager, cacheStoreKey); 1181 return rollupShareObject.cacheStoreManager.mount(cacheStoreKey).getCache(cacheServiceKey); 1182 } 1183 1184 return undefined; 1185} 1186 1187export function setRollupCache(rollupShareObject: object, projectConfig: object, key: string, value: object): void { 1188 if (!rollupShareObject) { 1189 return; 1190 } 1191 1192 // Preferentially set cache object to the rollup’s cache interface. 1193 if (rollupShareObject.cache) { 1194 // Only the cache object’s name as the cache key is required. 1195 rollupShareObject.cache.set(key, value); 1196 return; 1197 } 1198 1199 // Try to set cache object to the rollup's cacheStoreManager interface. 1200 if (rollupShareObject.cacheStoreManager) { 1201 const cacheStoreKey: string = getRollupCacheStoreKey(projectConfig); 1202 const cacheServiceKey: string = getRollupCacheKey(projectConfig) + '#' + key; 1203 1204 rollupShareObject.cacheStoreManager.mount(cacheStoreKey).setCache(cacheServiceKey, value); 1205 } 1206}