* Copyright (c) 2023-2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import path from 'path';
import fs from 'fs';
import type sourceMap from 'source-map';
import { minify, MinifyOutput } from 'terser';
import {
endFilesEvent,
endSingleFileEvent,
EventList,
MemoryUtils,
nameCacheMap,
performancePrinter,
ProjectCollections,
startFilesEvent,
startSingleFileEvent,
} from 'arkguard';
import type { HvigorErrorInfo } from 'arkguard';
import {
OH_MODULES,
SEPARATOR_AT,
SEPARATOR_BITWISE_AND,
SEPARATOR_SLASH
} from './fast_build/ark_compiler/common/ark_define';
import {
ARKTS_MODULE_NAME,
PACKAGES,
TEMPORARY,
ZERO,
ONE,
EXTNAME_JS,
EXTNAME_TS,
EXTNAME_MJS,
EXTNAME_CJS,
EXTNAME_ABC,
EXTNAME_ETS,
EXTNAME_D_ETS,
EXTNAME_TS_MAP,
EXTNAME_JS_MAP,
ESMODULE,
FAIL,
TS2ABC,
ES2ABC,
EXTNAME_PROTO_BIN,
NATIVE_MODULE
} from './pre_define';
import {
isMac,
isWindows,
isPackageModulesFile,
genTemporaryPath,
getExtensionIfUnfullySpecifiedFilepath,
mkdirsSync,
toUnixPath,
validateFilePathLength,
harFilesRecord,
getProjectRootPath
} from './utils';
import type { GeneratedFileInHar } from './utils';
import {
extendSdkConfigs,
projectConfig,
sdkConfigPrefix
} from '../main';
import {
getRelativeSourcePath,
mangleFilePath,
setNewNameCache,
getNameCacheByPath,
setUnobfuscationNames,
writeObfuscatedFile
} from './fast_build/ark_compiler/common/ob_config_resolver';
import { moduleRequestCallback } from './fast_build/system_api/api_check_utils';
import { SourceMapGenerator } from './fast_build/ark_compiler/generate_sourcemap';
import { sourceFileBelongProject } from './fast_build/ark_compiler/module/module_source_file';
import { MemoryMonitor } from './fast_build/meomry_monitor/rollup-plugin-memory-monitor';
import { MemoryDefine } from './fast_build/meomry_monitor/memory_define';
import {
ArkTSInternalErrorDescription,
ArkTSErrorDescription,
ErrorCode
} from './fast_build/ark_compiler/error_code';
import {
LogData,
LogDataFactory
} from './fast_build/ark_compiler/logger';
import * as ts from 'typescript';
import {
PreloadFileModules
} from './fast_build/ark_compiler/module/module_preload_file_utils';
export const SRC_MAIN: string = 'src/main';
export let newSourceMaps: Object = {};
export const packageCollection: Map<string, Array<string>> = new Map();
export function getNormalizedOhmUrlByFilepath(filePath: string, projectConfig: Object, logger: Object,
pkgParams: Object, importerFile: string): string {
const { pkgName, pkgPath, isRecordName, moduleName } = pkgParams;
const ohmurlInfo: Object = getPkgInfo(filePath, projectConfig, logger, pkgPath, pkgName, importerFile);
if (!ohmurlInfo) {
return filePath;
}
const { projectFilePath, pkgInfo } = ohmurlInfo;
const recordName: string = `${pkgInfo.bundleName}&${pkgName}/${projectFilePath}&${pkgInfo.version}`;
if (isRecordName) {
return recordName;
}
return `${pkgInfo.isSO ? 'Y' : 'N'}&${moduleName || pkgInfo.moduleName}&${recordName}`;
}
export function getPkgInfo(filePath: string, projectConfig: Object, logger: Object, pkgPath: string,
pkgName: string, importerFile?: string): Object {
if (filePath.startsWith('\x00')) {
filePath = filePath.replace('\x00', '');
}
let unixFilePath: string = toUnixPath(filePath);
unixFilePath = unixFilePath.substring(0, filePath.lastIndexOf('.'));
let pkgInfo = projectConfig.pkgContextInfo[pkgName];
if (!pkgInfo || !pkgPath) {
const errInfo: LogData = LogDataFactory.newInstance(
ErrorCode.ETS2BUNDLE_EXTERNAL_FAILED_TO_RESOLVE_OHM_URL,
ArkTSErrorDescription,
'Failed to resolve OhmUrl. ' +
`Failed to get a resolved OhmUrl for "${filePath}" imported by "${importerFile}".`,
'',
[`Check whether the "${pkgName}" module which ${filePath} belongs to is correctly configured.`,
`Check if the corresponding file name "${filePath}" is correct(including case-sensitivity).`]
);
logger.printError(errInfo);
logger.returnErrorFileId(importerFile);
return undefined;
}
const projectFilePath: string = unixFilePath.replace(toUnixPath(pkgPath) + '/', '');
return { projectFilePath, pkgInfo };
}
export function getOhmUrlByFilepath(filePath: string, projectConfig: Object, logger: Object, namespace?: string,
importerFile?: string): string {
if (filePath.startsWith('\x00')) {
filePath = filePath.replace('\x00', '');
}
let unixFilePath: string = toUnixPath(filePath);
unixFilePath = unixFilePath.substring(0, filePath.lastIndexOf('.'));
const REG_PROJECT_SRC: RegExp = /(\S+)\/src\/(?:main|ohosTest)\/(ets|js|mock)\/(\S+)/;
const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath);
const bundleName: string = packageInfo[0];
const moduleName: string = packageInfo[1];
const moduleRootPath: string = toUnixPath(projectConfig.modulePathMap[moduleName]);
const projectRootPath: string = toUnixPath(getProjectRootPath(filePath, projectConfig, projectConfig?.rootPathSet));
const projectFilePath: string = unixFilePath.replace(projectRootPath, '');
const packageDir: string = projectConfig.packageDir;
const result: RegExpMatchArray | null = projectFilePath.match(REG_PROJECT_SRC);
if (result && result[1].indexOf(packageDir) === -1) {
const relativePath = processSrcMain(result, projectFilePath);
if (namespace && moduleName !== namespace) {
return `${bundleName}/${moduleName}@${namespace}/${relativePath}`;
}
return `${bundleName}/${moduleName}/${relativePath}`;
}
const processParams: Object = {
projectFilePath,
unixFilePath,
packageDir,
projectRootPath,
moduleRootPath,
projectConfig,
namespace,
logger,
importerFile,
originalFilePath: filePath
};
return processPackageDir(processParams);
}
function processSrcMain(result: RegExpMatchArray | null, projectFilePath: string): string {
let langType: string = result[2];
let relativePath: string = result[3];
const REG_SRC_MAIN: RegExp = /src\/(?:main|ohosTest)\/(ets|js)\//;
const srcMainIndex: number = result[1].search(REG_SRC_MAIN);
if (srcMainIndex !== -1) {
relativePath = projectFilePath.substring(srcMainIndex).replace(REG_SRC_MAIN, '');
langType = projectFilePath.replace(relativePath, '').match(REG_SRC_MAIN)[1];
}
return `${langType}/${relativePath}`;
}
function processPackageDir(params: Object): string {
const { projectFilePath, unixFilePath, packageDir, projectRootPath, moduleRootPath,
projectConfig, namespace, logger, importerFile, originalFilePath } = params;
if (projectFilePath.indexOf(packageDir) !== -1) {
if (compileToolIsRollUp()) {
const tryProjectPkg: string = toUnixPath(path.join(projectRootPath, packageDir));
if (unixFilePath.indexOf(tryProjectPkg) !== -1) {
return unixFilePath.replace(tryProjectPkg, `${packageDir}`).replace(new RegExp(packageDir, 'g'), PACKAGES);
}
for (const moduleName in projectConfig.modulePathMap) {
const modulePath: string = projectConfig.modulePathMap[moduleName];
const tryModulePkg: string = toUnixPath(path.resolve(modulePath, packageDir));
if (unixFilePath.indexOf(tryModulePkg) !== -1) {
return unixFilePath.replace(tryModulePkg, `${packageDir}@${moduleName}`).replace(new RegExp(packageDir, 'g'), PACKAGES);
}
}
const errInfo: LogData = LogDataFactory.newInstance(
ErrorCode.ETS2BUNDLE_EXTERNAL_FAILED_TO_RESOLVE_OHM_URL,
ArkTSErrorDescription,
'Failed to resolve OhmUrl. ' +
`Failed to get a resolved OhmUrl for "${originalFilePath}" imported by "${importerFile}".`,
'',
[`Check whether the module which ${originalFilePath} belongs to is correctly configured.`,
`Check if the corresponding file name "${originalFilePath}" is correct(including case-sensitivity).`]
);
logger.printError(errInfo);
logger.returnErrorFileId(importerFile);
return originalFilePath;
}
const tryProjectPkg: string = toUnixPath(path.join(projectRootPath, packageDir));
if (unixFilePath.indexOf(tryProjectPkg) !== -1) {
return unixFilePath.replace(tryProjectPkg, `${packageDir}/${ONE}`).replace(new RegExp(packageDir, 'g'), PACKAGES);
}
const tryModulePkg: string = toUnixPath(path.join(moduleRootPath, packageDir));
if (unixFilePath.indexOf(tryModulePkg) !== -1) {
return unixFilePath.replace(tryModulePkg, `${packageDir}/${ZERO}`).replace(new RegExp(packageDir, 'g'), PACKAGES);
}
}
const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath);
const bundleName: string = packageInfo[0];
const moduleName: string = packageInfo[1];
for (const key in projectConfig.modulePathMap) {
const moduleRootPath: string = toUnixPath(projectConfig.modulePathMap[key]);
if (unixFilePath.indexOf(moduleRootPath + '/') !== -1) {
const relativeModulePath: string = unixFilePath.replace(moduleRootPath + '/', '');
if (namespace && moduleName !== namespace) {
return `${bundleName}/${moduleName}@${namespace}/${relativeModulePath}`;
}
return `${bundleName}/${moduleName}/${relativeModulePath}`;
}
}
const errInfo: LogData = LogDataFactory.newInstance(
ErrorCode.ETS2BUNDLE_EXTERNAL_FAILED_TO_RESOLVE_OHM_URL,
ArkTSErrorDescription,
'Failed to resolve OhmUrl. ' +
`Failed to get a resolved OhmUrl for "${originalFilePath}" imported by "${importerFile}".`,
'',
[`Check whether the module which ${originalFilePath} belongs to is correctly configured.`,
`Check if the corresponding file name "${originalFilePath}" is correct(including case-sensitivity).`]
);
logger.printError(errInfo);
logger.returnErrorFileId(importerFile);
return originalFilePath;
}
export type OhmUrlParams = {
moduleRequest: string;
moduleId: string;
config?: Object;
logger?: Object;
importerFile?: string;
};
export function getOhmUrlBySystemApiOrLibRequest(params: OhmUrlParams,
useNormalizedOHMUrl: boolean = false, needPreloadSo: boolean = false): string {
const REG_SYSTEM_MODULE: RegExp = new RegExp(`@(${sdkConfigPrefix})\\.(\\S+)`);
const REG_LIB_SO: RegExp = /lib(\S+)\.so/;
const moduleRequest = params.moduleRequest;
if (REG_SYSTEM_MODULE.test(moduleRequest.trim())) {
return moduleRequest.replace(REG_SYSTEM_MODULE, (_, moduleType, systemKey) => {
let moduleRequestStr = '';
if (extendSdkConfigs) {
moduleRequestStr = moduleRequestCallback(moduleRequest, _, moduleType, systemKey);
}
if (moduleRequestStr !== '') {
needPreloadSo && PreloadFileModules.updatePreloadFileDataByItems(
moduleRequest, moduleRequestStr, params.moduleId);
return moduleRequestStr;
}
let resultOhmUrl: string = '';
const systemModule: string = `${moduleType}.${systemKey}`;
if (NATIVE_MODULE.has(systemModule)) {
resultOhmUrl = `@native:${systemModule}`;
} else if (moduleType === ARKTS_MODULE_NAME) {
resultOhmUrl = `@ohos:${systemModule}`;
} else {
resultOhmUrl = `@ohos:${systemKey}`;
}
needPreloadSo && PreloadFileModules.updatePreloadFileDataByItems(moduleRequest, resultOhmUrl, params.moduleId);
return resultOhmUrl;
});
}
if (REG_LIB_SO.test(moduleRequest.trim())) {
if (useNormalizedOHMUrl) {
const pkgInfo = params.config.pkgContextInfo[moduleRequest];
if (!pkgInfo) {
const errInfo: LogData = LogDataFactory.newInstance(
ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_PKG_CONTENT_INFO,
ArkTSInternalErrorDescription,
`Can not get pkgContextInfo of package '${moduleRequest}' ` +
`which being imported by '${params.importerFile}'`
);
params.logger?.printError(errInfo);
return moduleRequest;
}
const isSo = pkgInfo.isSO ? 'Y' : 'N';
return `@normalized:${isSo}&${pkgInfo.moduleName}&${pkgInfo.bundleName}&${moduleRequest}&${pkgInfo.version}`;
}
return moduleRequest.replace(REG_LIB_SO, (_, libsoKey) => {
return `@app:${projectConfig.bundleName}/${projectConfig.moduleName}/${libsoKey}`;
});
}
return undefined;
}
export function genSourceMapFileName(temporaryFile: string): string {
let abcFile: string = temporaryFile;
if (temporaryFile.endsWith(EXTNAME_TS)) {
abcFile = temporaryFile.replace(/\.ts$/, EXTNAME_TS_MAP);
} else {
abcFile = temporaryFile.replace(/\.js$/, EXTNAME_JS_MAP);
}
return abcFile;
}
export function getBuildModeInLowerCase(projectConfig: Object): string {
return (compileToolIsRollUp() ? projectConfig.buildMode : projectConfig.buildArkMode).toLowerCase();
}
* This Api only used by webpack compiling process - js-loader
* @param sourcePath The path in build cache dir
* @param sourceCode The intermediate js source code
*/
export function writeFileSyncByString(sourcePath: string, sourceCode: string, projectConfig: Object, logger: Object): void {
const filePath: string = genTemporaryPath(sourcePath, projectConfig.projectPath, process.env.cachePath,
projectConfig, undefined);
if (filePath.length === 0) {
return;
}
mkdirsSync(path.dirname(filePath));
if (/\.js$/.test(sourcePath)) {
sourceCode = transformModuleSpecifier(sourcePath, sourceCode, projectConfig);
if (projectConfig.buildArkMode === 'debug') {
fs.writeFileSync(filePath, sourceCode);
return;
}
writeObfuscatedSourceCode({content: sourceCode, buildFilePath: filePath, relativeSourceFilePath: ''},
logger, projectConfig);
}
if (/\.json$/.test(sourcePath)) {
fs.writeFileSync(filePath, sourceCode);
}
}
export function transformModuleSpecifier(sourcePath: string, sourceCode: string, projectConfig: Object): string {
const REG_RELATIVE_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]((?:\.\/|\.\.\/)[^'"]+|(?:\.\/?|\.\.\/?))['"]/g;
const REG_HAR_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]([^\.\/][^'"]+)['"]/g;
const REG_REQUIRE_NATIVE_MODULE: RegExp = /var (\S+) = globalThis.requireNativeModule\(['"](\S+)['"]\);/g;
const REG_REQUIRE_NAPI_APP: RegExp = /var (\S+) = globalThis.requireNapi\(['"](\S+)['"], true, ['"](\S+)['"]\);/g;
const REG_REQUIRE_NAPI_OHOS: RegExp = /var (\S+) = globalThis.requireNapi\(['"](\S+)['"]\);/g;
return sourceCode.replace(REG_HAR_DEPENDENCY, (item, moduleRequest) => {
return replaceHarDependency(item, moduleRequest, projectConfig);
}).replace(REG_RELATIVE_DEPENDENCY, (item, moduleRequest) => {
return replaceRelativeDependency(item, moduleRequest, toUnixPath(sourcePath), projectConfig);
}).replace(REG_REQUIRE_NATIVE_MODULE, (_, moduleRequest, moduleName) => {
return `import ${moduleRequest} from '@native:${moduleName}';`;
}).replace(REG_REQUIRE_NAPI_APP, (_, moduleRequest, soName, moudlePath) => {
return `import ${moduleRequest} from '@app:${moudlePath}/${soName}';`;
}).replace(REG_REQUIRE_NAPI_OHOS, (_, moduleRequest, moduleName) => {
return `import ${moduleRequest} from '@ohos:${moduleName}';`;
});
}
function removeSuffix(filePath: string): string {
const SUFFIX_REG = /\.(?:d\.)?(ets|ts|mjs|cjs|js)$/;
return filePath.split(path.sep).join('/').replace(SUFFIX_REG, '');
}
export function getNormalizedOhmUrlByModuleRequest(moduleInfoByModuleRequest: Object, projectConfig: Object,
logger?: Object): string {
const normalizedPath = moduleInfoByModuleRequest.normalizedPath;
const pkgName = moduleInfoByModuleRequest.packageName;
const pkgInfo: Object = projectConfig.pkgContextInfo[pkgName];
if (!normalizedPath || !pkgName || !pkgInfo) {
const errInfo: LogData = LogDataFactory.newInstance(
ErrorCode.ETS2BUNDLE_INTERNAL_PACKAGE_NOT_FOUND_IN_CONTEXT_INFO,
ArkTSInternalErrorDescription,
`Failed to find package '${pkgName}'. ` +
`Failed to obtain package '${pkgName}' from the package context information.`
);
logger?.printError(errInfo);
return normalizedPath;
}
const isSo = pkgInfo.isSO ? 'Y' : 'N';
return `@normalized:${isSo}&${pkgInfo.moduleName}&${pkgInfo.bundleName}&${toUnixPath(normalizedPath)}&${pkgInfo.version}`;
}
export function getNormalizedOhmUrlByAliasName(aliasName: string, projectConfig: Object,
logger?: Object, filePath?: string): string {
let pkgName: string = aliasName;
const aliasPkgNameMap: Map<string, string> = projectConfig.dependencyAliasMap;
if (aliasPkgNameMap.has(aliasName)) {
pkgName = aliasPkgNameMap.get(aliasName);
}
const pkgInfo: Object = projectConfig.pkgContextInfo[pkgName];
if (!pkgInfo) {
const errInfo: LogData = LogDataFactory.newInstance(
ErrorCode.ETS2BUNDLE_INTERNAL_PACKAGE_NOT_FOUND_IN_CONTEXT_INFO,
ArkTSInternalErrorDescription,
`Failed to find package '${pkgName}'. ` +
`Failed to obtain package '${pkgName}' from the package context information.`
);
logger.printError(errInfo);
}
let normalizedPath: string = '';
if (!filePath) {
if (!pkgInfo.entryPath) {
const errInfo: LogData = LogDataFactory.newInstance(
ErrorCode.ETS2BUNDLE_INTERNAL_PACKAGE_ENTRY_FILE_NOT_FOUND,
ArkTSInternalErrorDescription,
`Failed to find entry file of '${pkgName}'. ` +
`Failed to obtain the entry file information of '${pkgName}' from the package context information.`
);
logger.printError(errInfo);
}
normalizedPath = `${pkgName}/${toUnixPath(pkgInfo.entryPath)}`;
normalizedPath = removeSuffix(normalizedPath);
} else {
const relativePath = toUnixPath(filePath).replace(aliasName, '');
normalizedPath = `${pkgName}${relativePath}`;
}
const isSo = pkgInfo.isSO ? 'Y' : 'N';
return `@normalized:${isSo}&${pkgInfo.moduleName}&${pkgInfo.bundleName}&${normalizedPath}&${pkgInfo.version}`;
}
export function getOhmUrlByByteCodeHar(moduleRequest: string, projectConfig: Object, rollupObject: Object, logger?: Object):
string | undefined {
if (projectConfig.useNormalizedOHMUrl &&
projectConfig.byteCodeHarInfo && Object.keys(projectConfig.byteCodeHarInfo).length > 0) {
const moduleInfoByModuleRequest: Object = rollupObject.share?.importResolver?.(moduleRequest);
if (moduleInfoByModuleRequest) {
return getNormalizedOhmUrlByModuleRequest(moduleInfoByModuleRequest, projectConfig, logger);
}
let aliasName: string = getAliasNameFromPackageMap(projectConfig.byteCodeHarInfo, moduleRequest);
if (aliasName) {
return getNormalizedOhmUrlByAliasName(aliasName, projectConfig, logger);
}
for (const byteCodeHarName in projectConfig.byteCodeHarInfo) {
if (moduleRequest.startsWith(byteCodeHarName + '/')) {
return getNormalizedOhmUrlByAliasName(byteCodeHarName, projectConfig, logger, moduleRequest);
}
}
}
return undefined;
}
function getAliasNameFromPackageMap(externalPkgMap: Object, moduleRequest: string): string | undefined {
const ONLY_SLASHES_REGEX: RegExp = /^\/*$|^\\*$/;
const keys: string[] = Object.keys(externalPkgMap);
for (const key of keys) {
if (moduleRequest === key) {
return key;
}
if (moduleRequest.length > key.length && moduleRequest.startsWith(key)) {
const remaining: string = moduleRequest.replace(key, '');
if (ONLY_SLASHES_REGEX.test(remaining)) {
return key;
}
}
}
return undefined;
}
export function getOhmUrlByExternalPackage(moduleRequest: string, projectConfig: Object, rollupObject?: Object, logger?: Object,
useNormalizedOHMUrl: boolean = false): string | undefined {
let externalPkgMap: Object = Object.assign({}, projectConfig.hspNameOhmMap, projectConfig.harNameOhmMap);
if (Object.keys(externalPkgMap).length !== 0) {
const moduleInfoByModuleRequest: Object = rollupObject?.share?.importResolver?.(moduleRequest);
if (useNormalizedOHMUrl && moduleInfoByModuleRequest) {
return getNormalizedOhmUrlByModuleRequest(moduleInfoByModuleRequest, projectConfig, logger);
}
let aliasName: string = getAliasNameFromPackageMap(externalPkgMap, moduleRequest);
if (aliasName) {
if (useNormalizedOHMUrl) {
return getNormalizedOhmUrlByAliasName(aliasName, projectConfig, logger);
}
return externalPkgMap[moduleRequest];
}
for (const externalPkgName in externalPkgMap) {
if (moduleRequest.startsWith(externalPkgName + '/')) {
if (useNormalizedOHMUrl) {
return getNormalizedOhmUrlByAliasName(externalPkgName, projectConfig, logger, moduleRequest);
}
const idx: number = externalPkgMap[externalPkgName].split('/', 2).join('/').length;
const ohmName: string = externalPkgMap[externalPkgName].substring(0, idx);
if (moduleInfoByModuleRequest) {
const relativePathByModuleRequest: string = toUnixPath(moduleInfoByModuleRequest.normalizedPath).split(SRC_MAIN)[1];
return ohmName + relativePathByModuleRequest;
}
if (moduleRequest.indexOf(externalPkgName + '/' + SRC_MAIN) === 0) {
return moduleRequest.replace(externalPkgName + '/' + SRC_MAIN, ohmName);
} else {
return moduleRequest.replace(externalPkgName, ohmName);
}
}
}
}
return undefined;
}
function replaceHarDependency(item: string, moduleRequest: string, projectConfig: Object): string {
const hspOhmUrl: string | undefined = getOhmUrlByExternalPackage(moduleRequest, projectConfig);
if (hspOhmUrl !== undefined) {
return item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => {
return quotation + hspOhmUrl + quotation;
});
}
return item;
}
function locateActualFilePathWithModuleRequest(absolutePath: string): string {
if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isDirectory()) {
return absolutePath;
}
const filePath: string = absolutePath + getExtensionIfUnfullySpecifiedFilepath(absolutePath);
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
return absolutePath;
}
return path.join(absolutePath, 'index');
}
function replaceRelativeDependency(item: string, moduleRequest: string, sourcePath: string, projectConfig: Object): string {
if (sourcePath && projectConfig.compileMode === ESMODULE) {
const SUFFIX_REG: RegExp = /\.(?:[cm]?js|[e]?ts|json)$/;
moduleRequest = moduleRequest.replace(SUFFIX_REG, '');
item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => {
let normalizedModuleRequest: string = toUnixPath(path.normalize(moduleRequest));
if (moduleRequest.startsWith('./')) {
normalizedModuleRequest = './' + normalizedModuleRequest;
}
return quotation + normalizedModuleRequest + quotation;
});
const filePath: string =
locateActualFilePathWithModuleRequest(path.resolve(path.dirname(sourcePath), moduleRequest));
const result: RegExpMatchArray | null =
filePath.match(/(\S+)(\/|\\)src(\/|\\)(?:main|ohosTest)(\/|\\)(ets|js)(\/|\\)(\S+)/);
if (result && projectConfig.aceModuleJsonPath) {
const npmModuleIdx: number = result[1].search(/(\/|\\)node_modules(\/|\\)/);
const projectRootPath: string = projectConfig.projectRootPath;
if (npmModuleIdx === -1 || npmModuleIdx === projectRootPath.search(/(\/|\\)node_modules(\/|\\)/)) {
const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath);
const bundleName: string = packageInfo[0];
const moduleName: string = packageInfo[1];
moduleRequest = `@bundle:${bundleName}/${moduleName}/${result[5]}/${toUnixPath(result[7])}`;
item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => {
return quotation + moduleRequest + quotation;
});
}
}
}
return item;
}
* Informantion of build files
*/
export interface ModuleInfo {
content: string,
* the path in build cache dir
*/
buildFilePath: string,
* the `originSourceFilePath` relative to project root dir.
*/
relativeSourceFilePath: string,
* the origin source file path will be set with rollup moduleId when obfuscate intermediate js source code,
* whereas be set with tsc node.fileName when obfuscate intermediate ts source code.
*/
originSourceFilePath?: string,
rollupModuleId?: string
}
export async function writeObfuscatedSourceCode(moduleInfo: ModuleInfo, logger: Function | Object,
projectConfig: Object, rollupNewSourceMaps: Object = {}): Promise<void> {
* Obfuscation mode rules:
* - When isArkguardEnabled=true (source code obfuscation): Obfuscate ALL files
* - When isBytecodeObfEnabled=true (bytecode obfuscation): Only decl files will be obfuscated
* - These two flags are mutually exclusive (only one can be true at a time)
*/
let shouldSkipObfuscation = false;
if (projectConfig.isBytecodeObfEnabled) {
shouldSkipObfuscation = !/\.d\.e?ts$/.test(moduleInfo.buildFilePath);
}
const isObfuscatorEnabled = projectConfig.arkObfuscator &&
(projectConfig.isArkguardEnabled || !shouldSkipObfuscation);
if (compileToolIsRollUp() && isObfuscatorEnabled) {
startFilesEvent(moduleInfo.buildFilePath);
const recordInfo = MemoryMonitor.recordStage(MemoryDefine.WRITE_OBFUSCATED_SOURCE_CODE);
const previousStageSourceMap: sourceMap.RawSourceMap | undefined = getPreviousStageSourceMap(moduleInfo, rollupNewSourceMaps);
collectObfuscationFileContent(moduleInfo, projectConfig, previousStageSourceMap);
if (!projectConfig.arkObfuscator.shouldReObfuscate) {
MemoryUtils.tryGC();
await writeArkguardObfuscatedSourceCode(moduleInfo, logger as Function, projectConfig, previousStageSourceMap);
MemoryUtils.tryGC();
}
MemoryMonitor.stopRecordStage(recordInfo);
endFilesEvent(moduleInfo.buildFilePath, undefined, true);
return;
}
mkdirsSync(path.dirname(moduleInfo.buildFilePath));
if (!compileToolIsRollUp()) {
await writeMinimizedSourceCode(moduleInfo.content, moduleInfo.buildFilePath, logger, projectConfig.compileHar);
return;
}
if (moduleInfo.originSourceFilePath) {
const originSourceFilePath = toUnixPath(moduleInfo.originSourceFilePath);
let genFileInHar: GeneratedFileInHar = harFilesRecord.get(originSourceFilePath);
if (!genFileInHar) {
genFileInHar = { sourcePath: originSourceFilePath };
}
if (!genFileInHar.sourceCachePath) {
genFileInHar.sourceCachePath = toUnixPath(moduleInfo.buildFilePath);
}
harFilesRecord.set(originSourceFilePath, genFileInHar);
}
writeFileSyncCaseAware(moduleInfo.buildFilePath, moduleInfo.content);
}
export function getPreviousStageSourceMap(moduleInfo: ModuleInfo, rollupNewSourceMaps: Object = {}): sourceMap.RawSourceMap | undefined {
const isDeclaration = (/\.d\.e?ts$/).test(moduleInfo.buildFilePath);
const sourceMapGeneratorInstance = SourceMapGenerator.getInstance();
let previousStageSourceMap: sourceMap.RawSourceMap | undefined = undefined;
if (moduleInfo.relativeSourceFilePath.length > 0 && !isDeclaration) {
const selectedFilePath = sourceMapGeneratorInstance.isNewSourceMaps() ? moduleInfo.rollupModuleId! : moduleInfo.relativeSourceFilePath;
previousStageSourceMap = sourceMapGeneratorInstance.getSpecifySourceMap(rollupNewSourceMaps, selectedFilePath) as sourceMap.RawSourceMap;
}
return previousStageSourceMap;
}
* collect current obfuscated input content, used for re-obfuscate.
*/
export function collectObfuscationFileContent(moduleInfo: ModuleInfo, projectConfig: Object,
previousStageSourceMap: sourceMap.RawSourceMap | undefined): void {
const arkObfuscator = projectConfig.arkObfuscator;
const isDeclaration = (/\.d\.e?ts$/).test(moduleInfo.buildFilePath);
const isOriginalDeclaration = (/\.d\.e?ts$/).test(moduleInfo.originSourceFilePath);
if (arkObfuscator.filePathManager && (!isDeclaration || isOriginalDeclaration)) {
const fileContent: ProjectCollections.FileContent = {
moduleInfo: moduleInfo,
previousStageSourceMap: previousStageSourceMap as ts.RawSourceMap,
};
arkObfuscator.fileContentManager.updateFileContent(fileContent);
}
}
* This Api only be used by rollup compiling process & only be
* exported for unit test.
*/
export async function writeArkguardObfuscatedSourceCode(moduleInfo: ModuleInfo, printObfLogger: Function,
projectConfig: Object, previousStageSourceMap: sourceMap.RawSourceMap | undefined): Promise<void> {
const arkObfuscator = projectConfig.arkObfuscator;
const isDeclaration = (/\.d\.e?ts$/).test(moduleInfo.buildFilePath);
const packageDir = projectConfig.packageDir;
const projectRootPath = projectConfig.projectRootPath;
const useNormalized = projectConfig.useNormalizedOHMUrl;
const localPackageSet = projectConfig.localPackageSet;
const useTsHar = projectConfig.useTsHar;
const sourceMapGeneratorInstance = SourceMapGenerator.getInstance();
const historyNameCache: Map<string, string> = getNameCacheByPath(moduleInfo, isDeclaration, projectRootPath);
let mixedInfo: { content: string, sourceMap?: Object, nameCache?: Object, unobfuscationNameMap?: Map<string, Set<string>>};
let projectInfo: {
packageDir: string,
projectRootPath: string,
localPackageSet: Set<string>,
useNormalized: boolean,
useTsHar: boolean
} = { packageDir, projectRootPath, localPackageSet, useNormalized, useTsHar };
let filePathObj = { buildFilePath: moduleInfo.buildFilePath, relativeFilePath: moduleInfo.relativeSourceFilePath };
try {
startSingleFileEvent(EventList.OBFUSCATE, performancePrinter.timeSumPrinter, filePathObj.buildFilePath);
mixedInfo = await arkObfuscator.obfuscate(moduleInfo.content, filePathObj, previousStageSourceMap,
historyNameCache, moduleInfo.originSourceFilePath, projectInfo);
endSingleFileEvent(EventList.OBFUSCATE, performancePrinter.timeSumPrinter);
} catch (err) {
const errorInfo: string = `ArkTS:INTERNAL ERROR: Failed to obfuscate file '${moduleInfo.relativeSourceFilePath}' with arkguard. ${err}`;
const errorCodeInfo: HvigorErrorInfo = {
code: '10810001',
description: 'ArkTS compiler Error',
cause: `ArkTS:INTERNAL ERROR: Failed to obfuscate file '${moduleInfo.relativeSourceFilePath}' with arkguard. ${err}`,
position: moduleInfo.relativeSourceFilePath,
solutions: [`Please modify the code based on the error information.`],
};
printObfLogger(errorInfo, errorCodeInfo, 'error');
}
if (mixedInfo.sourceMap && !isDeclaration) {
const selectedFilePath = sourceMapGeneratorInstance.isNewSourceMaps() ? moduleInfo.rollupModuleId! : moduleInfo.relativeSourceFilePath;
mixedInfo.sourceMap.sources = [moduleInfo.relativeSourceFilePath];
sourceMapGeneratorInstance.fillSourceMapPackageInfo(moduleInfo.rollupModuleId!, mixedInfo.sourceMap);
sourceMapGeneratorInstance.updateSourceMap(selectedFilePath, mixedInfo.sourceMap);
}
setNewNameCache(mixedInfo.nameCache, isDeclaration, moduleInfo, projectConfig);
setUnobfuscationNames(mixedInfo.unobfuscationNameMap, moduleInfo.relativeSourceFilePath, isDeclaration);
const newFilePath: string = tryMangleFileName(moduleInfo.buildFilePath, projectConfig, moduleInfo.originSourceFilePath);
if (newFilePath !== moduleInfo.buildFilePath && !isDeclaration) {
sourceMapGeneratorInstance.saveKeyMappingForObfFileName(moduleInfo.rollupModuleId!);
}
writeObfuscatedFile(newFilePath, mixedInfo.content ?? '');
}
* This function will be called when obfuscating sourceFile and declaration file.
* Declaration file is not affected by bytecode obfuscation.
* When bytecode obfuscation is enabel, the name of sourceFile will not be obfuscated.
* Because harFilesRecord is collected in this function
* This parameter is added to process sourceFile and return early only when bytecode obfuscation is turned on.
* It does not affect the original call effect.
*/
export function tryMangleFileName(filePath: string, projectConfig: Object, originalFilePath: string, skipObf: boolean = false): string {
originalFilePath = toUnixPath(originalFilePath);
let isOhModule = isPackageModulesFile(originalFilePath, projectConfig);
let genFileInHar: GeneratedFileInHar = harFilesRecord.get(originalFilePath);
if (!genFileInHar) {
genFileInHar = { sourcePath: originalFilePath };
harFilesRecord.set(originalFilePath, genFileInHar);
}
if (skipObf) {
return filePath;
}
if (projectConfig.obfuscationMergedObConfig?.options?.enableFileNameObfuscation && !isOhModule) {
const mangledFilePath: string = mangleFilePath(filePath);
if ((/\.d\.e?ts$/).test(filePath)) {
if (projectConfig?.projectArkOption?.bundle?.bundledDeclare &&
fs.existsSync(genFileInHar.originalDeclarationCachePath)) {
fs.unlinkSync(genFileInHar.originalDeclarationCachePath);
}
genFileInHar.obfuscatedDeclarationCachePath = mangledFilePath;
} else {
genFileInHar.obfuscatedSourceCachePath = mangledFilePath;
}
filePath = mangledFilePath;
} else if (!(/\.d\.e?ts$/).test(filePath)) {
genFileInHar.sourceCachePath = filePath;
}
return filePath;
}
export async function mangleDeclarationFileName(printObfLogger: Function, projectConfig: Object,
sourceFileBelongProject: Map<string, string>): Promise<void> {
for (const [sourcePath, genFilesInHar] of harFilesRecord) {
if (genFilesInHar.originalDeclarationCachePath && genFilesInHar.originalDeclarationContent) {
let filePath = genFilesInHar.originalDeclarationCachePath;
let relativeSourceFilePath = getRelativeSourcePath(filePath,
projectConfig.projectRootPath, sourceFileBelongProject.get(toUnixPath(filePath)));
await writeObfuscatedSourceCode({
content: genFilesInHar.originalDeclarationContent,
buildFilePath: genFilesInHar.originalDeclarationCachePath,
relativeSourceFilePath: relativeSourceFilePath,
originSourceFilePath: projectConfig?.projectArkOption?.bundle?.bundledDeclare ? filePath : sourcePath
}, printObfLogger, projectConfig, {});
}
}
}
export async function writeMinimizedSourceCode(content: string, filePath: string, logger: Object,
isHar: boolean = false): Promise<void> {
let result: MinifyOutput;
try {
const minifyOptions = {
compress: {
join_vars: false,
sequences: 0,
directives: false
}
};
if (!isHar) {
minifyOptions.format = {
semicolons: false,
beautify: true,
indent_level: 2
};
}
result = await minify(content, minifyOptions);
} catch {
const errInfo: LogData = LogDataFactory.newInstance(
ErrorCode.ETS2BUNDLE_INTERNAL_SOURCE_CODE_OBFUSCATION_FAILED,
ArkTSInternalErrorDescription,
`Failed to obfuscate source code for ${filePath}`
);
logger.printError(errInfo);
}
fs.writeFileSync(filePath, result.code);
}
export function writeDeclarationFiles(compileMode: string): void {
if (compileToolIsRollUp() && compileMode === ESMODULE) {
for (const genFilesInHar of harFilesRecord.values()) {
if (genFilesInHar.originalDeclarationCachePath && genFilesInHar.originalDeclarationContent) {
mkdirsSync(path.dirname(genFilesInHar.originalDeclarationCachePath));
writeFileSyncCaseAware(genFilesInHar.originalDeclarationCachePath, genFilesInHar.originalDeclarationContent);
}
}
}
}
export function genBuildPath(filePath: string, projectPath: string, buildPath: string, projectConfig: Object): string {
filePath = toUnixPath(filePath);
if (filePath.endsWith(EXTNAME_MJS)) {
filePath = filePath.replace(/\.mjs$/, EXTNAME_JS);
}
if (filePath.endsWith(EXTNAME_CJS)) {
filePath = filePath.replace(/\.cjs$/, EXTNAME_JS);
}
projectPath = toUnixPath(projectPath);
if (isPackageModulesFile(filePath, projectConfig)) {
const packageDir: string = projectConfig.packageDir;
const fakePkgModulesPath: string = toUnixPath(path.join(projectConfig.projectRootPath, packageDir));
let output: string = '';
if (filePath.indexOf(fakePkgModulesPath) === -1) {
const hapPath: string = toUnixPath(projectConfig.projectRootPath);
const tempFilePath: string = filePath.replace(hapPath, '');
const sufStr: string = tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1);
output = path.join(projectConfig.nodeModulesPath, ZERO, sufStr);
} else {
output = filePath.replace(fakePkgModulesPath, path.join(projectConfig.nodeModulesPath, ONE));
}
return output;
}
if (filePath.indexOf(projectPath) !== -1) {
const sufStr: string = filePath.replace(projectPath, '');
const output: string = path.join(buildPath, sufStr);
return output;
}
return '';
}
export function getPackageInfo(configFile: string): Array<string> {
if (packageCollection.has(configFile)) {
return packageCollection.get(configFile);
}
const data: Object = JSON.parse(fs.readFileSync(configFile).toString());
const bundleName: string = data.app.bundleName;
const moduleName: string = data.module.name;
packageCollection.set(configFile, [bundleName, moduleName]);
return [bundleName, moduleName];
}
* This Api only used by webpack compiling process - result_process
* @param sourcePath The path in build cache dir
* @param sourceContent The intermediate js source code
*/
export function generateSourceFilesToTemporary(sourcePath: string, sourceContent: string, sourceMap: Object,
projectConfig: Object, logger: Object): void {
let jsFilePath: string = genTemporaryPath(sourcePath, projectConfig.projectPath, process.env.cachePath,
projectConfig, undefined);
if (jsFilePath.length === 0) {
return;
}
if (jsFilePath.endsWith(EXTNAME_ETS)) {
jsFilePath = jsFilePath.replace(/\.ets$/, EXTNAME_JS);
} else {
jsFilePath = jsFilePath.replace(/\.ts$/, EXTNAME_JS);
}
let sourceMapFile: string = genSourceMapFileName(jsFilePath);
if (sourceMapFile.length > 0 && projectConfig.buildArkMode === 'debug') {
let source = toUnixPath(sourcePath).replace(toUnixPath(projectConfig.projectRootPath) + '/', '');
sourceMap.sources = [source];
sourceMap.file = path.basename(sourceMap.file);
delete sourceMap.sourcesContent;
newSourceMaps[source] = sourceMap;
}
sourceContent = transformModuleSpecifier(sourcePath, sourceContent, projectConfig);
mkdirsSync(path.dirname(jsFilePath));
if (projectConfig.buildArkMode === 'debug') {
fs.writeFileSync(jsFilePath, sourceContent);
return;
}
writeObfuscatedSourceCode({content: sourceContent, buildFilePath: jsFilePath, relativeSourceFilePath: ''},
logger, projectConfig);
}
export function genAbcFileName(temporaryFile: string): string {
let abcFile: string = temporaryFile;
if (temporaryFile.endsWith(EXTNAME_TS)) {
abcFile = temporaryFile.replace(/\.ts$/, EXTNAME_ABC);
} else {
abcFile = temporaryFile.replace(/\.js$/, EXTNAME_ABC);
}
return abcFile;
}
export function isOhModules(projectConfig: Object): boolean {
return projectConfig.packageDir === OH_MODULES;
}
export function isEs2Abc(projectConfig: Object): boolean {
return projectConfig.pandaMode === ES2ABC || projectConfig.pandaMode === 'undefined' ||
projectConfig.pandaMode === undefined;
}
export function isTs2Abc(projectConfig: Object): boolean {
return projectConfig.pandaMode === TS2ABC;
}
export function genProtoFileName(temporaryFile: string): string {
return temporaryFile.replace(/\.(?:[tj]s|json)$/, EXTNAME_PROTO_BIN);
}
export function genMergeProtoFileName(temporaryFile: string): string {
let protoTempPathArr: string[] = temporaryFile.split(TEMPORARY);
const sufStr: string = protoTempPathArr[protoTempPathArr.length - 1];
let protoBuildPath: string = path.join(process.env.cachePath, 'protos', sufStr);
return protoBuildPath;
}
export function removeDuplicateInfo(moduleInfos: Array<any>): Array<any> {
const tempModuleInfos: any[] = Array<any>();
moduleInfos.forEach((item) => {
let check: boolean = tempModuleInfos.every((newItem) => {
return item.tempFilePath !== newItem.tempFilePath;
});
if (check) {
tempModuleInfos.push(item);
}
});
moduleInfos = tempModuleInfos;
return moduleInfos;
}
export function buildCachePath(tailName: string, projectConfig: Object, logger: Object): string {
let pathName: string = process.env.cachePath !== undefined ?
path.join(projectConfig.cachePath, tailName) : path.join(projectConfig.aceModuleBuild, tailName);
validateFilePathLength(pathName, logger);
return pathName;
}
export function getArkBuildDir(arkDir: string): string {
if (isWindows()) {
return path.join(arkDir, 'build-win');
} else if (isMac()) {
return path.join(arkDir, 'build-mac');
} else {
return path.join(arkDir, 'build');
}
}
export function getBuildBinDir(arkDir: string): string {
return path.join(getArkBuildDir(arkDir), 'bin');
}
export function cleanUpUtilsObjects(): void {
newSourceMaps = {};
nameCacheMap.clear();
packageCollection.clear();
}
export function compileToolIsRollUp(): boolean {
return process.env.compileTool === 'rollup';
}
export function transformOhmurlToRecordName(ohmurl: string): string {
return ohmurl.split(SEPARATOR_BITWISE_AND).slice(2).join(SEPARATOR_BITWISE_AND);
}
export function transformOhmurlToPkgName(ohmurl: string): string {
let normalizedPath: string = ohmurl.split(SEPARATOR_BITWISE_AND)[3];
let paths: Array<string> = normalizedPath.split(SEPARATOR_SLASH);
if (normalizedPath.startsWith(SEPARATOR_AT)) {
return paths.slice(0, 2).join(SEPARATOR_SLASH);
}
return paths[0];
}
export function writeFileSyncCaseAware(filePath: fs.PathOrFileDescriptor, data: string | NodeJS.ArrayBufferView, options?: fs.WriteFileOptions): void {
if (typeof filePath !== 'number' && fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
fs.writeFileSync(filePath, data, options);
}