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