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 path from 'path'; 17import fs from 'fs'; 18import type sourceMap from 'source-map'; 19 20import { minify, MinifyOutput } from 'terser'; 21import { getMapFromJson, deleteLineInfoForNameString, MemoryUtils, nameCacheMap } from 'arkguard'; 22import { 23 OH_MODULES, 24 SEPARATOR_AT, 25 SEPARATOR_BITWISE_AND, 26 SEPARATOR_SLASH 27} from './fast_build/ark_compiler/common/ark_define'; 28import { 29 ARKTS_MODULE_NAME, 30 PACKAGES, 31 TEMPORARY, 32 ZERO, 33 ONE, 34 EXTNAME_JS, 35 EXTNAME_TS, 36 EXTNAME_MJS, 37 EXTNAME_CJS, 38 EXTNAME_ABC, 39 EXTNAME_ETS, 40 EXTNAME_TS_MAP, 41 EXTNAME_JS_MAP, 42 ESMODULE, 43 FAIL, 44 TS2ABC, 45 ES2ABC, 46 EXTNAME_PROTO_BIN, 47 NATIVE_MODULE 48} from './pre_define'; 49import { 50 isMac, 51 isWindows, 52 isPackageModulesFile, 53 genTemporaryPath, 54 getExtensionIfUnfullySpecifiedFilepath, 55 mkdirsSync, 56 toUnixPath, 57 validateFilePathLength, 58 harFilesRecord, 59 getProjectRootPath 60} from './utils'; 61import type { GeneratedFileInHar } from './utils'; 62import { 63 extendSdkConfigs, 64 projectConfig, 65 sdkConfigPrefix 66} from '../main'; 67import { getRelativeSourcePath, mangleFilePath } from './fast_build/ark_compiler/common/ob_config_resolver'; 68import { moduleRequestCallback } from './fast_build/system_api/api_check_utils'; 69import { performancePrinter } from 'arkguard/lib/ArkObfuscator'; 70import { SourceMapGenerator } from './fast_build/ark_compiler/generate_sourcemap'; 71import { sourceFileBelongProject } from './fast_build/ark_compiler/module/module_source_file'; 72 73const red: string = '\u001b[31m'; 74const reset: string = '\u001b[39m'; 75const IDENTIFIER_CACHE: string = 'IdentifierCache'; 76 77export const SRC_MAIN: string = 'src/main'; 78 79export let newSourceMaps: Object = {}; 80 81export const packageCollection: Map<string, Array<string>> = new Map(); 82// Splicing ohmurl or record name based on filePath and context information table. 83export function getNormalizedOhmUrlByFilepath(filePath: string, projectConfig: Object, logger: Object, 84 pkgParams: Object, importerFile: string): string { 85 const { pkgName, pkgPath, isRecordName } = pkgParams; 86 // rollup uses commonjs plugin to handle commonjs files, 87 // the commonjs files are prefixed with '\x00' and need to be removed. 88 if (filePath.startsWith('\x00')) { 89 filePath = filePath.replace('\x00', ''); 90 } 91 let unixFilePath: string = toUnixPath(filePath); 92 unixFilePath = unixFilePath.substring(0, filePath.lastIndexOf('.')); // remove extension 93 let projectFilePath: string = unixFilePath.replace(toUnixPath(pkgPath), ''); 94 // case1: /entry/src/main/ets/xxx/yyy 95 // case2: /entry/src/ohosTest/ets/xxx/yyy 96 // case3: /node_modules/xxx/yyy 97 // case4: /entry/node_modules/xxx/yyy 98 // case5: /library/node_modules/xxx/yyy 99 // case6: /library/index.ts 100 // ---> @normalized:N&<moduleName>&<bunldName>&<packageName>/entry/ets/xxx/yyy&<version> 101 let pkgInfo = projectConfig.pkgContextInfo[pkgName]; 102 if (pkgInfo === undefined) { 103 logger.error(red, 'ArkTS:ERROR Failed to resolve OhmUrl.\n' + 104 `Error Message: Failed to get a resolved OhmUrl for "${filePath}" imported by "${importerFile}".\n` + 105 `Solutions: > Check whether the module which ${filePath} belongs to is correctly configured.` + 106 '> Check the corresponding file name is correct(including case-sensitivity).', reset); 107 } 108 let recordName = `${pkgInfo.bundleName}&${pkgName}${projectFilePath}&${pkgInfo.version}`; 109 if (isRecordName) { 110 // record name style: <bunldName>&<packageName>/entry/ets/xxx/yyy&<version> 111 return recordName; 112 } 113 return `${pkgInfo.isSO ? 'Y' : 'N'}&${pkgInfo.moduleName}&${recordName}`; 114} 115 116export function getOhmUrlByFilepath(filePath: string, projectConfig: Object, logger: Object, namespace?: string, 117 importerFile?: string): string { 118 // remove '\x00' from the rollup virtual commonjs file's filePath 119 if (filePath.startsWith('\x00')) { 120 filePath = filePath.replace('\x00', ''); 121 } 122 let unixFilePath: string = toUnixPath(filePath); 123 unixFilePath = unixFilePath.substring(0, filePath.lastIndexOf('.')); // remove extension 124 const REG_PROJECT_SRC: RegExp = /(\S+)\/src\/(?:main|ohosTest)\/(ets|js|mock)\/(\S+)/; 125 126 const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath); 127 const bundleName: string = packageInfo[0]; 128 const moduleName: string = packageInfo[1]; 129 const moduleRootPath: string = toUnixPath(projectConfig.modulePathMap[moduleName]); 130 const projectRootPath: string = toUnixPath(getProjectRootPath(filePath, projectConfig, projectConfig?.rootPathSet)); 131 // case1: /entry/src/main/ets/xxx/yyy ---> @bundle:<bundleName>/entry/ets/xxx/yyy 132 // case2: /entry/src/ohosTest/ets/xxx/yyy ---> @bundle:<bundleName>/entry_test@entry/ets/xxx/yyy 133 // case3: /node_modules/xxx/yyy ---> @package:pkg_modules/xxx/yyy 134 // case4: /entry/node_modules/xxx/yyy ---> @package:pkg_modules@entry/xxx/yyy 135 // case5: /library/node_modules/xxx/yyy ---> @package:pkg_modules@library/xxx/yyy 136 // case6: /library/index.ts ---> @bundle:<bundleName>/library/index 137 const projectFilePath: string = unixFilePath.replace(projectRootPath, ''); 138 const packageDir: string = projectConfig.packageDir; 139 const result: RegExpMatchArray | null = projectFilePath.match(REG_PROJECT_SRC); 140 if (result && result[1].indexOf(packageDir) === -1) { 141 const relativePath = processSrcMain(result, projectFilePath); 142 if (namespace && moduleName !== namespace) { 143 return `${bundleName}/${moduleName}@${namespace}/${relativePath}`; 144 } 145 return `${bundleName}/${moduleName}/${relativePath}`; 146 } 147 148 const processParams: Object = { 149 projectFilePath, 150 unixFilePath, 151 packageDir, 152 projectRootPath, 153 moduleRootPath, 154 projectConfig, 155 namespace, 156 logger, 157 importerFile, 158 originalFilePath: filePath 159 }; 160 return processPackageDir(processParams); 161} 162 163function processSrcMain(result: RegExpMatchArray | null, projectFilePath: string): string { 164 let langType: string = result[2]; 165 let relativePath: string = result[3]; 166 // case7: /entry/src/main/ets/xxx/src/main/js/yyy ---> @bundle:<bundleName>/entry/ets/xxx/src/main/js/yyy 167 const REG_SRC_MAIN: RegExp = /src\/(?:main|ohosTest)\/(ets|js)\//; 168 const srcMainIndex: number = result[1].search(REG_SRC_MAIN); 169 if (srcMainIndex !== -1) { 170 relativePath = projectFilePath.substring(srcMainIndex).replace(REG_SRC_MAIN, ''); 171 langType = projectFilePath.replace(relativePath, '').match(REG_SRC_MAIN)[1]; 172 } 173 return `${langType}/${relativePath}`; 174} 175 176function processPackageDir(params: Object): string { 177 const { projectFilePath, unixFilePath, packageDir, projectRootPath, moduleRootPath, 178 projectConfig, namespace, logger, importerFile, originalFilePath } = params; 179 if (projectFilePath.indexOf(packageDir) !== -1) { 180 if (compileToolIsRollUp()) { 181 const tryProjectPkg: string = toUnixPath(path.join(projectRootPath, packageDir)); 182 if (unixFilePath.indexOf(tryProjectPkg) !== -1) { 183 return unixFilePath.replace(tryProjectPkg, `${packageDir}`).replace(new RegExp(packageDir, 'g'), PACKAGES); 184 } 185 186 // iterate the modulePathMap to find the module which contains the pkg_module's file 187 for (const moduleName in projectConfig.modulePathMap) { 188 const modulePath: string = projectConfig.modulePathMap[moduleName]; 189 const tryModulePkg: string = toUnixPath(path.resolve(modulePath, packageDir)); 190 if (unixFilePath.indexOf(tryModulePkg) !== -1) { 191 return unixFilePath.replace(tryModulePkg, `${packageDir}@${moduleName}`).replace(new RegExp(packageDir, 'g'), PACKAGES); 192 } 193 } 194 195 logger.error(red, 'ArkTS:ERROR Failed to resolve OhmUrl.\n' + 196 `Error Message: Failed to get a resolved OhmUrl for "${originalFilePath}" imported by "${importerFile}".\n` + 197 `Solutions: > Check whether the module which ${originalFilePath} belongs to is correctly configured.` + 198 '> Check the corresponding file name is correct(including case-sensitivity).', reset); 199 return originalFilePath; 200 } 201 202 // webpack with old implematation 203 const tryProjectPkg: string = toUnixPath(path.join(projectRootPath, packageDir)); 204 if (unixFilePath.indexOf(tryProjectPkg) !== -1) { 205 return unixFilePath.replace(tryProjectPkg, `${packageDir}/${ONE}`).replace(new RegExp(packageDir, 'g'), PACKAGES); 206 } 207 208 const tryModulePkg: string = toUnixPath(path.join(moduleRootPath, packageDir)); 209 if (unixFilePath.indexOf(tryModulePkg) !== -1) { 210 return unixFilePath.replace(tryModulePkg, `${packageDir}/${ZERO}`).replace(new RegExp(packageDir, 'g'), PACKAGES); 211 } 212 } 213 214 const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath); 215 const bundleName: string = packageInfo[0]; 216 const moduleName: string = packageInfo[1]; 217 for (const key in projectConfig.modulePathMap) { 218 const moduleRootPath: string = toUnixPath(projectConfig.modulePathMap[key]); 219 if (unixFilePath.indexOf(moduleRootPath + '/') !== -1) { 220 const relativeModulePath: string = unixFilePath.replace(moduleRootPath + '/', ''); 221 if (namespace && moduleName !== namespace) { 222 return `${bundleName}/${moduleName}@${namespace}/${relativeModulePath}`; 223 } 224 return `${bundleName}/${moduleName}/${relativeModulePath}`; 225 } 226 } 227 228 logger.error(red, 'ArkTS:ERROR Failed to resolve OhmUrl.\n' + 229 `Error Message: Failed to get a resolved OhmUrl for "${originalFilePath}" imported by "${importerFile}".\n` + 230 `Solutions: > Check whether the module which ${originalFilePath} belongs to is correctly configured.` + 231 '> Check the corresponding file name is correct(including case-sensitivity).', reset); 232 return originalFilePath; 233} 234 235 236export function getOhmUrlBySystemApiOrLibRequest(moduleRequest: string, config?: Object, logger?: Object, 237 importerFile?: string, useNormalizedOHMUrl: boolean = false): string { 238 // 'arkui-x' represents cross platform related APIs, processed as 'ohos' 239 const REG_SYSTEM_MODULE: RegExp = new RegExp(`@(${sdkConfigPrefix})\\.(\\S+)`); 240 const REG_LIB_SO: RegExp = /lib(\S+)\.so/; 241 242 if (REG_SYSTEM_MODULE.test(moduleRequest.trim())) { 243 return moduleRequest.replace(REG_SYSTEM_MODULE, (_, moduleType, systemKey) => { 244 let moduleRequestStr = ''; 245 if (extendSdkConfigs) { 246 moduleRequestStr = moduleRequestCallback(moduleRequest, _, moduleType, systemKey); 247 } 248 if (moduleRequestStr !== '') { 249 return moduleRequestStr; 250 } 251 const systemModule: string = `${moduleType}.${systemKey}`; 252 if (NATIVE_MODULE.has(systemModule)) { 253 return `@native:${systemModule}`; 254 } else if (moduleType === ARKTS_MODULE_NAME) { 255 // @arkts.xxx -> @ohos:arkts.xxx 256 return `@ohos:${systemModule}`; 257 } else { 258 return `@ohos:${systemKey}`; 259 }; 260 }); 261 } 262 if (REG_LIB_SO.test(moduleRequest.trim())) { 263 if (useNormalizedOHMUrl) { 264 const pkgInfo = config.pkgContextInfo[moduleRequest]; 265 if (pkgInfo === undefined) { 266 logger?.error(red, `ArkTS:INTERNAL ERROR: Can not get pkgContextInfo of package '${moduleRequest}' ` + 267 `which being imported by '${importerFile}'`, reset); 268 } 269 const isSo = pkgInfo.isSO ? 'Y' : 'N'; 270 return `@normalized:${isSo}&${pkgInfo.moduleName}&${pkgInfo.bundleName}&${moduleRequest}&${pkgInfo.version}`; 271 } 272 return moduleRequest.replace(REG_LIB_SO, (_, libsoKey) => { 273 return `@app:${projectConfig.bundleName}/${projectConfig.moduleName}/${libsoKey}`; 274 }); 275 } 276 return undefined; 277} 278 279export function genSourceMapFileName(temporaryFile: string): string { 280 let abcFile: string = temporaryFile; 281 if (temporaryFile.endsWith(EXTNAME_TS)) { 282 abcFile = temporaryFile.replace(/\.ts$/, EXTNAME_TS_MAP); 283 } else { 284 abcFile = temporaryFile.replace(/\.js$/, EXTNAME_JS_MAP); 285 } 286 return abcFile; 287} 288 289export function getBuildModeInLowerCase(projectConfig: Object): string { 290 return (compileToolIsRollUp() ? projectConfig.buildMode : projectConfig.buildArkMode).toLowerCase(); 291} 292 293/** 294 * This Api only used by webpack compiling process - js-loader 295 * @param sourcePath The path in build cache dir 296 * @param sourceCode The intermediate js source code 297 */ 298export function writeFileSyncByString(sourcePath: string, sourceCode: string, projectConfig: Object, logger: Object): void { 299 const filePath: string = genTemporaryPath(sourcePath, projectConfig.projectPath, process.env.cachePath, 300 projectConfig, undefined); 301 if (filePath.length === 0) { 302 return; 303 } 304 mkdirsSync(path.dirname(filePath)); 305 if (/\.js$/.test(sourcePath)) { 306 sourceCode = transformModuleSpecifier(sourcePath, sourceCode, projectConfig); 307 if (projectConfig.buildArkMode === 'debug') { 308 fs.writeFileSync(filePath, sourceCode); 309 return; 310 } 311 writeObfuscatedSourceCode({content: sourceCode, buildFilePath: filePath, relativeSourceFilePath: ''}, 312 logger, projectConfig); 313 } 314 if (/\.json$/.test(sourcePath)) { 315 fs.writeFileSync(filePath, sourceCode); 316 } 317} 318 319export function transformModuleSpecifier(sourcePath: string, sourceCode: string, projectConfig: Object): string { 320 // replace relative moduleSpecifier with ohmURl 321 const REG_RELATIVE_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]((?:\.\/|\.\.\/)[^'"]+|(?:\.\/?|\.\.\/?))['"]/g; 322 const REG_HAR_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]([^\.\/][^'"]+)['"]/g; 323 // replace requireNapi and requireNativeModule with import 324 const REG_REQUIRE_NATIVE_MODULE: RegExp = /var (\S+) = globalThis.requireNativeModule\(['"](\S+)['"]\);/g; 325 const REG_REQUIRE_NAPI_APP: RegExp = /var (\S+) = globalThis.requireNapi\(['"](\S+)['"], true, ['"](\S+)['"]\);/g; 326 const REG_REQUIRE_NAPI_OHOS: RegExp = /var (\S+) = globalThis.requireNapi\(['"](\S+)['"]\);/g; 327 328 return sourceCode.replace(REG_HAR_DEPENDENCY, (item, moduleRequest) => { 329 return replaceHarDependency(item, moduleRequest, projectConfig); 330 }).replace(REG_RELATIVE_DEPENDENCY, (item, moduleRequest) => { 331 return replaceRelativeDependency(item, moduleRequest, toUnixPath(sourcePath), projectConfig); 332 }).replace(REG_REQUIRE_NATIVE_MODULE, (_, moduleRequest, moduleName) => { 333 return `import ${moduleRequest} from '@native:${moduleName}';`; 334 }).replace(REG_REQUIRE_NAPI_APP, (_, moduleRequest, soName, moudlePath) => { 335 return `import ${moduleRequest} from '@app:${moudlePath}/${soName}';`; 336 }).replace(REG_REQUIRE_NAPI_OHOS, (_, moduleRequest, moduleName) => { 337 return `import ${moduleRequest} from '@ohos:${moduleName}';`; 338 }); 339} 340 341function removeSuffix(filePath: string): string { 342 const SUFFIX_REG = /\.(?:d\.)?(ets|ts|mjs|cjs|js)$/; 343 return filePath.split(path.sep).join('/').replace(SUFFIX_REG, ''); 344} 345 346export function getNormalizedOhmUrlByAliasName(aliasName: string, projectConfig: Object, 347 logger?: Object, filePath?: string): string { 348 let pkgName: string = aliasName; 349 const aliasPkgNameMap: Map<string, string> = projectConfig.dependencyAliasMap; 350 if (aliasPkgNameMap.has(aliasName)) { 351 pkgName = aliasPkgNameMap.get(aliasName); 352 } 353 const pkgInfo: Object = projectConfig.pkgContextInfo[pkgName]; 354 if (!pkgInfo) { 355 logger.error(red, `ArkTS:INTERNAL ERROR: Failed to find package '${pkgName}'.\n` + 356 `Error Message: Failed to obtain package '${pkgName}' from the package context information.`, reset); 357 } 358 let normalizedPath: string = ''; 359 if (!filePath) { 360 if (!pkgInfo.entryPath) { 361 logger.error(red, `ArkTS:INTERNAL ERROR: Failed to find entry file of '${pkgName}'.\n` + 362 `Error Message: Failed to obtain the entry file information of '${pkgName}' from the package context information.`, reset); 363 } 364 normalizedPath = `${pkgName}/${toUnixPath(pkgInfo.entryPath)}`; 365 normalizedPath = removeSuffix(normalizedPath); 366 } else { 367 const relativePath = toUnixPath(filePath).replace(aliasName, ''); 368 normalizedPath = `${pkgName}${relativePath}`; 369 } 370 const isSo = pkgInfo.isSO ? 'Y' : 'N'; 371 return `@normalized:${isSo}&${pkgInfo.moduleName}&${pkgInfo.bundleName}&${normalizedPath}&${pkgInfo.version}`; 372} 373 374export function getOhmUrlByByteCodeHar(moduleRequest: string, projectConfig: Object, logger?: Object): 375 string | undefined { 376 if (projectConfig.byteCodeHarInfo) { 377 if (Object.prototype.hasOwnProperty.call(projectConfig.byteCodeHarInfo, moduleRequest)) { 378 return getNormalizedOhmUrlByAliasName(moduleRequest, projectConfig, logger); 379 } 380 for (const byteCodeHarName in projectConfig.byteCodeHarInfo) { 381 if (moduleRequest.startsWith(byteCodeHarName + '/')) { 382 return getNormalizedOhmUrlByAliasName(byteCodeHarName, projectConfig, logger, moduleRequest); 383 } 384 } 385 } 386 return undefined; 387} 388 389export function getOhmUrlByExternalPackage(moduleRequest: string, projectConfig: Object, logger?: Object, 390 useNormalizedOHMUrl: boolean = false): string | undefined { 391 // The externalPkgMap store the ohmurl with the alias of hsp package and the hars depended on bytecode har. 392 let externalPkgMap: Object = Object.assign({}, projectConfig.hspNameOhmMap, projectConfig.harNameOhmMap); 393 if (Object.keys(externalPkgMap).length !== 0) { 394 if (Object.prototype.hasOwnProperty.call(externalPkgMap, moduleRequest)) { 395 if (useNormalizedOHMUrl) { 396 return getNormalizedOhmUrlByAliasName(moduleRequest, projectConfig, logger); 397 } 398 // case1: "@ohos/lib" ---> "@bundle:bundleName/lib/ets/index" 399 return externalPkgMap[moduleRequest]; 400 } 401 402 for (const externalPkgName in externalPkgMap) { 403 if (moduleRequest.startsWith(externalPkgName + '/')) { 404 if (useNormalizedOHMUrl) { 405 return getNormalizedOhmUrlByAliasName(externalPkgName, projectConfig, logger, moduleRequest); 406 } 407 // case2: "@ohos/lib/src/main/ets/pages/page1" ---> "@bundle:bundleName/lib/ets/pages/page1" 408 const idx: number = externalPkgMap[externalPkgName].split('/', 2).join('/').length; 409 const ohmName: string = externalPkgMap[externalPkgName].substring(0, idx); 410 if (moduleRequest.indexOf(externalPkgName + '/' + SRC_MAIN) === 0) { 411 return moduleRequest.replace(externalPkgName + '/' + SRC_MAIN, ohmName); 412 } else { 413 return moduleRequest.replace(externalPkgName, ohmName); 414 } 415 } 416 } 417 } 418 return undefined; 419} 420 421function replaceHarDependency(item: string, moduleRequest: string, projectConfig: Object): string { 422 const hspOhmUrl: string | undefined = getOhmUrlByExternalPackage(moduleRequest, projectConfig); 423 if (hspOhmUrl !== undefined) { 424 return item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { 425 return quotation + hspOhmUrl + quotation; 426 }); 427 } 428 return item; 429} 430 431function locateActualFilePathWithModuleRequest(absolutePath: string): string { 432 if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isDirectory()) { 433 return absolutePath; 434 } 435 436 const filePath: string = absolutePath + getExtensionIfUnfullySpecifiedFilepath(absolutePath); 437 if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { 438 return absolutePath; 439 } 440 441 return path.join(absolutePath, 'index'); 442} 443 444function replaceRelativeDependency(item: string, moduleRequest: string, sourcePath: string, projectConfig: Object): string { 445 if (sourcePath && projectConfig.compileMode === ESMODULE) { 446 // remove file extension from moduleRequest 447 const SUFFIX_REG: RegExp = /\.(?:[cm]?js|[e]?ts|json)$/; 448 moduleRequest = moduleRequest.replace(SUFFIX_REG, ''); 449 450 // normalize the moduleRequest 451 item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { 452 let normalizedModuleRequest: string = toUnixPath(path.normalize(moduleRequest)); 453 if (moduleRequest.startsWith('./')) { 454 normalizedModuleRequest = './' + normalizedModuleRequest; 455 } 456 return quotation + normalizedModuleRequest + quotation; 457 }); 458 459 const filePath: string = 460 locateActualFilePathWithModuleRequest(path.resolve(path.dirname(sourcePath), moduleRequest)); 461 const result: RegExpMatchArray | null = 462 filePath.match(/(\S+)(\/|\\)src(\/|\\)(?:main|ohosTest)(\/|\\)(ets|js)(\/|\\)(\S+)/); 463 if (result && projectConfig.aceModuleJsonPath) { 464 const npmModuleIdx: number = result[1].search(/(\/|\\)node_modules(\/|\\)/); 465 const projectRootPath: string = projectConfig.projectRootPath; 466 if (npmModuleIdx === -1 || npmModuleIdx === projectRootPath.search(/(\/|\\)node_modules(\/|\\)/)) { 467 const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath); 468 const bundleName: string = packageInfo[0]; 469 const moduleName: string = packageInfo[1]; 470 moduleRequest = `@bundle:${bundleName}/${moduleName}/${result[5]}/${toUnixPath(result[7])}`; 471 item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { 472 return quotation + moduleRequest + quotation; 473 }); 474 } 475 } 476 } 477 return item; 478} 479 480interface ModuleInfo { 481 content: string, 482 /** 483 * the path in build cache dir 484 */ 485 buildFilePath: string, 486 /** 487 * the `originSourceFilePath` relative to project root dir. 488 */ 489 relativeSourceFilePath: string, 490 /** 491 * the origin source file path will be set with rollup moduleId when obfuscate intermediate js source code, 492 * whereas be set with tsc node.fileName when obfuscate intermediate ts source code. 493 */ 494 originSourceFilePath?: string, 495 rollupModuleId?: string 496} 497 498export async function writeObfuscatedSourceCode(moduleInfo: ModuleInfo, logger: Object, 499 projectConfig: Object, rollupNewSourceMaps: Object = {}): Promise<void> { 500 if (compileToolIsRollUp() && projectConfig.arkObfuscator) { 501 MemoryUtils.tryGC(); 502 performancePrinter?.filesPrinter?.startEvent(moduleInfo.buildFilePath); 503 await writeArkguardObfuscatedSourceCode(moduleInfo, logger, projectConfig, rollupNewSourceMaps); 504 performancePrinter?.filesPrinter?.endEvent(moduleInfo.buildFilePath, undefined, true); 505 MemoryUtils.tryGC(); 506 return; 507 } 508 mkdirsSync(path.dirname(moduleInfo.buildFilePath)); 509 if (!compileToolIsRollUp()) { 510 await writeMinimizedSourceCode(moduleInfo.content, moduleInfo.buildFilePath, logger, projectConfig.compileHar); 511 return; 512 } 513 514 if (moduleInfo.originSourceFilePath) { 515 const originSourceFilePath = toUnixPath(moduleInfo.originSourceFilePath); 516 let genFileInHar: GeneratedFileInHar = harFilesRecord.get(originSourceFilePath); 517 518 if (!genFileInHar) { 519 genFileInHar = { sourcePath: originSourceFilePath }; 520 } 521 if (!genFileInHar.sourceCachePath) { 522 genFileInHar.sourceCachePath = toUnixPath(moduleInfo.buildFilePath); 523 } 524 harFilesRecord.set(originSourceFilePath, genFileInHar); 525 } 526 527 fs.writeFileSync(moduleInfo.buildFilePath, moduleInfo.content); 528} 529 530/** 531 * This Api only be used by rollup compiling process & only be 532 * exported for unit test. 533 */ 534export async function writeArkguardObfuscatedSourceCode(moduleInfo: ModuleInfo, logger: Object, 535 projectConfig: Object, rollupNewSourceMaps: Object = {}): Promise<void> { 536 const arkObfuscator = projectConfig.arkObfuscator; 537 const isDeclaration = (/\.d\.e?ts$/).test(moduleInfo.buildFilePath); 538 const packageDir = projectConfig.packageDir; 539 const projectRootPath = projectConfig.projectRootPath; 540 const useNormalized = projectConfig.useNormalizedOHMUrl; 541 const localPackageSet = projectConfig.localPackageSet; 542 const useTsHar = projectConfig.useTsHar; 543 const sourceMapGeneratorInstance = SourceMapGenerator.getInstance(); 544 545 let previousStageSourceMap: sourceMap.RawSourceMap | undefined = undefined; 546 if (moduleInfo.relativeSourceFilePath.length > 0 && !isDeclaration) { 547 const selectedFilePath = sourceMapGeneratorInstance.isNewSourceMaps() ? moduleInfo.rollupModuleId! : moduleInfo.relativeSourceFilePath; 548 previousStageSourceMap = sourceMapGeneratorInstance.getSpecifySourceMap(rollupNewSourceMaps, selectedFilePath) as sourceMap.RawSourceMap; 549 } 550 551 let historyNameCache = new Map<string, string>(); 552 if (nameCacheMap) { 553 let namecachePath = moduleInfo.relativeSourceFilePath; 554 if (isDeclaration) { 555 namecachePath = getRelativeSourcePath(moduleInfo.originSourceFilePath, projectRootPath, 556 sourceFileBelongProject.get(toUnixPath(moduleInfo.originSourceFilePath))); 557 } 558 let identifierCache = nameCacheMap.get(namecachePath)?.[IDENTIFIER_CACHE]; 559 deleteLineInfoForNameString(historyNameCache, identifierCache); 560 } 561 562 let mixedInfo: { content: string, sourceMap?: Object, nameCache?: Object }; 563 let projectInfo: { 564 packageDir: string, 565 projectRootPath: string, 566 localPackageSet: Set<string>, 567 useNormalized: boolean, 568 useTsHar: boolean 569 } = { packageDir, projectRootPath, localPackageSet, useNormalized, useTsHar }; 570 try { 571 mixedInfo = await arkObfuscator.obfuscate(moduleInfo.content, moduleInfo.buildFilePath, previousStageSourceMap, 572 historyNameCache, moduleInfo.originSourceFilePath, projectInfo); 573 } catch (err) { 574 logger.error(red, `ArkTS:INTERNAL ERROR: Failed to obfuscate file '${moduleInfo.relativeSourceFilePath}' with arkguard. ${err}`); 575 } 576 577 if (mixedInfo.sourceMap && !isDeclaration) { 578 const selectedFilePath = sourceMapGeneratorInstance.isNewSourceMaps() ? moduleInfo.rollupModuleId! : moduleInfo.relativeSourceFilePath; 579 mixedInfo.sourceMap.sources = [moduleInfo.relativeSourceFilePath]; 580 sourceMapGeneratorInstance.fillSourceMapPackageInfo(moduleInfo.rollupModuleId!, mixedInfo.sourceMap); 581 sourceMapGeneratorInstance.updateSpecifySourceMap(rollupNewSourceMaps, selectedFilePath, mixedInfo.sourceMap); 582 } 583 584 if (mixedInfo.nameCache && !isDeclaration) { 585 let obfName: string = moduleInfo.relativeSourceFilePath; 586 let isOhModule = isPackageModulesFile(moduleInfo.originSourceFilePath, projectConfig); 587 if (projectConfig.obfuscationMergedObConfig?.options.enableFileNameObfuscation && !isOhModule) { 588 obfName = mangleFilePath(moduleInfo.relativeSourceFilePath); 589 } 590 mixedInfo.nameCache.obfName = obfName; 591 nameCacheMap.set(moduleInfo.relativeSourceFilePath, mixedInfo.nameCache); 592 } 593 594 const newFilePath: string = tryMangleFileName(moduleInfo.buildFilePath, projectConfig, moduleInfo.originSourceFilePath); 595 if (newFilePath !== moduleInfo.buildFilePath && !isDeclaration) { 596 sourceMapGeneratorInstance.saveKeyMappingForObfFileName(moduleInfo.rollupModuleId!); 597 } 598 mkdirsSync(path.dirname(newFilePath)); 599 fs.writeFileSync(newFilePath, mixedInfo.content ?? ''); 600} 601 602export function tryMangleFileName(filePath: string, projectConfig: Object, originalFilePath: string): string { 603 originalFilePath = toUnixPath(originalFilePath); 604 let isOhModule = isPackageModulesFile(originalFilePath, projectConfig); 605 let genFileInHar: GeneratedFileInHar = harFilesRecord.get(originalFilePath); 606 if (!genFileInHar) { 607 genFileInHar = { sourcePath: originalFilePath }; 608 harFilesRecord.set(originalFilePath, genFileInHar); 609 } 610 611 if (projectConfig.obfuscationMergedObConfig?.options?.enableFileNameObfuscation && !isOhModule) { 612 const mangledFilePath: string = mangleFilePath(filePath); 613 if ((/\.d\.e?ts$/).test(filePath)) { 614 genFileInHar.obfuscatedDeclarationCachePath = mangledFilePath; 615 } else { 616 genFileInHar.obfuscatedSourceCachePath = mangledFilePath; 617 } 618 filePath = mangledFilePath; 619 } else if (!(/\.d\.e?ts$/).test(filePath)) { 620 genFileInHar.sourceCachePath = filePath; 621 } 622 return filePath; 623} 624 625export async function mangleDeclarationFileName(logger: Object, projectConfig: Object, 626 sourceFileBelongProject: Map<string, string>): Promise<void> { 627 for (const [sourcePath, genFilesInHar] of harFilesRecord) { 628 if (genFilesInHar.originalDeclarationCachePath && genFilesInHar.originalDeclarationContent) { 629 let filePath = genFilesInHar.originalDeclarationCachePath; 630 let relativeSourceFilePath = getRelativeSourcePath(filePath, 631 projectConfig.projectRootPath, sourceFileBelongProject.get(toUnixPath(filePath))); 632 await writeObfuscatedSourceCode({ 633 content: genFilesInHar.originalDeclarationContent, 634 buildFilePath: genFilesInHar.originalDeclarationCachePath, 635 relativeSourceFilePath: relativeSourceFilePath, 636 originSourceFilePath: sourcePath 637 }, logger, projectConfig, {}); 638 } 639 } 640} 641 642export async function writeMinimizedSourceCode(content: string, filePath: string, logger: Object, 643 isHar: boolean = false): Promise<void> { 644 let result: MinifyOutput; 645 try { 646 const minifyOptions = { 647 compress: { 648 join_vars: false, 649 sequences: 0, 650 directives: false 651 } 652 }; 653 if (!isHar) { 654 minifyOptions['format'] = { 655 semicolons: false, 656 beautify: true, 657 indent_level: 2 658 }; 659 } 660 result = await minify(content, minifyOptions); 661 } catch { 662 logger.error(red, `ArkTS:INTERNAL ERROR: Failed to obfuscate source code for ${filePath}`, reset); 663 } 664 665 fs.writeFileSync(filePath, result.code); 666} 667 668export function genBuildPath(filePath: string, projectPath: string, buildPath: string, projectConfig: Object): string { 669 filePath = toUnixPath(filePath); 670 if (filePath.endsWith(EXTNAME_MJS)) { 671 filePath = filePath.replace(/\.mjs$/, EXTNAME_JS); 672 } 673 if (filePath.endsWith(EXTNAME_CJS)) { 674 filePath = filePath.replace(/\.cjs$/, EXTNAME_JS); 675 } 676 projectPath = toUnixPath(projectPath); 677 678 if (isPackageModulesFile(filePath, projectConfig)) { 679 const packageDir: string = projectConfig.packageDir; 680 const fakePkgModulesPath: string = toUnixPath(path.join(projectConfig.projectRootPath, packageDir)); 681 let output: string = ''; 682 if (filePath.indexOf(fakePkgModulesPath) === -1) { 683 const hapPath: string = toUnixPath(projectConfig.projectRootPath); 684 const tempFilePath: string = filePath.replace(hapPath, ''); 685 const sufStr: string = tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1); 686 output = path.join(projectConfig.nodeModulesPath, ZERO, sufStr); 687 } else { 688 output = filePath.replace(fakePkgModulesPath, path.join(projectConfig.nodeModulesPath, ONE)); 689 } 690 return output; 691 } 692 693 if (filePath.indexOf(projectPath) !== -1) { 694 const sufStr: string = filePath.replace(projectPath, ''); 695 const output: string = path.join(buildPath, sufStr); 696 return output; 697 } 698 699 return ''; 700} 701 702export function getPackageInfo(configFile: string): Array<string> { 703 if (packageCollection.has(configFile)) { 704 return packageCollection.get(configFile); 705 } 706 const data: Object = JSON.parse(fs.readFileSync(configFile).toString()); 707 const bundleName: string = data.app.bundleName; 708 const moduleName: string = data.module.name; 709 packageCollection.set(configFile, [bundleName, moduleName]); 710 return [bundleName, moduleName]; 711} 712 713/** 714 * This Api only used by webpack compiling process - result_process 715 * @param sourcePath The path in build cache dir 716 * @param sourceContent The intermediate js source code 717 */ 718export function generateSourceFilesToTemporary(sourcePath: string, sourceContent: string, sourceMap: Object, 719 projectConfig: Object, logger: Object): void { 720 let jsFilePath: string = genTemporaryPath(sourcePath, projectConfig.projectPath, process.env.cachePath, 721 projectConfig, undefined); 722 if (jsFilePath.length === 0) { 723 return; 724 } 725 if (jsFilePath.endsWith(EXTNAME_ETS)) { 726 jsFilePath = jsFilePath.replace(/\.ets$/, EXTNAME_JS); 727 } else { 728 jsFilePath = jsFilePath.replace(/\.ts$/, EXTNAME_JS); 729 } 730 let sourceMapFile: string = genSourceMapFileName(jsFilePath); 731 if (sourceMapFile.length > 0 && projectConfig.buildArkMode === 'debug') { 732 let source = toUnixPath(sourcePath).replace(toUnixPath(projectConfig.projectRootPath) + '/', ''); 733 // adjust sourceMap info 734 sourceMap.sources = [source]; 735 sourceMap.file = path.basename(sourceMap.file); 736 delete sourceMap.sourcesContent; 737 newSourceMaps[source] = sourceMap; 738 } 739 sourceContent = transformModuleSpecifier(sourcePath, sourceContent, projectConfig); 740 741 mkdirsSync(path.dirname(jsFilePath)); 742 if (projectConfig.buildArkMode === 'debug') { 743 fs.writeFileSync(jsFilePath, sourceContent); 744 return; 745 } 746 747 writeObfuscatedSourceCode({content: sourceContent, buildFilePath: jsFilePath, relativeSourceFilePath: ''}, 748 logger, projectConfig); 749} 750 751export function genAbcFileName(temporaryFile: string): string { 752 let abcFile: string = temporaryFile; 753 if (temporaryFile.endsWith(EXTNAME_TS)) { 754 abcFile = temporaryFile.replace(/\.ts$/, EXTNAME_ABC); 755 } else { 756 abcFile = temporaryFile.replace(/\.js$/, EXTNAME_ABC); 757 } 758 return abcFile; 759} 760 761export function isOhModules(projectConfig: Object): boolean { 762 return projectConfig.packageDir === OH_MODULES; 763} 764 765export function isEs2Abc(projectConfig: Object): boolean { 766 return projectConfig.pandaMode === ES2ABC || projectConfig.pandaMode === 'undefined' || 767 projectConfig.pandaMode === undefined; 768} 769 770export function isTs2Abc(projectConfig: Object): boolean { 771 return projectConfig.pandaMode === TS2ABC; 772} 773 774export function genProtoFileName(temporaryFile: string): string { 775 return temporaryFile.replace(/\.(?:[tj]s|json)$/, EXTNAME_PROTO_BIN); 776} 777 778export function genMergeProtoFileName(temporaryFile: string): string { 779 let protoTempPathArr: string[] = temporaryFile.split(TEMPORARY); 780 const sufStr: string = protoTempPathArr[protoTempPathArr.length - 1]; 781 let protoBuildPath: string = path.join(process.env.cachePath, 'protos', sufStr); 782 783 return protoBuildPath; 784} 785 786export function removeDuplicateInfo(moduleInfos: Array<any>): Array<any> { 787 const tempModuleInfos: any[] = Array<any>(); 788 moduleInfos.forEach((item) => { 789 let check: boolean = tempModuleInfos.every((newItem) => { 790 return item.tempFilePath !== newItem.tempFilePath; 791 }); 792 if (check) { 793 tempModuleInfos.push(item); 794 } 795 }); 796 moduleInfos = tempModuleInfos; 797 798 return moduleInfos; 799} 800 801export function buildCachePath(tailName: string, projectConfig: Object, logger: Object): string { 802 let pathName: string = process.env.cachePath !== undefined ? 803 path.join(projectConfig.cachePath, tailName) : path.join(projectConfig.aceModuleBuild, tailName); 804 validateFilePathLength(pathName, logger); 805 return pathName; 806} 807 808export function getArkBuildDir(arkDir: string): string { 809 if (isWindows()) { 810 return path.join(arkDir, 'build-win'); 811 } else if (isMac()) { 812 return path.join(arkDir, 'build-mac'); 813 } else { 814 return path.join(arkDir, 'build'); 815 } 816} 817 818export function getBuildBinDir(arkDir: string): string { 819 return path.join(getArkBuildDir(arkDir), 'bin'); 820} 821 822export function cleanUpUtilsObjects(): void { 823 newSourceMaps = {}; 824 nameCacheMap.clear(); 825 packageCollection.clear(); 826} 827 828export function getHookEventFactory(share: Object, pluginName: string, hookName: string): Object { 829 if (typeof share.getHookEventFactory === 'function') { 830 return share.getHookEventFactory(pluginName, hookName); 831 } else { 832 return undefined; 833 } 834} 835 836export function createAndStartEvent(eventOrEventFactory: Object, eventName: string, syncFlag = false): Object { 837 if (eventOrEventFactory === undefined) { 838 return undefined; 839 } 840 let event: Object; 841 if (typeof eventOrEventFactory.createSubEvent === 'function') { 842 event = eventOrEventFactory.createSubEvent(eventName); 843 } else { 844 event = eventOrEventFactory.createEvent(eventName); 845 } 846 if (typeof event.startAsyncEvent === 'function' && syncFlag) { 847 event.startAsyncEvent(); 848 } else { 849 event.start(); 850 } 851 return event; 852} 853 854export function stopEvent(event: Object, syncFlag = false): void { 855 if (event !== undefined) { 856 if (typeof event.stopAsyncEvent === 'function' && syncFlag) { 857 event.stopAsyncEvent(); 858 } else { 859 event.stop(); 860 } 861 } 862} 863 864export function compileToolIsRollUp(): boolean { 865 return process.env.compileTool === 'rollup'; 866} 867 868export function transformOhmurlToRecordName(ohmurl: string): string { 869 // @normalized:N&<moduleName>&<bunldName>&<packageName>/entry/ets/xxx/yyy&<version> 870 // ----> <bunldName>&<packageName>/entry/ets/xxx/yyy&<version> 871 return ohmurl.split(SEPARATOR_BITWISE_AND).slice(2).join(SEPARATOR_BITWISE_AND); 872} 873 874export function transformOhmurlToPkgName(ohmurl: string): string { 875 let normalizedPath: string = ohmurl.split(SEPARATOR_BITWISE_AND)[3]; 876 let paths: Array<string> = normalizedPath.split(SEPARATOR_SLASH); 877 if (normalizedPath.startsWith(SEPARATOR_AT)) { 878 // Spec: If the normalized import path starts with '@', the package name is before the second '/' in the normalized 879 // import path, like: @aaa/bbb/ccc/ddd ---> package name is @aaa/bbb 880 return paths.slice(0, 2).join(SEPARATOR_SLASH); 881 } 882 return paths[0]; 883}