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