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 125// This API is used exclusively to determine whether a file needs to be converted into a JS file without removing the cached files, 126// which differs from API 'shouldETSOrTSFileTransformToJS'. 127export function shouldETSOrTSFileTransformToJSWithoutRemove(filePath: string, projectConfig: Object, metaInfo?: Object): boolean { 128 if (!projectConfig.processTs) { 129 return true; 130 } 131 132 if (compilingEtsOrTsFiles.indexOf(filePath) !== -1) { 133 // file involves in compilation 134 return hasTsNoCheckOrTsIgnoreFiles.indexOf(filePath) !== -1; 135 } 136 137 let cacheFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, projectConfig.cachePath, 138 projectConfig, metaInfo); 139 cacheFilePath = updateCacheFilePathIfEnableObuscatedFilePath(filePath, cacheFilePath, projectConfig); 140 cacheFilePath = toUnixPath(changeFileExtension(cacheFilePath, EXTNAME_JS)); 141 return fs.existsSync(cacheFilePath); 142} 143 144function updateCacheFilePathIfEnableObuscatedFilePath(filePath: string, cacheFilePath: string, projectConfig: Object): string { 145 const isPackageModules = isPackageModulesFile(filePath, projectConfig); 146 if (enableObfuscatedFilePathConfig(isPackageModules, projectConfig) && enableObfuscateFileName(isPackageModules, projectConfig)) { 147 return handleObfuscatedFilePath(cacheFilePath, isPackageModules, projectConfig); 148 } 149 return cacheFilePath; 150} 151 152export async function writeFileContentToTempDir(id: string, content: string, projectConfig: Object, 153 logger: Object, parentEvent: Object, metaInfo: Object): Promise<void> { 154 if (isCommonJsPluginVirtualFile(id)) { 155 return; 156 } 157 158 if (!isCurrentProjectFiles(id, projectConfig)) { 159 return; 160 } 161 162 let filePath: string; 163 if (projectConfig.compileHar) { 164 // compileShared: compile shared har of project 165 filePath = genTemporaryPath(id, 166 projectConfig.compileShared ? projectConfig.projectRootPath : projectConfig.moduleRootPath, 167 projectConfig.compileShared ? path.resolve(projectConfig.aceModuleBuild, '../etsFortgz') : projectConfig.cachePath, 168 projectConfig, metaInfo, projectConfig.compileShared); 169 } else { 170 filePath = genTemporaryPath(id, projectConfig.projectPath, projectConfig.cachePath, projectConfig, metaInfo); 171 } 172 173 const eventWriteFileContent = createAndStartEvent(parentEvent, 'write file content'); 174 switch (path.extname(id)) { 175 case EXTNAME_ETS: 176 case EXTNAME_TS: 177 case EXTNAME_JS: 178 case EXTNAME_MJS: 179 case EXTNAME_CJS: 180 await writeFileContent(id, filePath, content, projectConfig, logger, metaInfo); 181 break; 182 case EXTNAME_JSON: 183 const newFilePath: string = tryMangleFileName(filePath, projectConfig, id); 184 mkdirsSync(path.dirname(newFilePath)); 185 fs.writeFileSync(newFilePath, content ?? ''); 186 break; 187 default: 188 break; 189 } 190 stopEvent(eventWriteFileContent); 191} 192 193async function writeFileContent(sourceFilePath: string, filePath: string, content: string, 194 projectConfig: Object, logger: Object, metaInfo?: Object): Promise<void> { 195 if (!isSpecifiedExt(sourceFilePath, EXTNAME_JS)) { 196 filePath = changeFileExtension(filePath, EXTNAME_JS); 197 } 198 199 if (!isDebug(projectConfig)) { 200 const relativeSourceFilePath: string = getRelativeSourcePath(sourceFilePath, projectConfig.projectRootPath, 201 metaInfo?.belongProjectPath); 202 await writeObfuscatedSourceCode({content: content, buildFilePath: filePath, 203 relativeSourceFilePath: relativeSourceFilePath, originSourceFilePath: sourceFilePath, rollupModuleId: sourceFilePath}, 204 logger, projectConfig, SourceMapGenerator.getInstance().getSourceMaps()); 205 return; 206 } 207 mkdirsSync(path.dirname(filePath)); 208 fs.writeFileSync(filePath, content, 'utf-8'); 209} 210 211export function getEs2abcFileThreadNumber(): number { 212 const fileThreads: number = os.cpus().length < 16 ? os.cpus().length : 16; 213 return fileThreads; 214} 215 216export function isCommonJsPluginVirtualFile(filePath: string): boolean { 217 // rollup uses commonjs plugin to handle commonjs files, 218 // which will automatic generate files like 'jsfile.js?commonjs-exports' 219 return filePath.includes('\x00'); 220} 221 222export function isCurrentProjectFiles(filePath: string, projectConfig: Object): boolean { 223 if (projectConfig.rootPathSet) { 224 for (const projectRootPath of projectConfig.rootPathSet) { 225 if (isFileInProject(filePath, projectRootPath)) { 226 return true; 227 } 228 } 229 return false; 230 } 231 return isFileInProject(filePath, projectConfig.projectRootPath); 232} 233 234export function genTemporaryModuleCacheDirectoryForBundle(projectConfig: Object): string { 235 const buildDirArr: string[] = projectConfig.aceModuleBuild.split(path.sep); 236 const abilityDir: string = buildDirArr[buildDirArr.length - 1]; 237 const temporaryModuleCacheDirPath: string = path.join(projectConfig.cachePath, TEMPORARY, abilityDir); 238 mkdirsSync(temporaryModuleCacheDirPath); 239 240 return temporaryModuleCacheDirPath; 241} 242 243export function isSpecifiedExt(filePath: string, fileExtendName: string) { 244 return path.extname(filePath) === fileExtendName; 245} 246 247export function genCachePath(tailName: string, projectConfig: Object, logger: Object): string { 248 const pathName: string = projectConfig.cachePath !== undefined ? 249 path.join(projectConfig.cachePath, TEMPORARY, tailName) : path.join(projectConfig.aceModuleBuild, tailName); 250 mkdirsSync(path.dirname(pathName)); 251 252 validateFilePathLength(pathName, logger); 253 return pathName; 254} 255 256export function isTsOrEtsSourceFile(file: string): boolean { 257 return /(?<!\.d)\.[e]?ts$/.test(file); 258} 259 260export function isJsSourceFile(file: string): boolean { 261 return /\.[cm]?js$/.test(file); 262} 263 264export function isJsonSourceFile(file: string): boolean { 265 return /\.json$/.test(file); 266} 267 268export async function updateSourceMap(originMap: sourceMap.RawSourceMap, newMap: sourceMap.RawSourceMap): Promise<any> { 269 if (!originMap) { 270 return newMap; 271 } 272 if (!newMap) { 273 return originMap; 274 } 275 const originConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(originMap); 276 const newConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(newMap); 277 const newMappingList: sourceMap.MappingItem[] = []; 278 newConsumer.eachMapping((mapping: sourceMap.MappingItem) => { 279 if (mapping.originalLine == null) { 280 return; 281 } 282 const originalPos = 283 originConsumer.originalPositionFor({ line: mapping.originalLine, column: mapping.originalColumn }); 284 if (originalPos.source == null) { 285 return; 286 } 287 mapping.originalLine = originalPos.line; 288 mapping.originalColumn = originalPos.column; 289 newMappingList.push(mapping); 290 }); 291 const updatedGenerator: sourceMap.SourceMapGenerator = sourceMap.SourceMapGenerator.fromSourceMap(newConsumer); 292 updatedGenerator._file = originMap.file; 293 updatedGenerator._mappings._array = newMappingList; 294 return JSON.parse(updatedGenerator.toString()); 295} 296 297export function hasArkDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration | 298 ts.StructDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration, decortorName: string): boolean { 299 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 300 if (decorators && decorators.length) { 301 for (let i = 0; i < decorators.length; i++) { 302 const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').replace(/\s*/g, '').trim(); 303 return originalDecortor === decortorName; 304 } 305 } 306 return false; 307} 308 309export const utUtils = { 310 writeFileContent 311};