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 cleanUpUtilsObjects, 47 createAndStartEvent, 48 stopEvent 49} from '../../ark_utils'; 50import { AOT_FULL, AOT_PARTIAL, AOT_TYPE } from '../../pre_define'; 51import { SourceMapGenerator } from './generate_sourcemap'; 52import { 53 handleObfuscatedFilePath, 54 enableObfuscateFileName, 55 enableObfuscatedFilePathConfig, 56 getRelativeSourcePath 57} from './common/ob_config_resolver'; 58 59export let hasTsNoCheckOrTsIgnoreFiles: string[] = []; 60export let compilingEtsOrTsFiles: string[] = []; 61 62export function cleanUpFilesList(): void { 63 hasTsNoCheckOrTsIgnoreFiles = []; 64 compilingEtsOrTsFiles = []; 65} 66 67export function needAotCompiler(projectConfig: Object): boolean { 68 return projectConfig.compileMode === ESMODULE && (projectConfig.anBuildMode === AOT_FULL || 69 projectConfig.anBuildMode === AOT_PARTIAL); 70} 71 72export function isAotMode(projectConfig: Object): boolean { 73 return projectConfig.compileMode === ESMODULE && (projectConfig.anBuildMode === AOT_FULL || 74 projectConfig.anBuildMode === AOT_PARTIAL || projectConfig.anBuildMode === AOT_TYPE); 75} 76 77export function isDebug(projectConfig: Object): boolean { 78 return projectConfig.buildMode.toLowerCase() === DEBUG; 79} 80 81export function isBranchElimination(projectConfig: Object): boolean { 82 return projectConfig.branchElimination; 83} 84 85export function isMasterOrPrimary() { 86 return ((nodeLargeOrEqualTargetVersion(16) && cluster.isPrimary) || 87 (!nodeLargeOrEqualTargetVersion(16) && cluster.isMaster)); 88} 89 90export function changeFileExtension(file: string, targetExt: string, originExt = ''): string { 91 let currentExt = originExt.length === 0 ? path.extname(file) : originExt; 92 let fileWithoutExt = file.substring(0, file.lastIndexOf(currentExt)); 93 return fileWithoutExt + targetExt; 94} 95 96function removeCacheFile(cacheFilePath: string, ext: string): void { 97 let filePath = toUnixPath(changeFileExtension(cacheFilePath, ext)); 98 if (fs.existsSync(filePath)) { 99 fs.rmSync(filePath); 100 } 101} 102 103export function shouldETSOrTSFileTransformToJS(filePath: string, projectConfig: Object, metaInfo?: Object): boolean { 104 let cacheFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, projectConfig.cachePath, 105 projectConfig, metaInfo); 106 107 if (!projectConfig.processTs) { 108 removeCacheFile(cacheFilePath, EXTNAME_TS); 109 return true; 110 } 111 112 if (compilingEtsOrTsFiles.indexOf(filePath) !== -1) { 113 // file involves in compilation 114 const hasTsNoCheckOrTsIgnore = hasTsNoCheckOrTsIgnoreFiles.indexOf(filePath) !== -1; 115 // Remove cacheFile whose extension is different the target file 116 removeCacheFile(cacheFilePath, hasTsNoCheckOrTsIgnore ? EXTNAME_TS : EXTNAME_JS); 117 return hasTsNoCheckOrTsIgnore; 118 } 119 120 cacheFilePath = updateCacheFilePathIfEnableObuscatedFilePath(filePath, cacheFilePath, projectConfig); 121 cacheFilePath = toUnixPath(changeFileExtension(cacheFilePath, EXTNAME_JS)); 122 return fs.existsSync(cacheFilePath); 123} 124 125function updateCacheFilePathIfEnableObuscatedFilePath(filePath: string, cacheFilePath: string, projectConfig: Object): string { 126 const isPackageModules = isPackageModulesFile(filePath, projectConfig); 127 if (enableObfuscatedFilePathConfig(isPackageModules, projectConfig) && enableObfuscateFileName(isPackageModules, projectConfig)) { 128 return handleObfuscatedFilePath(cacheFilePath, isPackageModules, projectConfig); 129 } 130 return cacheFilePath; 131} 132 133export async function writeFileContentToTempDir(id: string, content: string, projectConfig: Object, 134 logger: Object, parentEvent: Object, metaInfo: Object): Promise<void> { 135 if (isCommonJsPluginVirtualFile(id)) { 136 return; 137 } 138 139 if (!isCurrentProjectFiles(id, projectConfig)) { 140 return; 141 } 142 143 let filePath: string; 144 if (projectConfig.compileHar) { 145 // compileShared: compile shared har of project 146 filePath = genTemporaryPath(id, 147 projectConfig.compileShared ? projectConfig.projectRootPath : projectConfig.moduleRootPath, 148 projectConfig.compileShared ? path.resolve(projectConfig.aceModuleBuild, '../etsFortgz') : projectConfig.cachePath, 149 projectConfig, metaInfo, projectConfig.compileShared); 150 } else { 151 filePath = genTemporaryPath(id, projectConfig.projectPath, projectConfig.cachePath, projectConfig, metaInfo); 152 } 153 154 const eventWriteFileContent = createAndStartEvent(parentEvent, 'write file content'); 155 switch (path.extname(id)) { 156 case EXTNAME_ETS: 157 case EXTNAME_TS: 158 case EXTNAME_JS: 159 case EXTNAME_MJS: 160 case EXTNAME_CJS: 161 await writeFileContent(id, filePath, content, projectConfig, logger, metaInfo); 162 break; 163 case EXTNAME_JSON: 164 const newFilePath: string = tryMangleFileName(filePath, projectConfig, id); 165 mkdirsSync(path.dirname(newFilePath)); 166 fs.writeFileSync(newFilePath, content ?? ''); 167 break; 168 default: 169 break; 170 } 171 stopEvent(eventWriteFileContent); 172} 173 174async function writeFileContent(sourceFilePath: string, filePath: string, content: string, 175 projectConfig: Object, logger: Object, metaInfo?: Object): Promise<void> { 176 if (!isSpecifiedExt(sourceFilePath, EXTNAME_JS)) { 177 filePath = changeFileExtension(filePath, EXTNAME_JS); 178 } 179 180 if (!isDebug(projectConfig)) { 181 const relativeSourceFilePath: string = getRelativeSourcePath(sourceFilePath, projectConfig.projectRootPath, 182 metaInfo?.belongProjectPath); 183 await writeObfuscatedSourceCode({content: content, buildFilePath: filePath, 184 relativeSourceFilePath: relativeSourceFilePath, originSourceFilePath: sourceFilePath, rollupModuleId: sourceFilePath}, 185 logger, projectConfig, SourceMapGenerator.getInstance().getSourceMaps()); 186 return; 187 } 188 mkdirsSync(path.dirname(filePath)); 189 fs.writeFileSync(filePath, content, 'utf-8'); 190} 191 192export function getEs2abcFileThreadNumber(): number { 193 const fileThreads: number = os.cpus().length < 16 ? os.cpus().length : 16; 194 return fileThreads; 195} 196 197export function isCommonJsPluginVirtualFile(filePath: string): boolean { 198 // rollup uses commonjs plugin to handle commonjs files, 199 // which will automatic generate files like 'jsfile.js?commonjs-exports' 200 return filePath.includes('\x00'); 201} 202 203export function isCurrentProjectFiles(filePath: string, projectConfig: Object): boolean { 204 if (projectConfig.rootPathSet) { 205 for (const projectRootPath of projectConfig.rootPathSet) { 206 if (isFileInProject(filePath, projectRootPath)) { 207 return true; 208 } 209 } 210 return false; 211 } 212 return isFileInProject(filePath, projectConfig.projectRootPath); 213} 214 215export function genTemporaryModuleCacheDirectoryForBundle(projectConfig: Object): string { 216 const buildDirArr: string[] = projectConfig.aceModuleBuild.split(path.sep); 217 const abilityDir: string = buildDirArr[buildDirArr.length - 1]; 218 const temporaryModuleCacheDirPath: string = path.join(projectConfig.cachePath, TEMPORARY, abilityDir); 219 mkdirsSync(temporaryModuleCacheDirPath); 220 221 return temporaryModuleCacheDirPath; 222} 223 224export function isSpecifiedExt(filePath: string, fileExtendName: string) { 225 return path.extname(filePath) === fileExtendName; 226} 227 228export function genCachePath(tailName: string, projectConfig: Object, logger: Object): string { 229 const pathName: string = projectConfig.cachePath !== undefined ? 230 path.join(projectConfig.cachePath, TEMPORARY, tailName) : path.join(projectConfig.aceModuleBuild, tailName); 231 mkdirsSync(path.dirname(pathName)); 232 233 validateFilePathLength(pathName, logger); 234 return pathName; 235} 236 237export function isTsOrEtsSourceFile(file: string): boolean { 238 return /(?<!\.d)\.[e]?ts$/.test(file); 239} 240 241export function isJsSourceFile(file: string): boolean { 242 return /\.[cm]?js$/.test(file); 243} 244 245export function isJsonSourceFile(file: string): boolean { 246 return /\.json$/.test(file); 247} 248 249export async function updateSourceMap(originMap: sourceMap.RawSourceMap, newMap: sourceMap.RawSourceMap): Promise<any> { 250 if (!originMap) { 251 return newMap; 252 } 253 if (!newMap) { 254 return originMap; 255 } 256 const originConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(originMap); 257 const newConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(newMap); 258 const newMappingList: sourceMap.MappingItem[] = []; 259 newConsumer.eachMapping((mapping: sourceMap.MappingItem) => { 260 if (mapping.originalLine == null) { 261 return; 262 } 263 const originalPos = 264 originConsumer.originalPositionFor({ line: mapping.originalLine, column: mapping.originalColumn }); 265 if (originalPos.source == null) { 266 return; 267 } 268 mapping.originalLine = originalPos.line; 269 mapping.originalColumn = originalPos.column; 270 newMappingList.push(mapping); 271 }); 272 const updatedGenerator: sourceMap.SourceMapGenerator = sourceMap.SourceMapGenerator.fromSourceMap(newConsumer); 273 updatedGenerator._file = originMap.file; 274 updatedGenerator._mappings._array = newMappingList; 275 return JSON.parse(updatedGenerator.toString()); 276} 277 278export function hasArkDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration | 279 ts.StructDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration, decortorName: string): boolean { 280 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 281 if (decorators && decorators.length) { 282 for (let i = 0; i < decorators.length; i++) { 283 const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').replace(/\s*/g, '').trim(); 284 return originalDecortor === decortorName; 285 } 286 } 287 return false; 288} 289 290export const utUtils = { 291 writeFileContent 292};