1/* 2 * Copyright (c) 2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import cluster from 'cluster'; 17import fs from 'fs'; 18import path from 'path'; 19import ts from 'typescript'; 20import os from 'os'; 21import sourceMap from 'source-map'; 22 23import { 24 DEBUG, 25 ESMODULE, 26 EXTNAME_ETS, 27 EXTNAME_JS, 28 EXTNAME_TS, 29 EXTNAME_JSON, 30 EXTNAME_CJS, 31 EXTNAME_MJS, 32 TEMPORARY 33} from './common/ark_define'; 34import { 35 nodeLargeOrEqualTargetVersion, 36 genTemporaryPath, 37 mkdirsSync, 38 validateFilePathLength, 39 toUnixPath, 40 isPackageModulesFile, 41 isFileInProject 42} from '../../utils'; 43import { 44 tryMangleFileName, 45 writeObfuscatedSourceCode, 46} from '../../ark_utils'; 47import { AOT_FULL, AOT_PARTIAL, AOT_TYPE } from '../../pre_define'; 48import { SourceMapGenerator } from './generate_sourcemap'; 49import { 50 handleObfuscatedFilePath, 51 enableObfuscateFileName, 52 enableObfuscatedFilePathConfig, 53 getRelativeSourcePath 54} from './common/ob_config_resolver'; 55import { 56 CompileEvent, 57 createAndStartEvent, 58 stopEvent 59} from '../../performance'; 60import { BytecodeObfuscator } from './bytecode_obfuscator'; 61 62export let hasTsNoCheckOrTsIgnoreFiles: string[] = []; 63export let compilingEtsOrTsFiles: string[] = []; 64 65export function cleanUpFilesList(): void { 66 hasTsNoCheckOrTsIgnoreFiles = []; 67 compilingEtsOrTsFiles = []; 68} 69 70export function needAotCompiler(projectConfig: Object): boolean { 71 return projectConfig.compileMode === ESMODULE && (projectConfig.anBuildMode === AOT_FULL || 72 projectConfig.anBuildMode === AOT_PARTIAL); 73} 74 75export function isAotMode(projectConfig: Object): boolean { 76 return projectConfig.compileMode === ESMODULE && (projectConfig.anBuildMode === AOT_FULL || 77 projectConfig.anBuildMode === AOT_PARTIAL || projectConfig.anBuildMode === AOT_TYPE); 78} 79 80export function isDebug(projectConfig: Object): boolean { 81 return projectConfig.buildMode.toLowerCase() === DEBUG; 82} 83 84export function isBranchElimination(projectConfig: Object): boolean { 85 return projectConfig.branchElimination; 86} 87 88export function isMasterOrPrimary() { 89 return ((nodeLargeOrEqualTargetVersion(16) && cluster.isPrimary) || 90 (!nodeLargeOrEqualTargetVersion(16) && cluster.isMaster)); 91} 92 93export function changeFileExtension(file: string, targetExt: string, originExt = ''): string { 94 let currentExt = originExt.length === 0 ? path.extname(file) : originExt; 95 let fileWithoutExt = file.substring(0, file.lastIndexOf(currentExt)); 96 return fileWithoutExt + targetExt; 97} 98 99export function removeCacheFile(cacheFilePath: string, ext: string): void { 100 let filePath = toUnixPath(changeFileExtension(cacheFilePath, ext)); 101 if (fs.existsSync(filePath)) { 102 fs.rmSync(filePath); 103 } 104} 105 106export function shouldETSOrTSFileTransformToJS(filePath: string, projectConfig: Object, metaInfo?: Object): boolean { 107 let cacheFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, projectConfig.cachePath, 108 projectConfig, metaInfo); 109 110 if (!projectConfig.processTs) { 111 removeCacheFile(cacheFilePath, EXTNAME_TS); 112 return true; 113 } 114 115 if (compilingEtsOrTsFiles.indexOf(filePath) !== -1) { 116 // file involves in compilation 117 const hasTsNoCheckOrTsIgnore = hasTsNoCheckOrTsIgnoreFiles.indexOf(filePath) !== -1; 118 // Remove cacheFile whose extension is different the target file 119 removeCacheFile(cacheFilePath, hasTsNoCheckOrTsIgnore ? EXTNAME_TS : EXTNAME_JS); 120 return hasTsNoCheckOrTsIgnore; 121 } 122 123 cacheFilePath = updateCacheFilePathIfEnableObuscatedFilePath(filePath, cacheFilePath, projectConfig); 124 cacheFilePath = toUnixPath(changeFileExtension(cacheFilePath, EXTNAME_JS)); 125 return fs.existsSync(cacheFilePath); 126} 127 128// This API is used exclusively to determine whether a file needs to be converted into a JS file without removing the cached files, 129// which differs from API 'shouldETSOrTSFileTransformToJS'. 130export function shouldETSOrTSFileTransformToJSWithoutRemove(filePath: string, projectConfig: Object, metaInfo?: Object): boolean { 131 if (!projectConfig.processTs) { 132 return true; 133 } 134 135 if (compilingEtsOrTsFiles.indexOf(filePath) !== -1) { 136 // file involves in compilation 137 return hasTsNoCheckOrTsIgnoreFiles.indexOf(filePath) !== -1; 138 } 139 140 let cacheFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, projectConfig.cachePath, 141 projectConfig, metaInfo); 142 cacheFilePath = updateCacheFilePathIfEnableObuscatedFilePath(filePath, cacheFilePath, projectConfig); 143 cacheFilePath = toUnixPath(changeFileExtension(cacheFilePath, EXTNAME_JS)); 144 return fs.existsSync(cacheFilePath); 145} 146 147function updateCacheFilePathIfEnableObuscatedFilePath(filePath: string, cacheFilePath: string, projectConfig: Object): string { 148 const isPackageModules = isPackageModulesFile(filePath, projectConfig); 149 if (enableObfuscatedFilePathConfig(isPackageModules, projectConfig) && enableObfuscateFileName(isPackageModules, projectConfig)) { 150 return handleObfuscatedFilePath(cacheFilePath, isPackageModules, projectConfig); 151 } 152 return cacheFilePath; 153} 154 155export async function writeFileContentToTempDir(id: string, content: string, projectConfig: Object, 156 logger: Object, parentEvent: CompileEvent, metaInfo: Object): Promise<void> { 157 if (isCommonJsPluginVirtualFile(id)) { 158 return; 159 } 160 161 if (!isCurrentProjectFiles(id, projectConfig)) { 162 return; 163 } 164 165 let filePath: string; 166 if (projectConfig.compileHar) { 167 // compileShared: compile shared har of project 168 filePath = genTemporaryPath(id, 169 projectConfig.compileShared ? projectConfig.projectRootPath : projectConfig.moduleRootPath, 170 projectConfig.compileShared ? path.resolve(projectConfig.aceModuleBuild, '../etsFortgz') : projectConfig.cachePath, 171 projectConfig, metaInfo, projectConfig.compileShared); 172 } else { 173 filePath = genTemporaryPath(id, projectConfig.projectPath, projectConfig.cachePath, projectConfig, metaInfo); 174 } 175 176 const eventWriteFileContent = createAndStartEvent(parentEvent, 'write file content'); 177 switch (path.extname(id)) { 178 case EXTNAME_ETS: 179 case EXTNAME_TS: 180 case EXTNAME_JS: 181 case EXTNAME_MJS: 182 case EXTNAME_CJS: 183 await writeFileContent(id, filePath, content, projectConfig, logger, metaInfo); 184 break; 185 case EXTNAME_JSON: 186 const newFilePath: string = tryMangleFileName(filePath, projectConfig, id, projectConfig.isBytecodeObfEnabled); 187 mkdirsSync(path.dirname(newFilePath)); 188 fs.writeFileSync(newFilePath, content ?? ''); 189 break; 190 default: 191 break; 192 } 193 stopEvent(eventWriteFileContent); 194} 195 196async function writeFileContent(sourceFilePath: string, filePath: string, content: string, 197 projectConfig: Object, logger: Object, metaInfo?: Object): Promise<void> { 198 if (!isSpecifiedExt(sourceFilePath, EXTNAME_JS)) { 199 filePath = changeFileExtension(filePath, EXTNAME_JS); 200 } 201 202 if (!isDebug(projectConfig)) { 203 const relativeSourceFilePath: string = getRelativeSourcePath(sourceFilePath, projectConfig.projectRootPath, 204 metaInfo?.belongProjectPath); 205 await writeObfuscatedSourceCode({content: content, buildFilePath: filePath, 206 relativeSourceFilePath: relativeSourceFilePath, originSourceFilePath: sourceFilePath, rollupModuleId: sourceFilePath}, 207 logger, projectConfig, SourceMapGenerator.getInstance().getSourceMaps()); 208 return; 209 } 210 mkdirsSync(path.dirname(filePath)); 211 fs.writeFileSync(filePath, content, 'utf-8'); 212} 213 214export function getEs2abcFileThreadNumber(): number { 215 const fileThreads: number = os.cpus().length < 16 ? os.cpus().length : 16; 216 return fileThreads; 217} 218 219export function isCommonJsPluginVirtualFile(filePath: string): boolean { 220 // rollup uses commonjs plugin to handle commonjs files, 221 // which will automatic generate files like 'jsfile.js?commonjs-exports' 222 return filePath.includes('\x00'); 223} 224 225export function isCurrentProjectFiles(filePath: string, projectConfig: Object): boolean { 226 if (projectConfig.rootPathSet) { 227 for (const projectRootPath of projectConfig.rootPathSet) { 228 if (isFileInProject(filePath, projectRootPath)) { 229 return true; 230 } 231 } 232 return false; 233 } 234 return isFileInProject(filePath, projectConfig.projectRootPath); 235} 236 237export function genTemporaryModuleCacheDirectoryForBundle(projectConfig: Object): string { 238 const buildDirArr: string[] = projectConfig.aceModuleBuild.split(path.sep); 239 const abilityDir: string = buildDirArr[buildDirArr.length - 1]; 240 const temporaryModuleCacheDirPath: string = path.join(projectConfig.cachePath, TEMPORARY, abilityDir); 241 mkdirsSync(temporaryModuleCacheDirPath); 242 243 return temporaryModuleCacheDirPath; 244} 245 246export function isSpecifiedExt(filePath: string, fileExtendName: string) { 247 return path.extname(filePath) === fileExtendName; 248} 249 250export function genCachePath(tailName: string, projectConfig: Object, logger: Object): string { 251 const pathName: string = projectConfig.cachePath !== undefined ? 252 path.join(projectConfig.cachePath, TEMPORARY, tailName) : path.join(projectConfig.aceModuleBuild, tailName); 253 mkdirsSync(path.dirname(pathName)); 254 255 validateFilePathLength(pathName, logger); 256 return pathName; 257} 258 259export function isTsOrEtsSourceFile(file: string): boolean { 260 return /(?<!\.d)\.[e]?ts$/.test(file); 261} 262 263export function isJsSourceFile(file: string): boolean { 264 return /\.[cm]?js$/.test(file); 265} 266 267export function isJsonSourceFile(file: string): boolean { 268 return /\.json$/.test(file); 269} 270 271export async function updateSourceMap(originMap: sourceMap.RawSourceMap, newMap: sourceMap.RawSourceMap): Promise<any> { 272 if (!originMap) { 273 return newMap; 274 } 275 if (!newMap) { 276 return originMap; 277 } 278 const originConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(originMap); 279 const newConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(newMap); 280 const newMappingList: sourceMap.MappingItem[] = []; 281 newConsumer.eachMapping((mapping: sourceMap.MappingItem) => { 282 if (mapping.originalLine == null) { 283 return; 284 } 285 const originalPos = 286 originConsumer.originalPositionFor({ line: mapping.originalLine, column: mapping.originalColumn }); 287 if (originalPos.source == null) { 288 return; 289 } 290 mapping.originalLine = originalPos.line; 291 mapping.originalColumn = originalPos.column; 292 newMappingList.push(mapping); 293 }); 294 const updatedGenerator: sourceMap.SourceMapGenerator = sourceMap.SourceMapGenerator.fromSourceMap(newConsumer); 295 updatedGenerator._file = originMap.file; 296 updatedGenerator._mappings._array = newMappingList; 297 return JSON.parse(updatedGenerator.toString()); 298} 299 300export function hasArkDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration | 301 ts.StructDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration, decortorName: string): boolean { 302 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 303 if (decorators && decorators.length) { 304 for (let i = 0; i < decorators.length; i++) { 305 const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').replace(/\s*/g, '').trim(); 306 return originalDecortor === decortorName; 307 } 308 } 309 return false; 310} 311 312export const utUtils = { 313 writeFileContent 314};